Ejemplo n.º 1
0
    def acb(self, op_hash='', **kwargs):
        logger.debug('Callback kwargs: {}'.format(kwargs))

        rp = self.get_rp(op_hash)

        try:
            session_info = self.rph.session_interface.get_state(
                kwargs['state'])
        except KeyError:
            raise cherrypy.HTTPError(400, 'Unknown state')

        logger.debug('Session info: {}'.format(session_info))
        # rp.service_context.provider_info['issuer'] != state_info['iss']:
        #   raise cherrypy.HTTPError(400, 'Wrong Issuer')
        res = self.rph.finalize(session_info['iss'], kwargs)

        if is_error_message(res):
            raise cherrypy.HTTPError(400, res['error'])
        else:
            fname = os.path.join(self.html_home, 'opresult.html')
            _pre_html = open(fname, 'r').read()
            _html = _pre_html.format(result=create_result_page(
                userinfo=res['userinfo'], access_token=res['token'],
                client=rp))
            return as_bytes(_html)
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    def service_endpoint(self, name, **kwargs):
        logger.info(kwargs)
        logger.info('At the {} endpoint'.format(name))

        endpoint = self.endpoint_context.endpoint[name]

        try:
            authn = cherrypy.request.headers['Authorization']
        except KeyError:
            pr_args = {}
        else:
            pr_args = {'auth': authn}

        if endpoint.request_placement == 'body':
            if cherrypy.request.process_request_body is True:
                _request = cherrypy.request.body.read()
            else:
                raise cherrypy.HTTPError(400, 'Missing HTTP body')
            if not _request:
                _request = kwargs

            req_args = endpoint.parse_request(_request, **pr_args)
        else:
            req_args = endpoint.parse_request(kwargs, **pr_args)
        logger.info('request: {}'.format(req_args))

        if is_error_message(req_args):
            return as_bytes(req_args.to_json())

        args = endpoint.process_request(req_args)
        return self.do_response(endpoint, req_args, **args)
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
    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 client is None:
            client = self.get_client_from_session_key(state)

        if authorization_response is None:
            if state:
                authorization_response = client.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 = client.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'}]:

            # 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}
Ejemplo n.º 6
0
    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)

        return {
            'userinfo': inforesp,
            'state': authorization_response['state'],
            'token': token['access_token']
        }
Ejemplo n.º 7
0
    def test_error_message(self):
        err = ResponseMessage(error="invalid_request",
                              error_description="Something was missing",
                              error_uri="http://example.com/error_message.html")

        ue_str = err.to_urlencoded()
        del err["error_uri"]
        ueo_str = err.to_urlencoded()

        assert ue_str != ueo_str
        assert "error_message" not in ueo_str
        assert "error_message" in ue_str
        assert is_error_message(err)
Ejemplo n.º 8
0
    def finalize_auth(self,
                      client,
                      issuer: str,
                      response: dict,
                      behaviour_args: Optional[dict] = None):
        """
        Given the response returned to the redirect_uri, parse and verify it.

        :param behaviour_args: For fine tuning behaviour
        :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.
        """

        logger.debug(20 * "*" + " finalize_auth " + 20 * "*")

        _srv = client.get_service('authorization')
        try:
            authorization_response = _srv.parse_response(
                response, sformat='dict', behaviour_args=behaviour_args)
        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

        _context = client.client_get("service_context")
        try:
            _iss = _context.state.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,
                                    key=authorization_response['state'])
        _context.state.store_item(authorization_response, "auth_response",
                                  authorization_response['state'])
        return authorization_response
Ejemplo n.º 9
0
    def get_tokens(self, state, client: Optional[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(20 * "*" + " get_tokens " + 20 * "*")

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

        _context = client.client_get("service_context")
        authorization_response = _context.state.get_item(
            AuthorizationResponse, 'auth_response', state)
        authorization_request = _context.state.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.get_client_id(),
            'client_secret': _context.get('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
Ejemplo n.º 10
0
    def repost_fragment(self, **kwargs):
        logger.debug('repost_fragment kwargs: {}'.format(kwargs))
        args = compact(parse_qs(kwargs['url_fragment']))
        op_hash = kwargs['op_hash']

        rp = self.get_rp(op_hash)

        x = rp.service_context.state_db[args['state']]
        logger.debug('State info: {}'.format(x))
        res = self.rph.finalize(x['as'], args)

        if is_error_message(res):
            raise cherrypy.HTTPError(400, res['error'])
        else:
            fname = os.path.join(self.html_home, 'opresult.html')
            _pre_html = open(fname, 'r').read()
            _html = _pre_html.format(result=create_result_page(userinfo=res[
                'userinfo'], access_token=res['token'], client=rp))
            return as_bytes(_html)
Ejemplo n.º 11
0
    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'])
        self.session_interface.store_item(authorization_response,
                                          "auth_response",
                                          authorization_response['state'])
        return authorization_response
Ejemplo n.º 12
0
def dynamic_provider_info_discovery(client):
    """
    This is about performing dynamic Provider Info discovery

    :param client: A :py:class:`oidcservice.oidc.Client` instance
    """
    try:
        client.service['provider_info']
    except KeyError:
        raise ConfigurationError('Can not do dynamic provider info discovery')
    else:
        try:
            client.service_context.set(
                'issuer', client.service_context.config['srv_discovery_url'])
        except KeyError:
            pass

        response = client.do_request('provider_info')
        if is_error_message(response):
            raise OidcServiceError(response['error'])
Ejemplo n.º 13
0
def dynamic_provider_info_discovery(client: Client,
                                    behaviour_args: Optional[dict] = None):
    """
    This is about performing dynamic Provider Info discovery

    :param behaviour_args:
    :param client: A :py:class:`oidcrp.oidc.Client` instance
    """
    try:
        client.get_service('provider_info')
    except KeyError:
        raise ConfigurationError('Can not do dynamic provider info discovery')
    else:
        _context = client.client_get("service_context")
        try:
            _context.set('issuer', _context.config['srv_discovery_url'])
        except KeyError:
            pass

        response = client.do_request('provider_info',
                                     behaviour_args=behaviour_args)
        if is_error_message(response):
            raise OidcServiceError(response['error'])
Ejemplo n.º 14
0
    def finalize(self,
                 issuer,
                 response,
                 behaviour_args: Optional[dict] = None):
        """
        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 behaviour_args: For fine tuning
        :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]

        if behaviour_args:
            logger.debug(f"Finalize behaviour args: {behaviour_args}")

        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,
                                             behaviour_args=behaviour_args)
        _id_token = token.get("id_token")
        logger.debug(f"ID Token: {_id_token}")

        if client.client_get("service", "userinfo") 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 _id_token:  # look for it in the ID Token
            inforesp = self.userinfo_in_id_token(_id_token)
        else:
            inforesp = {}

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

        _context = client.client_get("service_context")
        try:
            _sid_support = _context.get(
                'provider_info')['backchannel_logout_session_supported']
        except KeyError:
            try:
                _sid_support = _context.get(
                    'provider_info')['frontchannel_logout_session_supported']
            except:
                _sid_support = False

        if _sid_support and _id_token:
            try:
                sid = _id_token['sid']
            except KeyError:
                pass
            else:
                _context.state.store_sid2state(sid, _state)

        if _id_token:
            _context.state.store_sub2state(_id_token['sub'], _state)
        else:
            _context.state.store_sub2state(inforesp['sub'], _state)

        return {
            'userinfo': inforesp,
            'state': authorization_response['state'],
            'token': token['access_token'],
            'id_token': _id_token,
            'session_state': authorization_response.get('session_state', '')
        }
 def test_auth_error_message(self):
     resp = AuthorizationResponse(
         error="invalid_request", error_description="Something was missing"
     )
     assert is_error_message(resp)
Ejemplo n.º 16
0
    def parse_response(self, info, sformat="", state="", **kwargs):
        """
        This the start of a pipeline that will:

            1 Deserializes a response into it's response message class.
              Or :py:class:`oidcmsg.oauth2.ErrorResponse` if it's an error
              message
            2 verifies the correctness of the response by running the
              verify method belonging to the message class used.
            3 runs the do_post_parse_response method iff the response was not
              an error response.

        :param info: The response, can be either in a JSON or an urlencoded
            format
        :param sformat: Which serialization that was used
        :param state: The state
        :param kwargs: Extra key word arguments
        :return: The parsed and to some extend verified response
        """

        if not sformat:
            sformat = self.response_body_type

        LOGGER.debug('response format: %s', sformat)

        if sformat in ['jose', 'jws', 'jwe']:
            resp = self.post_parse_response(info, state=state)

            if not resp:
                LOGGER.error('Missing or faulty response')
                raise ResponseError("Missing or faulty response")

            return resp

        # If format is urlencoded 'info' may be a URL
        # in which case I have to get at the query/fragment part
        if sformat == "urlencoded":
            info = self.get_urlinfo(info)

        if sformat == 'jwt':
            info = self._do_jwt(info)
            sformat = "dict"

        LOGGER.debug('response_cls: %s', self.response_cls.__name__)

        resp = self._do_response(info, sformat, **kwargs)

        LOGGER.debug('Initial response parsing => "%s"', resp.to_dict())

        # is this an error message
        if is_error_message(resp):
            LOGGER.debug('Error response: %s', resp)
        else:
            vargs = self.gather_verify_arguments()
            LOGGER.debug("Verify response with %s", vargs)
            try:
                # verify the message. If something is wrong an exception is
                # thrown
                resp.verify(**vargs)
            except Exception as err:
                LOGGER.error(
                    'Got exception while verifying response: %s', err)
                raise

            resp = self.post_parse_response(resp, state=state)

        if not resp:
            LOGGER.error('Missing or faulty response')
            raise ResponseError("Missing or faulty response")

        return resp
Ejemplo n.º 17
0
    def get_access_and_id_token(self,
                                authorization_response=None,
                                state: Optional[str] = '',
                                client: Optional[object] = None,
                                behaviour_args: Optional[dict] = 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 behaviour_args: For fine tuning behaviour
        :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.
        """

        logger.debug(20 * "*" + " get_access_and_id_token " + 20 * "*")

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

        _context = client.client_get("service_context")

        if authorization_response is None:
            if state:
                authorization_response = _context.state.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 = _context.state.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"]
            if behaviour_args:
                if behaviour_args.get("collect_tokens", False):
                    # get what you can from the token endpoint
                    token_resp = self.get_tokens(state, client=client)
                    if is_error_message(token_resp):
                        return False, "Invalid response %s." % token_resp[
                            "error"]
                    # Now which access_token should I use
                    access_token = token_resp["access_token"]
                    # May or may not get an ID Token
                    id_token = token_resp.get('__verified_id_token')

        elif _resp_type in [{'code'}, {'code', 'id_token'}]:
            # get the access token
            token_resp = self.get_tokens(state, client=client)
            if is_error_message(token_resp):
                return False, "Invalid response %s." % token_resp["error"]

            access_token = token_resp["access_token"]
            # May or may not get an ID Token
            id_token = token_resp.get('__verified_id_token')

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