예제 #1
0
def attribute_query(environ,
                    attribute_requests,
                    subject_id,
                    subject_id_format='urn:esg:openid'):
    """
    Make a query for a set of attributes
    Returns a dictionary of values for each attribute in the query
    
    @param attribute_requests: SAML Attribute instances
    @param subject_id: The ID of the subject, e.g. openid
    @param subject_id_format: optional format of the ID, default is openid
    """

    # Grab info from the environ
    saml_trusted_ca_dir = environ.get('saml_trusted_ca_dir', '')
    attr_service_uri = environ.get('attr_service_uri', '')

    client_binding = AttributeQuerySslSOAPBinding()
    client_binding.sslCACertDir = saml_trusted_ca_dir
    client_binding.clockSkewTolerance = 1  # 1 second tolerance

    # Make a new query object
    query = AttributeQuery()

    query.version = SAMLVersion(SAMLVersion.VERSION_20)
    query.id = str(uuid4())
    query.issueInstant = datetime.utcnow()

    query.issuer = Issuer()
    query.issuer.format = Issuer.X509_SUBJECT
    query.issuer.value = '/O=STFC/OU=SPBU/CN=test'

    query.subject = Subject()
    query.subject.nameID = NameID()
    query.subject.nameID.format = subject_id_format
    query.subject.nameID.value = subject_id

    # Specify what attributes to query for
    for attribute_request in attribute_requests:
        query.attributes.append(attribute_request)

    try:
        response = client_binding.send(query, uri=attr_service_uri)
        attribute_results = {}

        assertion = next(iter(response.assertions or []), None)
        if assertion:
            statement = next(iter(assertion.attributeStatements or []), None)
            if statement:
                for attribute in statement.attributes:
                    values = []
                    for attribute_value in attribute.attributeValues:
                        values.append(attribute_value.value)

                    attribute_results[attribute.name] = values

        return attribute_results

    except (RequestResponseError, Error) as e:
        logger.error("Error processing SOAP query for {0}: {1}".format(id, e))
    def _attributeQuery(self, thisSection):
        
        uri = self.cfgParser.get(thisSection, 'uri')
        print("Testing %s ..." % thisSection)
        print("Calling Attribute Service %r ..." % uri)
        
        binding = AttributeQuerySslSOAPBinding.fromConfig(
                                                self.__class__.CONFIG_FILEPATH, 
                                                section=thisSection,
                                                prefix='attributeQuery.')
        
        binding.subjectID = self.cfgParser.get(thisSection, 'subject')
        
        response = binding.send(uri=uri)
        
        # ESGFResponseElementTree has an extension to support ESG Group/Role 
        # Attribute Value 
        samlResponseElem = ESGFResponseElementTree.toXML(response)
        
        print("SAML Response ...")
#        print(ElementTree.tostring(samlResponseElem))
        print("Pretty print SAML Response ...")
        print(prettyPrint(samlResponseElem))
        
        self.assert_(response.status.statusCode.value==StatusCode.SUCCESS_URI)
예제 #3
0
    def __init__(self,
                 sessionCacheDataDir=None,
                 sessionCacheTimeout=None,
                 sessionCacheAssertionClockSkewTol=1.0):
        '''Initialise settings for connection to an Attribute Authority

        @param sessionCacheDataDir: directory for permanent storage of sessions.
        Sessions are used as a means of optimisation caching Attribute Query
        results to reduce the number of Attribute Authority web service calls.
        If set to None, sessions are cached in memory only.
        @type sessionCacheDataDir: None type / basestring
        @param sessionCacheTimeout: time in seconds for individual caches'
        lifetimes.  Set to None to set no expiry.
        @type sessionCacheTimeout: float/int/long/string or None type
        '''
        self.sessionCacheDataDir = sessionCacheDataDir
        self.sessionCacheTimeout = sessionCacheTimeout
        self.__sessionCacheAssertionClockSkewTol = \
            sessionCacheAssertionClockSkewTol

        self.__subjectAttributeId = None
        self.__mappingFilePath = None

        # Force mapping dict to have string type keys and items
        _typeCheckers = (lambda val: isinstance(val, basestring), ) * 2
        self.__attributeId2AttributeAuthorityMap = VettedDict(*_typeCheckers)

        self.__attribute_query = AttributeQueryFactory.create()
        self.__attribute_query_binding = AttributeQuerySslSOAPBinding()

        self.__cacheSessions = True
    def test02SendQuery(self):
        query_binding = AttributeQuerySslSOAPBinding()
        
        attribute_query = AttributeQueryFactory.create()
        attribute_query.subject.nameID.format = self.__class__.SUBJECT_FORMAT
        attribute_query.subject.nameID.value = self.__class__.SUBJECT
        attribute_query.issuerName = '/O=Site A/CN=Authorisation Service'


        attribute = Attribute()
        attribute.name = 'urn:ndg:saml:emailaddress'
        attribute.friendlyName = 'emailAddress'
        attribute.nameFormat = 'http://www.w3.org/2001/XMLSchema'
        
        attribute_query.attributes.append(attribute)
        
        query_binding.clockSkewTolerance = 2.
        query_binding.sslCACertDir = self.__class__.CLIENT_CACERT_DIR
        query_binding.sslCertFilePath = self.__class__.CLIENT_CERT_FILEPATH
        query_binding.sslPriKeyFilePath = self.__class__.CLIENT_PRIKEY_FILEPATH
        query_binding.sslValidDNs = self.__class__.VALID_DNS
        
        response = query_binding.send(attribute_query, 
                                      uri=self.__class__.SERVICE_URI)
        
        # Convert back to ElementTree instance read for string output
        samlResponseElem = ResponseElementTree.toXML(response)
        
        print("SAML Response ...")
        print(ElementTree.tostring(samlResponseElem))
        print("Pretty print SAML Response ...")
        print(prettyPrint(samlResponseElem))
        
        self.assert_(response.status.statusCode.value==StatusCode.SUCCESS_URI)
예제 #5
0
    def initialise(self, app_conf, prefix=DEFAULT_PARAM_PREFIX, **local_conf):
        """Initialise attributes from the given local configuration settings
        @param app_conf: application configuration settings - ignored - this
        method includes this arg to fit Paste middleware / app function
        signature
        @type app_conf: dict
        @param prefix: optional prefix for parameter names included in the
        local_conf dict - enables these parameters to be filtered from others
        which don't apply to this middleware
        @param local_conf: attribute settings to apply
        @type local_conf: dict
        """
        if prefix is None:
            prefix = ''
        prefixLength = len(prefix)
        queryPrefix = prefix + self.__class__.ATTRIBUTE_QUERY_PARAMS_PREFIX
        queryPrefixLength = len(queryPrefix)
        for k in local_conf:
            # SSL parameters apply to this class and to the attribute query
            # client.
            if k.startswith(queryPrefix):
                paramName = k[queryPrefixLength:]
            elif k.startswith(prefix):
                paramName = k[prefixLength:]

            if paramName in AttributeRequestMiddleware.PARAM_NAMES:
                setattr(self, paramName, local_conf[k])

        self._attributeQueryClient = AttributeQuerySslSOAPBinding()

        # Parse authorisation decision query options
        self._attributeQueryClient.parseKeywords(prefix=queryPrefix,
                                                 **local_conf)

        sslContext = ssl_context_util.make_ssl_context(self.sslPriKeyFilePath,
                                                       self.sslCertFilePath,
                                                       None,
                                                       self.sslCACertDir,
                                                       verify_peer=True)
        self._httpsClientConfig = httpsclient_utils.Configuration(
            sslContext, False)
예제 #6
0
    def dispatch(self, command):
        if command == self.__class__.ATTRIBUTE_QUERY_CMD:
            query = self.create_attribute_query()
            binding = AttributeQuerySslSOAPBinding()
            
        elif command == self.__class__.AUTHZ_DECISION_QUERY_CMD:
            query = self.create_authz_decision_query()
            binding = AuthzDecisionQuerySslSOAPBinding()

        binding.sslCACertDir = self.ca_cert_dir
        binding.ssl_no_peer_verification = self.ssl_no_peer_verification
        binding.sslCertFilePath = self.client_cert_filepath
        binding.sslPriKeyFilePath = self.client_prikey_filepath
        binding.clockSkewTolerance = self.clock_skew_tolerance

        response = binding.send(query, uri=self.service_uri)
        
        return response
예제 #7
0
class AttributeRequestMiddleware(NDGSecurityMiddlewareBase):
    """Makes an attribute request to obtain ESG attributes corresponding to the
    username stored in the environ.
    """
    DEFAULT_PARAM_PREFIX = 'attr_req.'
    ATTRIBUTE_QUERY_PARAMS_PREFIX = 'attributeQuery.'
    ATTRIBUTE_NAME_MAP = {
        'urn:esg:first:name': 'firstname',
        'urn:esg:last:name': 'lastname',
        'urn:esg:email:address': 'email'
    }
    # Key in environ for session
    SESSION_KEY_OPTION_DEFAULT = 'beaker.session.ndg.security'
    # Key in session for attribute dict
    SESSION_ATTRIBUTE_KEY_OPTION_DEFAULT = 'openid.ax'
    # Constants for parsing XDRS document
    XRDS_NS = 'xri://$xrd*($v*2.0)'
    XRDS_SERVICE_PATH = ('{%s}XRD/{%s}Service' % (XRDS_NS, XRDS_NS))
    XRDS_TYPE_PATH = ('{%s}Type' % XRDS_NS)
    XRDS_URI_PATH = ('{%s}URI' % XRDS_NS)
    XRDS_ATTRIBUTE_SERVICE_TYPE = 'urn:esg:security:attribute-service'

    PARAM_NAMES = ('attributeServiceUrl', 'sslCACertDir', 'sslCertFilePath',
                   'sslPriKeyFilePath', 'sessionAttributeKey', 'sessionKey')

    __slots__ = ('_app', '_attributeQueryClient', '_httpsClientConfig')

    __slots__ += tuple(['__' + i for i in PARAM_NAMES])
    del i

    def __init__(self, app):
        self._app = app

        self.__attributeServiceUrl = None
        self.__sessionAttributeKey = self.__class__.SESSION_ATTRIBUTE_KEY_OPTION_DEFAULT
        self.__sessionKey = self.__class__.SESSION_KEY_OPTION_DEFAULT

    def initialise(self, app_conf, prefix=DEFAULT_PARAM_PREFIX, **local_conf):
        """Initialise attributes from the given local configuration settings
        @param app_conf: application configuration settings - ignored - this
        method includes this arg to fit Paste middleware / app function
        signature
        @type app_conf: dict
        @param prefix: optional prefix for parameter names included in the
        local_conf dict - enables these parameters to be filtered from others
        which don't apply to this middleware
        @param local_conf: attribute settings to apply
        @type local_conf: dict
        """
        if prefix is None:
            prefix = ''
        prefixLength = len(prefix)
        queryPrefix = prefix + self.__class__.ATTRIBUTE_QUERY_PARAMS_PREFIX
        queryPrefixLength = len(queryPrefix)
        for k in local_conf:
            # SSL parameters apply to this class and to the attribute query
            # client.
            if k.startswith(queryPrefix):
                paramName = k[queryPrefixLength:]
            elif k.startswith(prefix):
                paramName = k[prefixLength:]

            if paramName in AttributeRequestMiddleware.PARAM_NAMES:
                setattr(self, paramName, local_conf[k])

        self._attributeQueryClient = AttributeQuerySslSOAPBinding()

        # Parse authorisation decision query options
        self._attributeQueryClient.parseKeywords(prefix=queryPrefix,
                                                 **local_conf)

        sslContext = ssl_context_util.make_ssl_context(self.sslPriKeyFilePath,
                                                       self.sslCertFilePath,
                                                       None,
                                                       self.sslCACertDir,
                                                       verify_peer=True)
        self._httpsClientConfig = httpsclient_utils.Configuration(
            sslContext, False)

    @classmethod
    def filter_app_factory(cls, app, app_conf, **kw):
        obj = cls(app)
        obj.initialise(app_conf, **kw)
        return obj

    def __call__(self, environ, start_response):
        """Checks whether the attributes are stored in the session and if not
        makes an attribute request.
        """
        # Get session.
        session = environ.get(self.sessionKey)
        if session is None:
            raise Exception(
                'AttributeRequestMiddleware.__call__: No beaker session key '
                '"%s" found in environ' % self.sessionKey)

        username = environ.get(self.__class__.USERNAME_ENVIRON_KEYNAME)
        log.debug("Found username: %s", username)
        if (username and (self.sessionAttributeKey not in session)):
            attributes = self._getAttributes(username)
            session[self.sessionAttributeKey] = attributes
            session.save()

        return self._app(environ, start_response)

    @staticmethod
    def _isHttpUrl(string):
        """Determines whether a string can be interpreted as a HTTP or HTTPS
        URL.
        @type string: basestring
        @param string: string to test
        @rtype: bool
        @return: True if string can be parsed as a URL with a scheme that is
        HTTP or HTTPS and at least a net location, otherwise False
        """
        parts = urlparse.urlsplit(string)
        return (parts.scheme in ['http', 'https']) and bool(parts.netloc)

    def _getAttributeService(self, subject):
        """
        @type subject: basestring
        @param subject: subject for which the query is to be made
        @rtype: basestring
        @return: URL of attribute service
        """
        if not self._isHttpUrl(subject):
            log.debug(
                "Subject is not a HTTP URL - not making Yadis request to"
                " obtain attribute service: %s", subject)
            return None
        try:
            log.debug(
                "Making Yadis request to obtain attribute service for"
                " subject %s", subject)
            xrdsStr = httpsclient_utils.fetch_from_url(subject,
                                                       self._httpsClientConfig)
        except Exception, exc:
            log.error(
                "Unable to determine attribute service for subject %s: %s",
                subject, exc.__str__())
            return None
        xrdsEl = ElementTree.XML(xrdsStr)
        for svcEl in xrdsEl.findall(self.__class__.XRDS_SERVICE_PATH):
            isAttrService = False
            for typeEl in svcEl.findall(self.__class__.XRDS_TYPE_PATH):
                if typeEl.text == self.__class__.XRDS_ATTRIBUTE_SERVICE_TYPE:
                    isAttrService = True
            if isAttrService:
                for uriEl in svcEl.findall(self.__class__.XRDS_URI_PATH):
                    attributeServiceUrl = uriEl.text
                    log.debug("Found attribute service URL: %s",
                              attributeServiceUrl)
                    return attributeServiceUrl
        return None
class CertExtApp(object):
    """Application to create a X.509 certificate extension containing a SAML
    assertion for inclusion by MyProxy into an issued certificate
    """
    DEFAULT_QUERY_ATTRIBUTES = ESGFDefaultQueryAttributes.ATTRIBUTES
    N_DEFAULT_QUERY_ATTRIBUTES = len(DEFAULT_QUERY_ATTRIBUTES)
    ESG_NAME_ID_FORMAT = ESGFSamlNamespaces.NAMEID_FORMAT
    
    CONNECTION_STRING_OPTNAME = 'connectionString'
    OPENID_SQLQUERY_OPTNAME = 'openIdSqlQuery'
    ATTRIBUTE_AUTHORITY_URI_OPTNAME = 'attributeAuthorityURI'
    
    CONFIG_FILE_OPTNAMES = (
        ATTRIBUTE_AUTHORITY_URI_OPTNAME,
        CONNECTION_STRING_OPTNAME,
        OPENID_SQLQUERY_OPTNAME,
    )
    ATTRIBUTE_QUERY_ATTRNAME = 'attributeQuery'
    LEN_ATTRIBUTE_QUERY_ATTRNAME = len(ATTRIBUTE_QUERY_ATTRNAME)
    __PRIVATE_ATTR_PREFIX = '__'
    __slots__ = tuple(
        [__PRIVATE_ATTR_PREFIX + i 
         for i in CONFIG_FILE_OPTNAMES + (ATTRIBUTE_QUERY_ATTRNAME,)]
    )
    del i
    
    def __init__(self):
        self.__attributeAuthorityURI = None
        self.__connectionString = None
        self.__openIdSqlQuery = None
        self.__attributeQuery = AttributeQuerySslSOAPBinding() 

    @classmethod
    def fromConfigFile(cls, configFilePath, **kw):
        '''Alternative constructor makes object from config file settings
        @type configFilePath: basestring
        @param configFilePath: configuration file path
        '''
        certExtApp = cls()
        certExtApp.readConfig(configFilePath, **kw)
        
        return certExtApp
        
    def __call__(self, username):
        """Main method - create SAML assertion by querying the user's OpenID
        identifier from the user database and using this to query the 
        Attribute Authority for attributes
        """
        self.__attributeQuery.subjectID = self.queryOpenId(username)
        response = self.__attributeQuery.send(uri=self.attributeAuthorityURI)
        
        try:
            assertionStr = self.serialiseAssertion(response.assertions[0])
            
        except (IndexError, TypeError):
            raise CertExtAppRetrieveError("Error accessing assertion from "
                                          "Attribute Authority SAML response: "
                                          "%s" % traceback.format_exc())
            
        return assertionStr

    def readConfig(self, cfg, prefix='', section='DEFAULT'):
        '''Read config file settings
        @type cfg: basestring /ConfigParser derived type
        @param cfg: configuration file path or ConfigParser type object
        @type prefix: basestring
        @param prefix: prefix for option names e.g. "certExtApp."
        @type section: baestring
        @param section: configuration file section from which to extract
        parameters.
        '''
        if isinstance(cfg, basestring):
            cfgFilePath = os.path.expandvars(cfg)
            _cfg = CaseSensitiveConfigParser()
            _cfg.read(cfgFilePath)
            
        elif isinstance(cfg, ConfigParser):
            _cfg = cfg   
        else:
            raise AttributeError('Expecting basestring or ConfigParser type '
                                 'for "cfg" attribute; got %r type' % type(cfg))
        
        prefixLen = len(prefix)
        for optName, val in _cfg.items(section):
            if prefix:
                # Filter attributes based on prefix
                if optName.startswith(prefix):
                    setattr(self, optName[prefixLen:], val)
            else:
                # No prefix set - attempt to set all attributes   
                setattr(self, optName, val)
            
    def __setattr__(self, name, value):
        """Enable setting of AttributeQuerySslSOAPBinding attributes from
        names starting with attributeQuery.* / attributeQuery_*.  Addition for
        setting these values from ini file
        """
        try:
            super(CertExtApp, self).__setattr__(name, value)
            
        except AttributeError:
            # Coerce into setting AttributeQuerySslSOAPBinding attributes - 
            # names must start with 'attributeQuery\W' e.g.
            # attributeQuery.clockSkew or attributeQuery_issuerDN
            if name.startswith(CertExtApp.ATTRIBUTE_QUERY_ATTRNAME):                
                setattr(self.__attributeQuery, 
                        name[CertExtApp.LEN_ATTRIBUTE_QUERY_ATTRNAME+1:], 
                        value)
            else:
                raise

    @property
    def attributeQuery(self):
        """SAML SOAP Attribute Query client binding object"""
        return self.__attributeQuery
    
    def _getAttributeAuthorityURI(self):
        return self.__attributeAuthorityURI

    def _setAttributeAuthorityURI(self, value):
        if not isinstance(value, basestring):
            raise TypeError('Expecting string type for "attributeAuthorityURI";'
                            ' got %r instead' % type(value))
        self.__attributeAuthorityURI = value

    attributeAuthorityURI = property(_getAttributeAuthorityURI,
                                     _setAttributeAuthorityURI, 
                                     doc="Attribute Authority SOAP SAML URI")

    def _getConnectionString(self):
        return self.__connectionString

    def _setConnectionString(self, value):
        if not isinstance(value, basestring):
            raise TypeError('Expecting string type for "%s" attribute; got %r'%
                            (CertExtApp.CONNECTION_STRING_OPTNAME,
                             type(value)))
        self.__connectionString = os.path.expandvars(value)

    connectionString = property(fget=_getConnectionString, 
                                fset=_setConnectionString, 
                                doc="Database connection string")

    def _getOpenIdSqlQuery(self):
        return self.__openIdSqlQuery

    def _setOpenIdSqlQuery(self, value):
        if not isinstance(value, basestring):
            raise TypeError('Expecting string type for "%s" attribute; got %r'% 
                        (CertExtApp.OPENID_SQLQUERY_OPTNAME,
                         type(value)))
        self.__openIdSqlQuery = value

    openIdSqlQuery = property(fget=_getOpenIdSqlQuery, 
                        fset=_setOpenIdSqlQuery, 
                        doc="SQL Query for authentication request")
        
    def __getstate__(self):
        '''Specific implementation needed with __slots__'''
        return dict([(attrName, getattr(self, attrName)) 
                     for attrName in CertExtApp.__slots__])
        
    def __setstate__(self, attrDict):
        '''Specific implementation needed with __slots__'''
        for attr, val in attrDict.items():
            setattr(self, attr, val)
    
    def serialiseAssertion(self, assertion):
        """Convert SAML assertion object into a string"""
        samlAssertionElem = AssertionElementTree.toXML(assertion)
        return ElementTree.tostring(samlAssertionElem)
    
    def queryOpenId(self, username):
        """Given a username, query for user OpenID from the user 
        database

        @type username: basestring
        @param username: username
        @rtype: basestring
        @return: the OpenID identifier corresponding to the input username
        """

        try:
            dbEngine = create_engine(self.connectionString)
        except ImportError, e:
            raise CertExtAppConfigError("Missing database engine for "
                                        "SQLAlchemy: %s" % e)
        connection = dbEngine.connect()
        
        try:
            queryInputs = dict(username=username)
            query = Template(self.openIdSqlQuery).substitute(queryInputs)
            result = connection.execute(query)

        except exc.ProgrammingError:
            raise CertExtAppSqlError("Error with SQL Syntax: %s" %
                                     traceback.format_exc())
        finally:
            connection.close()

        try:
            openId = [r for r in result][0][0]
        
        except Exception:
            raise CertExtAppRetrieveError("Error with result set: %s" %
                                          traceback.format_exc())
        
        log.debug('Query succeeded for user %r' % username)
        return openId
 def __init__(self):
     self.__attributeAuthorityURI = None
     self.__connectionString = None
     self.__openIdSqlQuery = None
     self.__attributeQuery = AttributeQuerySslSOAPBinding()