def __init__(self, base_url='', hash_seed="", keyjar=None, verify_ssl=True,
                 services=None, service_factory=None, client_configs=None,
                 client_authn_factory=None, client_cls=None,
                 state_db=None, http_lib=None, module_dirs=None, **kwargs):
        self.base_url = base_url
        self.hash_seed = as_bytes(hash_seed)
        self.verify_ssl = verify_ssl
        self.keyjar = keyjar

        if state_db:
            self.state_db = state_db
        else:
            self.state_db = InMemoryStateDataBase()

        self.session_interface = StateInterface(self.state_db)

        try:
            self.jwks_uri = add_path(base_url, kwargs['jwks_path'])
        except KeyError:
            pass

        self.extra = kwargs

        self.client_cls = client_cls or oidc.RP
        self.services = services
        self.service_factory = service_factory or factory
        self.client_authn_factory = client_authn_factory
        self.client_configs = client_configs
        self.module_dirs = module_dirs or ['oidc', 'oauth2']

        # keep track on which RP instance that serves with OP
        self.issuer2rp = {}
        self.hash2issuer = {}
        self.httplib = http_lib
    def __init__(self,
                 service_context,
                 state_db,
                 conf=None,
                 client_authn_factory=None,
                 **kwargs):
        StateInterface.__init__(self, state_db)

        if client_authn_factory is None:
            self.client_authn_factory = ca_factory
        else:
            self.client_authn_factory = client_authn_factory

        self.service_context = service_context
        self.default_request_args = {}
        if conf:
            self.conf = conf
            for param in [
                    'msg_type', 'response_cls', 'error_msg',
                    'default_authn_method', 'http_method', 'request_body_type',
                    'response_body_type'
            ]:
                if param in conf:
                    setattr(self, param, conf[param])
        else:
            self.conf = {}

        # pull in all the modifiers
        self.pre_construct = []
        self.post_construct = []
    def __init__(self,
                 base_url='',
                 hash_seed="",
                 keyjar=None,
                 verify_ssl=True,
                 services=None,
                 client_configs=None,
                 client_authn_factory=None,
                 client_cls=None,
                 state_db=None,
                 http_lib=None,
                 httpc_params=None,
                 **kwargs):

        self.base_url = base_url
        self.hash_seed = as_bytes(hash_seed)
        self.keyjar = keyjar

        if state_db:
            self.state_db = state_db
        else:
            self.state_db = InMemoryStateDataBase()

        self.session_interface = StateInterface(self.state_db)

        try:
            self.jwks_uri = add_path(base_url, kwargs['jwks_path'])
        except KeyError:
            self.jwks_uri = ""

        self.extra = kwargs

        self.client_cls = client_cls or oidc.RP
        self.services = services
        self.client_authn_factory = client_authn_factory
        self.client_configs = client_configs

        # keep track on which RP instance that serves with OP
        self.issuer2rp = {}
        self.hash2issuer = {}
        self.httplib = http_lib
        if not httpc_params:
            self.httpc_params = {'verify': verify_ssl}
        else:
            self.httpc_params = httpc_params

        if not getattr(self.keyjar, 'httpc_params', None):
            self.keyjar.httpc_params = self.httpc_params
Esempio n. 4
0
    def __init__(self,
                 state_db,
                 ca_certs=None,
                 client_authn_factory=None,
                 keyjar=None,
                 verify_ssl=True,
                 config=None,
                 client_cert=None,
                 httplib=None,
                 services=None,
                 service_factory=None,
                 jwks_uri='',
                 module_dirs=None):
        """

        :param ca_certs: Certificates used to verify HTTPS certificates
        :param client_authn_factory: Factory that this client can use to
            initiate a client authentication class.
        :param keyjar: A py:class:`oidcmsg.key_jar.KeyJar` instance
        :param verify_ssl: Whether the SSL certificate should be verified.
        :param config: Configuration information passed on to the
            :py:class:`oidcservice.service_context.ServiceContext` 
            initialization
        :param client_cert: Certificate used by the HTTP client
        :param httplib: A HTTP client to use
        :param services: A list of service definitions
        :param service_factory: A factory to use when building the
            :py:class:`oidcservice.service.Service` instances
        :param jwks_uri: A jwks_uri
        :return: Client instance
        """

        self.session_interface = StateInterface(state_db)
        self.http = httplib or HTTPLib(
            ca_certs=ca_certs, verify_ssl=verify_ssl, client_cert=client_cert)

        if not keyjar:
            keyjar = KeyJar()
            keyjar.verify_ssl = verify_ssl

        self.events = None
        self.service_context = ServiceContext(keyjar,
                                              config=config,
                                              jwks_uri=jwks_uri)
        if self.service_context.client_id:
            self.client_id = self.service_context.client_id

        _cam = client_authn_factory or ca_factory
        self.service_factory = service_factory or default_service_factory
        _srvs = services or DEFAULT_SERVICES

        if not module_dirs:
            module_dirs = ['oauth2']

        self.service = build_services(_srvs, self.service_factory, module_dirs,
                                      self.service_context, state_db, _cam)

        self.service_context.service = self.service

        self.verify_ssl = verify_ssl
Esempio n. 5
0
    def __init__(self,
                 state_db,
                 client_authn_factory=None,
                 keyjar=None,
                 verify_ssl=True,
                 config=None,
                 httplib=None,
                 services=None,
                 jwks_uri='',
                 httpc_params=None):
        """

        :param client_authn_factory: Factory that this client can use to
            initiate a client authentication class.
        :param keyjar: A py:class:`oidcmsg.key_jar.KeyJar` instance
        :param config: Configuration information passed on to the
            :py:class:`oidcservice.service_context.ServiceContext`
            initialization
        :param httplib: A HTTP client to use
        :param services: A list of service definitions
        :param jwks_uri: A jwks_uri
        :param httpc_params: HTTP request arguments
        :return: Client instance
        """

        self.session_interface = StateInterface(state_db)
        self.http = httplib or HTTPLib(httpc_params)

        if not keyjar:
            keyjar = KeyJar()
            keyjar.verify_ssl = verify_ssl

        self.events = None
        self.service_context = ServiceContext(keyjar,
                                              config=config,
                                              jwks_uri=jwks_uri,
                                              httpc_params=httpc_params)
        if self.service_context.client_id:
            self.client_id = self.service_context.client_id

        _cam = client_authn_factory or ca_factory

        _srvs = services or DEFAULT_SERVICES

        self.service = init_services(_srvs, self.service_context, state_db,
                                     _cam)

        if 'add_ons' in config:
            do_add_ons(config['add_ons'], self.service)

        self.service_context.service = self.service
        self.verify_ssl = verify_ssl
class RPHandler(object):
    def __init__(self, base_url='', hash_seed="", keyjar=None, verify_ssl=True,
                 services=None, service_factory=None, client_configs=None,
                 client_authn_factory=None, client_cls=None,
                 state_db=None, http_lib=None, module_dirs=None, **kwargs):
        self.base_url = base_url
        self.hash_seed = as_bytes(hash_seed)
        self.verify_ssl = verify_ssl
        self.keyjar = keyjar

        if state_db:
            self.state_db = state_db
        else:
            self.state_db = InMemoryStateDataBase()

        self.session_interface = StateInterface(self.state_db)

        try:
            self.jwks_uri = add_path(base_url, kwargs['jwks_path'])
        except KeyError:
            pass

        self.extra = kwargs

        self.client_cls = client_cls or oidc.RP
        self.services = services
        self.service_factory = service_factory or factory
        self.client_authn_factory = client_authn_factory
        self.client_configs = client_configs
        self.module_dirs = module_dirs or ['oidc', 'oauth2']

        # keep track on which RP instance that serves with OP
        self.issuer2rp = {}
        self.hash2issuer = {}
        self.httplib = http_lib

    def supports_webfinger(self):
        """
        WebFinger is only used when you don't know which OP/AS to talk to until
        a user gives you some information you can base a search on.

        :return: True if WebFinger is among the services supported.
        """
        _cnf = self.pick_config('')
        if 'WebFinger' in _cnf['services']:
            return True
        else:
            return False

    def state2issuer(self, state):
        """
        Given the state value find the Issuer ID of the OP/AS that state value
        was used against.
        Will raise a KeyError if the state is unknown.

        :param state: The state value
        :return: An Issuer ID
        """
        return self.session_interface.get_iss(state)

    def pick_config(self, issuer):
        """
        From the set of client configurations pick one based on the issuer ID.
        Will raise a KeyError if issuer is unknown.

        :param issuer: Issuer ID
        :return: A client configuration
        """
        return self.client_configs[issuer]

    def get_session_information(self, key):
        """
        This is the second of the methods users of this class should know about.
        It will return the complete session information as an
        :py:class:`oidcservice.state_interface.State` instance.

        :param key: The session key (state)
        :return: A State instance
        """
        return self.session_interface.get_state(key)

    def init_client(self, issuer):
        """
        Initiate a Client instance. Specifically which Client class is used
        is decided by configuration.

        :param issuer: An issuer ID
        :return: A Client instance
        """
        _cnf = self.pick_config(issuer)

        try:
            _services = _cnf['services']
        except KeyError:
            _services = self.services

        try:
            client = self.client_cls(
                state_db=self.state_db,
                client_authn_factory=self.client_authn_factory,
                verify_ssl=self.verify_ssl, services=_services,
                service_factory=self.service_factory, config=_cnf,
                httplib=self.httplib, module_dirs=self.module_dirs
            )
        except Exception as err:
            logger.error('Failed initiating client: {}'.format(err))
            message = traceback.format_exception(*sys.exc_info())
            logger.error(message)
            raise

        client.service_context.keyjar = self.keyjar.copy()
        client.service_context.base_url = self.base_url
        return client

    def do_provider_info(self, client=None, state=''):
        """
        Either get the provider info from configuration or through dynamic
        discovery.

        :param client: A Client instance
        :param state: A key by which the state of the session can be 
            retrieved 
        :return: issuer ID
        """

        if not client:
            if state:
                client = self.get_client_from_session_key(state)
            else:
                raise ValueError('Missing state/session key')

        if not client.service_context.provider_info:
            dynamic_provider_info_discovery(client)
            return client.service_context.provider_info['issuer']
        else:
            _pi = client.service_context.provider_info
            for endp in ['authorization_endpoint', 'token_endpoint',
                         'userinfo_endpoint']:
                if endp in _pi:
                    for srv in client.service.values():
                        if srv.endpoint_name == endp:
                            srv.endpoint = _pi[endp]
            try:
                return client.service_context.provider_info['issuer']
            except KeyError:
                return client.service_context.issuer

    def do_client_registration(self, client=None, iss_id='', state=''):
        """
        Prepare for and do client registration if configured to do so

        :param client: A Client instance
        :param state: A key by which the state of the session can be 
            retrieved 
        """

        if not client:
            if state:
                client = self.get_client_from_session_key(state)
            else:
                raise ValueError('Missing state/session key')

        _iss = client.service_context.issuer
        if not client.service_context.redirect_uris:
            # Create the necessary callback URLs
            # as a side effect self.hash2issuer is set
            callbacks = self.create_callbacks(_iss)

            client.service_context.redirect_uris = [
                v for k, v in callbacks.items() if not k.startswith('__')]
            client.service_context.callbacks = callbacks
        else:
            self.hash2issuer[iss_id] = _iss

        # This should only be interesting if the client supports Single Log Out
        try:
            client.service_context.post_logout_redirect_uris
        except AttributeError:
            client.service_context.post_logout_redirect_uris = [self.base_url]
        else:
            if not client.service_context.post_logout_redirect_uris:
                client.service_context.post_logout_redirect_uris = [
                    self.base_url]

        if not client.service_context.client_id:
            load_registration_response(client)

    def client_setup(self, iss_id='', user=''):
        """
        First if no issuer ID is given then the identifier for the user is
        used by the webfinger service to try to find the issuer ID.
        Once the method has an issuer ID if no client is bound to this issuer
        one is created and initiated with
        the necessary information for the client to be able to communicate
        with the OP/AS that has the provided issuer ID.

        :param iss_id: The issuer ID
        :param user: A user identifier
        :return: A :py:class:`oidcservice.oidc.Client` instance
        """

        logger.info('client_setup: iss_id={}, user={}'.format(iss_id, user))

        if not iss_id:
            if not user:
                raise ValueError('Need issuer or user')

            temporary_client = self.init_client('')
            temporary_client.do_request('webfinger', resource=user)
        else:
            temporary_client = None

        try:
            client = self.issuer2rp[iss_id]
        except KeyError:
            if temporary_client:
                client = temporary_client
            else:
                client = self.init_client(iss_id)
        else:
            return client

        issuer = self.do_provider_info(client)
        _sc = client.service_context
        try:
            _fe = _sc.federation_entity
        except AttributeError:
            _fe = None
            registration_type = 'explicit'
        else:
            registration_type = _fe.registration_type

        if registration_type == 'implicit':
            _sc.client_id = client.client_id = _fe.entity_id
            _sc.redirect_uris = _sc.behaviour['redirect_uris']
        else:
            self.do_client_registration(client, iss_id)

        self.issuer2rp[issuer] = client
        return client

    def create_callbacks(self, issuer):
        """
        To mitigate some security issues the redirect_uris should be OP/AS
        specific. This method creates a set of redirect_uris unique to the
        OP/AS.

        :param issuer: Issuer ID
        :return: A set of redirect_uris
        """
        _hash = hashlib.sha256()
        _hash.update(self.hash_seed)
        _hash.update(as_bytes(issuer))
        _hex = _hash.hexdigest()
        self.hash2issuer[_hex] = issuer
        return {
            'code': "{}/authz_cb/{}".format(self.base_url, _hex),
            'implicit': "{}/authz_im_cb/{}".format(self.base_url, _hex),
            'form_post': "{}/authz_fp_cb/{}".format(self.base_url, _hex),
            '__hex': _hex
        }

    def init_authorization(self, client=None, state='', req_args=None):
        """
        Constructs the URL that will redirect the user to the authorization
        endpoint of the OP/AS.

        :param client: A Client instance
        :param req_args: Non-default Request arguments
        :return: A dictionary with 2 keys: **url** The authorization redirect
            URL and **state** the key to the session information in the
            state data store.
        """
        if not client:
            if state:
                client = self.get_client_from_session_key(state)
            else:
                raise ValueError('Missing state/session key')

        service_context = client.service_context

        _nonce = rndstr(24)
        request_args = {
            'redirect_uri': service_context.redirect_uris[0],
            'scope': service_context.behaviour['scope'],
            'response_type': service_context.behaviour['response_types'][0],
            'nonce': _nonce
        }

        if req_args is not None:
            request_args.update(req_args)

        # Need a new state for a new authorization request
        _state = self.session_interface.create_state(service_context.issuer)
        request_args['state'] = _state
        self.session_interface.store_nonce2state(_nonce, _state)

        logger.debug('Authorization request args: {}'.format(request_args))

        _srv = client.service['authorization']
        _info = _srv.get_request_parameters(request_args=request_args)
        logger.debug('Authorization info: {}'.format(_info))
        return {'url': _info['url'], 'state': _state}

    def begin(self, issuer_id='', user_id=''):
        """
        This is the first of the 3 high level methods that most users of this
        library should confine them self to use.
        If will use client_setup to produce a Client instance ready to be used
        against the OP/AS the user wants to use.
        Once it has the client it will construct an Authorization
        request.

        :param issuer_id: Issuer ID
        :param user_id: A user identifier
        :return: A dictionary containing **url** the URL that will redirect the
            user to the OP/AS and **state** the session key which will
            allow higher level code to access session information.
        """

        # Get the client instance that has been assigned to this issuer
        client = self.client_setup(issuer_id, user_id)

        try:
            res = self.init_authorization(client)
        except Exception:
            message = traceback.format_exception(*sys.exc_info())
            logger.error(message)
            raise
        else:
            return res

    # ----------------------------------------------------------------------

    def get_client_from_session_key(self, state):
        return self.issuer2rp[self.state2issuer(state)]

    @staticmethod
    def get_response_type(client):
        """
        Return the response_type a specific client wants to use.

        :param client: A Client instance
        :return: The response_type
        """
        return client.service_context.behaviour['response_types'][0]

    @staticmethod
    def get_client_authn_method(client, endpoint):
        """
        Return the client authentication method a client wants to use a
        specific endpoint

        :param client: A Client instance
        :param endpoint: The endpoint at which the client has to authenticate
        :return: The client authentication method
        """
        if endpoint == 'token_endpoint':
            try:
                am = client.service_context.behaviour[
                    'token_endpoint_auth_method']
            except KeyError:
                return ''
            else:
                if isinstance(am, str):
                    return am
                else:  # a list
                    return am[0]

    def get_access_token(self, state, client=None):
        """
        Use the 'accesstoken' service to get an access token from the OP/AS.

        :param state: The state key (the state parameter in the
            authorization request)
        :param client: A Client instance
        :return: A :py:class:`oidcmsg.oidc.AccessTokenResponse` or
            :py:class:`oidcmsg.oauth2.AuthorizationResponse`
        """
        logger.debug('get_accesstoken')

        if client is None:
            client = self.get_client_from_session_key(state)

        authorization_response = self.session_interface.get_item(
            AuthorizationResponse, 'auth_response', state)
        authorization_request = self.session_interface.get_item(
            AuthorizationRequest, 'auth_request', state)

        req_args = {
            'code': authorization_response['code'],
            'state': state,
            'redirect_uri': authorization_request['redirect_uri'],
            'grant_type': 'authorization_code',
            'client_id': client.service_context.client_id,
            'client_secret': client.service_context.client_secret
        }
        logger.debug('request_args: {}'.format(req_args))
        try:
            tokenresp = client.do_request(
                'accesstoken', request_args=req_args,
                authn_method=self.get_client_authn_method(client,
                                                          "token_endpoint"),
                state=state
            )
        except Exception as err:
            message = traceback.format_exception(*sys.exc_info())
            logger.error(message)
            raise
        else:
            if is_error_message(tokenresp):
                raise OidcServiceError(tokenresp['error'])

        return tokenresp

    def refresh_access_token(self, state, client=None, scope=''):
        """
        Refresh an access token using a refresh_token. When asking for a new
        access token the RP can ask for another scope for the new token.

        :param client: A Client instance
        :param state: The state key (the state parameter in the
            authorization request)
        :param scope: What the returned token should be valid for.
        :return: A :py:class:`oidcmsg.oidc.AccessTokenResponse` instance
        """
        if scope:
            req_args = {'scope': scope}
        else:
            req_args = {}

        if client is None:
            client = self.get_client_from_session_key(state)

        try:
            tokenresp = client.do_request(
                'refresh_token',
                authn_method=self.get_client_authn_method(client,
                                                          "token_endpoint"),
                state=state, request_args=req_args
            )
        except Exception as err:
            message = traceback.format_exception(*sys.exc_info())
            logger.error(message)
            raise
        else:
            if is_error_message(tokenresp):
                raise OidcServiceError(tokenresp['error'])

        return tokenresp

    def get_user_info(self, state, client=None, access_token='',
                      **kwargs):
        """
        use the access token previously acquired to get some userinfo

        :param client: A Client instance
        :param state: The state value, this is the key into the session
            data store
        :param access_token: An access token
        :param kwargs: Extra keyword arguments
        :return: A :py:class:`oidcmsg.oidc.OpenIDSchema` instance
        """
        if not access_token:
            _arg = self.session_interface.multiple_extend_request_args(
                {}, state, ['access_token'],
                ['auth_response', 'token_response', 'refresh_token_response'])

        request_args = {'access_token': access_token}

        if client is None:
            client = self.get_client_from_session_key(state)

        resp = client.do_request('userinfo', state=state,
                                 request_args=request_args, **kwargs)
        if is_error_message(resp):
            raise OidcServiceError(resp['error'])

        return resp

    @staticmethod
    def userinfo_in_id_token(id_token):
        """
        Given an verified ID token return all the claims that may been user
        information.

        :param id_token: An :py:class:`oidcmsg.oidc.IDToken` instance
        :return: A dictionary with user information
        """
        res = dict([(k, id_token[k]) for k in OpenIDSchema.c_param.keys() if
                    k in id_token])
        res.update(id_token.extra())
        return res

    def finalize_auth(self, client, issuer, response):
        """
        Given the response returned to the redirect_uri, parse and verify it.

        :param client: A Client instance
        :param issuer: An Issuer ID
        :param response: The authorization response as a dictionary
        :return: An :py:class:`oidcmsg.oidc.AuthorizationResponse` or
            :py:class:`oidcmsg.oauth2.AuthorizationResponse` instance.
        """
        _srv = client.service['authorization']
        try:
            authorization_response = _srv.parse_response(response,
                                                         sformat='dict')
        except Exception as err:
            logger.error('Parsing authorization_response: {}'.format(err))
            message = traceback.format_exception(*sys.exc_info())
            logger.error(message)
            raise
        else:
            logger.debug(
                'Authz response: {}'.format(authorization_response.to_dict()))

        if is_error_message(authorization_response):
            return authorization_response

        try:
            _iss = self.session_interface.get_iss(
                authorization_response['state'])
        except KeyError:
            raise KeyError('Unknown state value')

        if _iss != issuer:
            logger.error('Issuer problem: {} != {}'.format(_iss, issuer))
            # got it from the wrong bloke
            raise ValueError('Impersonator {}'.format(issuer))

        _srv.update_service_context(authorization_response,
                                    state=authorization_response['state'])
        return authorization_response

    def get_access_and_id_token(self, authorization_response=None, state='',
                                client=None):
        """
        There are a number of services where access tokens and ID tokens can
        occur in the response. This method goes through the possible places
        based on the response_type the client uses.

        :param authorization_response: The Authorization response
        :param state: The state key (the state parameter in the
            authorization request)
        :return: A dictionary with 2 keys: **access_token** with the access
            token as value and **id_token** with a verified ID Token if one
            was returned otherwise None.
        """
        if authorization_response is None:
            if state:
                authorization_response = self.session_interface.get_item(
                    AuthorizationResponse, 'auth_response', state)
            else:
                raise ValueError(
                    'One of authorization_response or state must be provided')

        if not state:
            state = authorization_response['state']

        authreq = self.session_interface.get_item(
            AuthorizationRequest, 'auth_request', state)
        _resp_type = set(authreq['response_type'])

        access_token = None
        id_token = None
        if _resp_type in [{'id_token'}, {'id_token', 'token'},
                          {'code', 'id_token', 'token'}]:
            id_token = authorization_response['__verified_id_token']

        if _resp_type in [{'token'}, {'id_token', 'token'}, {'code', 'token'},
                          {'code', 'id_token', 'token'}]:
            access_token = authorization_response["access_token"]
        elif _resp_type in [{'code'}, {'code', 'id_token'}]:

            if client is None:
                client = self.get_client_from_session_key(state)

            # get the access token
            token_resp = self.get_access_token(state, client=client)
            if is_error_message(token_resp):
                return False, "Invalid response %s." % token_resp["error"]

            access_token = token_resp["access_token"]

            try:
                id_token = token_resp['__verified_id_token']
            except KeyError:
                pass

        return {'access_token': access_token, 'id_token': id_token}

    # noinspection PyUnusedLocal
    def finalize(self, issuer, response):
        """
        The third of the high level methods that a user of this Class should
        know about.
        Once the consumer has redirected the user back to the
        callback URL there might be a number of services that the client should
        use. Which one those are are defined by the client configuration.

        :param issuer: Who sent the response
        :param response: The Authorization response as a dictionary
        :returns: A dictionary with two claims:
            **state** The key under which the session information is
            stored in the data store and
            **error** and encountered error or
            **userinfo** The collected user information
        """

        client = self.issuer2rp[issuer]

        authorization_response = self.finalize_auth(client, issuer, response)
        if is_error_message(authorization_response):
            return {
                'state': authorization_response['state'],
                'error': authorization_response['error']
            }

        _state = authorization_response['state']
        token = self.get_access_and_id_token(authorization_response,
                                             state=_state, client=client)

        if 'userinfo' in client.service and token['access_token']:
            inforesp = self.get_user_info(
                state=authorization_response['state'], client=client,
                access_token=token['access_token'])

            if isinstance(inforesp, ResponseMessage) and 'error' in inforesp:
                return {
                    'error': "Invalid response %s." % inforesp["error"],
                    'state': _state
                }

        elif token['id_token']:  # look for it in the ID Token
            inforesp = self.userinfo_in_id_token(token['id_token'])
        else:
            inforesp = {}

        logger.debug("UserInfo: %s", inforesp)

        try:
            _sid_support = client.service_context.provider_info[
                'backchannel_logout_session_supported']
        except KeyError:
            try:
                _sid_support = client.service_context.provider_info[
                    'frontchannel_logout_session_supported']
            except:
                _sid_support = False

        if _sid_support:
            try:
                sid = token['id_token']['sid']
            except KeyError:
                pass
            else:
                client.session_interface.store_sid2state(sid, _state)

        client.session_interface.store_sub2state(token['id_token']['sub'],
                                                 _state)

        return {
            'userinfo': inforesp,
            'state': authorization_response['state'],
            'token': token['access_token'],
            'id_token': token['id_token']
        }

    def has_active_authentication(self, state):
        """
        Find out if the user has an active authentication

        :param state:
        :return: True/False
        """

        # Look for Id Token in all the places where it can be
        _arg = self.session_interface.multiple_extend_request_args(
            {}, state, ['__verified_id_token'],
            ['auth_response', 'token_response', 'refresh_token_response'])

        if _arg:
            _now = time_sans_frac()
            exp = _arg['__verified_id_token']['exp']
            return _now < exp
        else:
            return False

    def get_valid_access_token(self, state):
        """
        Find me a valid access token

        :param state:
        :return: An access token if a valid one exists and when it
            expires. Otherwise raise exception.
        """

        exp = 0
        token = None
        indefinite = []
        now = time_sans_frac()

        for cls, typ in [(AccessTokenResponse, 'refresh_token_response'),
                         (AccessTokenResponse, 'token_response'),
                         (AuthorizationResponse, 'auth_response')]:
            try:
                response = self.session_interface.get_item(cls, typ, state)
            except KeyError:
                pass
            else:
                try:
                    access_token = response['access_token']
                except:
                    continue
                else:
                    try:
                        _exp = response['__expires_at']
                    except KeyError:  # No expiry date, lives for ever
                        indefinite.append((access_token, 0))
                    else:
                        if _exp > now:  # expires sometime in the future
                            if _exp > exp:
                                exp = _exp
                                token = (access_token, _exp)

        if indefinite:
            return indefinite[0]
        else:
            if token:
                return token
            else:
                raise OidcServiceError('No valid access token')

    def logout(self, state, client=None, post_logout_redirect_uri=''):
        """
        Does a RP initiated logout from an OP. After logout the user will be
        redirect by the OP to a URL of choice (post_logout_redirect_uri).

        :param state: Key to an active session
        :param client: Which client to use
        :param post_logout_redirect_uri: If a special post_logout_redirect_uri
            should be used
        :return: A US
        """
        if client is None:
            client = self.get_client_from_session_key(state)

        try:
            srv = client.service['end_session']
        except KeyError:
            raise OidcServiceError("Does not know how to logout")

        if post_logout_redirect_uri:
            request_args = {
                "post_logout_redirect_uri": post_logout_redirect_uri
            }
        else:
            request_args = {}

        resp = srv.get_request_parameters(state=state,
                                          request_args=request_args)

        return resp

    def clear_session(self, state):
        client = self.get_client_from_session_key(state)
        client.session_interface.remove_state(state)
    def __init__(self,
                 client_authn_factory=None,
                 keyjar=None,
                 verify_ssl=True,
                 config=None,
                 httplib=None,
                 services=None,
                 jwks_uri='',
                 httpc_params=None):
        """

        :param client_authn_factory: Factory that this client can use to
            initiate a client authentication class.
        :param keyjar: A py:class:`oidcmsg.key_jar.KeyJar` instance
        :param config: Configuration information passed on to the
            :py:class:`oidcservice.service_context.ServiceContext`
            initialization
        :param httplib: A HTTP client to use
        :param services: A list of service definitions
        :param jwks_uri: A jwks_uri
        :param httpc_params: HTTP request arguments
        :return: Client instance
        """

        if httpc_params is None:
            httpc_params = {"verify": True}

        self.http = httplib or HTTPLib(httpc_params)

        # db_conf = config.get('db_conf')
        # if db_conf:
        #     _storage_cls_name = db_conf.get('abstract_storage_cls')
        #     self._storage_cls = importer(_storage_cls_name)
        #     self.db = self._storage_cls(db_conf.get('default'))
        #     if not keyjar:
        #         key_db_conf = db_conf.get('keyjar', db_conf.get('default'))
        #         keyjar = KeyJar(abstract_storage_cls=self._storage_cls, storage_conf=key_db_conf)
        #         keyjar.verify_ssl = verify_ssl

        self.events = None
        self.service_context = ServiceContext(keyjar,
                                              config=config,
                                              jwks_uri=jwks_uri,
                                              httpc_params=httpc_params)

        self.session_interface = StateInterface(self.service_context.state_db)

        if self.service_context.get('client_id'):
            self.client_id = self.service_context.get('client_id')

        _cam = client_authn_factory or ca_factory

        _srvs = services or DEFAULT_SERVICES

        self.service = init_services(_srvs, self.service_context, _cam)

        if 'add_ons' in config:
            do_add_ons(config['add_ons'], self.service)

        self.service_context.service = self.service
        # just ignore verify_ssl until it goes away
        self.verify_ssl = httpc_params.get("verify", True)