Exemplo n.º 1
0
def test_pkce_token():
    kb = KeyBundle(JWKS["keys"])
    kj = KeyJar()
    kj.issuer_keys[""] = [kb]
    constructor = JWTToken(
        "A",
        keyjar=kj,
        lt_pattern={"": 900},
        iss="https://example.com/as",
        sign_alg="RS256",
        encrypt=True,
    )

    sid = rndstr(32)
    session_info = {
        "sub": "subject_id",
        "client_id": "https://example.com/rp",
        "response_type": ["code"],
        "authzreq": "{}",
    }

    _cli = Client(config={"code_challenge": {"method": "S512", "length": 96}})
    args, cv = _cli.add_code_challenge()

    access_grant = constructor(
        sid,
        sinfo=session_info,
        kid="sign1",
        code_challenge=args["code_challenge"],
        code_challenge_method=args["code_challenge_method"],
    )

    _info = constructor.get_info(access_grant)
    assert _info["code_challenge_method"] == args["code_challenge_method"]
    assert _info["code_challenge"] == args["code_challenge"]
Exemplo n.º 2
0
def test_pkce_token():
    kb = KeyBundle(JWKS["keys"])
    kj = KeyJar()
    kj.issuer_keys[''] = [kb]
    constructor = JWTToken('A', keyjar=kj, lt_pattern={'': 900},
                           iss='https://example.com/as', sign_alg='RS256',
                           encrypt=True)

    sid = rndstr(32)
    session_info = {
        'sub': 'subject_id',
        'client_id': 'https://example.com/rp',
        'response_type': ['code'],
        'authzreq': '{}'
    }

    _cli = Client(config={'code_challenge': {'method': 'S512', 'length': 96}})
    args, cv = _cli.add_code_challenge()

    access_grant = constructor(
        sid, sinfo=session_info, kid='sign1',
        code_challenge=args['code_challenge'],
        code_challenge_method=args['code_challenge_method'])

    _info = constructor.get_info(access_grant)
    assert _info['code_challenge_method'] == args['code_challenge_method']
    assert _info['code_challenge'] == args['code_challenge']
Exemplo n.º 3
0
    def test_authenticated(self):
        client = Client(**CLIENT_CONFIG)
        client.authorization_endpoint = 'https://example.com/as'

        sid = rndstr(8)
        args = {
            'redirect_uri': "http://localhost:8087/authz",
            "state": sid,
            "response_type": 'code'
        }

        url, body, ht_args, csi = client.request_info(AuthorizationRequest,
                                                      'GET',
                                                      request_args=args)

        resp = self.provider.authorization_endpoint(urlparse(url).query)
        assert resp.status_code == 303
        resp = urlparse(resp.message).query
        aresp = client.parse_authz_response(resp)

        assert isinstance(aresp, AuthorizationResponse)
        assert _eq(aresp.keys(), ['state', 'code', 'client_id', 'iss'])
        assert _eq(client.grant[sid].keys(), [
            'tokens', 'code', 'exp_in', 'seed', 'id_token',
            'grant_expiration_time'
        ])
Exemplo n.º 4
0
    def test_authenticated(self):
        client = Client(**CLIENT_CONFIG)
        client.authorization_endpoint = "https://example.com/as"

        sid = rndstr(8)
        args = {
            "redirect_uri": "http://localhost:8087/authz",
            "state": sid,
            "response_type": "code",
        }

        url, body, ht_args, csi = client.request_info(
            AuthorizationRequest, "GET", request_args=args
        )

        resp = self.provider.authorization_endpoint(urlparse(url).query)
        assert resp.status_code == 303
        resp = urlparse(resp.message).query
        aresp = client.parse_authz_response(resp)

        assert isinstance(aresp, AuthorizationResponse)
        assert _eq(aresp.keys(), ["state", "code", "client_id", "iss"])
        assert _eq(
            client.grant[sid].keys(),
            ["tokens", "code", "exp_in", "seed", "id_token", "grant_expiration_time"],
        )
Exemplo n.º 5
0
def test_pkce_token():
    kb = KeyBundle(JWKS["keys"])
    kj = KeyJar()
    kj.issuer_keys[''] = [kb]
    constructor = JWTToken('A',
                           keyjar=kj,
                           lt_pattern={'': 900},
                           iss='https://example.com/as',
                           sign_alg='RS256',
                           encrypt=True)

    sid = rndstr(32)
    session_info = {
        'sub': 'subject_id',
        'client_id': 'https://example.com/rp',
        'response_type': ['code'],
        'authzreq': '{}'
    }

    _cli = Client(config={'code_challenge': {'method': 'S512', 'length': 96}})
    args, cv = _cli.add_code_challenge()

    access_grant = constructor(
        sid,
        sinfo=session_info,
        kid='sign1',
        code_challenge=args['code_challenge'],
        code_challenge_method=args['code_challenge_method'])

    _info = constructor.get_info(access_grant)
    assert _info['code_challenge_method'] == args['code_challenge_method']
    assert _info['code_challenge'] == args['code_challenge']
Exemplo n.º 6
0
def test_pkce_verify_512():
    _cli = Client(config={'code_challenge': {'method': 'S512', 'length': 96}})
    args, cv = _cli.add_code_challenge()

    authn_broker = AuthnBroker()
    authn_broker.add("UNDEFINED", DummyAuthn(None, "username"))
    _prov = Provider("as",
                     sdb.SessionDB(SERVER_INFO["issuer"]), CDB,
                     authn_broker, Implicit(), verify_client)

    assert _prov.verify_code_challenge(cv, args['code_challenge'],'S512') is True
Exemplo n.º 7
0
def test_pkce_verify_512():
    _cli = Client(config={'code_challenge': {'method': 'S512', 'length': 96}})
    args, cv = _cli.add_code_challenge()

    authn_broker = AuthnBroker()
    authn_broker.add("UNDEFINED", DummyAuthn(None, "username"))
    _prov = Provider("as", sdb.SessionDB(SERVER_INFO["issuer"]), CDB,
                     authn_broker, Implicit(), verify_client)

    assert _prov.verify_code_challenge(cv, args['code_challenge'],
                                       'S512') is True
Exemplo n.º 8
0
def test_pkce_verify_512(session_db_factory):
    _cli = Client(config={'code_challenge': {'method': 'S512', 'length': 96}})
    args, cv = _cli.add_code_challenge()

    authn_broker = AuthnBroker()
    authn_broker.add("UNDEFINED", DummyAuthn(None, "username"))
    _prov = Provider("as",
                     session_db_factory(SERVER_INFO["issuer"]), CDB,
                     authn_broker, Implicit(), verify_client)

    assert _prov.verify_code_challenge(cv, args['code_challenge'], 'S512') is True
    resp = _prov.verify_code_challenge('XXX', args['code_challenge'])
    assert isinstance(resp, Response)
    assert resp.info()['status_code'] == 401
Exemplo n.º 9
0
def test_pkce_verify_512(session_db_factory):
    _cli = Client(config={'code_challenge': {'method': 'S512', 'length': 96}})
    args, cv = _cli.add_code_challenge()

    authn_broker = AuthnBroker()
    authn_broker.add("UNDEFINED", DummyAuthn(None, "username"))
    _prov = Provider("as", session_db_factory(SERVER_INFO["issuer"]), CDB,
                     authn_broker, Implicit(), verify_client)

    assert _prov.verify_code_challenge(cv, args['code_challenge'],
                                       'S512') is True
    resp = _prov.verify_code_challenge('XXX', args['code_challenge'])
    assert isinstance(resp, Response)
    assert resp.info()['status_code'] == 401
Exemplo n.º 10
0
    def test_authenticated_token(self):
        client = Client(**CLIENT_CONFIG)
        client.authorization_endpoint = "https://example.com/as"

        sid = rndstr(8)
        args = {"redirect_uri": "http://localhost:8087/authz", "state": sid, "response_type": "token"}

        url, body, ht_args, csi = client.request_info(AuthorizationRequest, "GET", request_args=args)

        QUERY_STRING = url.split("?")[1]
        resp = self.provider.authorization_endpoint(QUERY_STRING)
        auth_resp = parse_qs(urlparse(resp.message).fragment)

        assert "access_token" in auth_resp
        assert auth_resp["token_type"][0] == "Bearer"
Exemplo n.º 11
0
    def __init__(self, provider_configuration, redirect_uri):
        """
        Args:
            provider_configuration (flask_pyoidc.provider_configuration.ProviderConfiguration)
        """
        self._provider_configuration = provider_configuration
        self._client = Client(client_authn_method=CLIENT_AUTHN_METHOD,
                              settings=provider_configuration.client_settings)
        # Token Introspection is implemented under extension sub-package of
        # the client in pyoidc.
        self._client_extension = ClientExtension(
            client_authn_method=CLIENT_AUTHN_METHOD,
            settings=provider_configuration.client_settings)
        # Client Credentials Flow is implemented under oauth2 sub-package of
        # the client in pyoidc.
        self._oauth2_client = Oauth2Client(
            client_authn_method=CLIENT_AUTHN_METHOD,
            message_factory=CCMessageFactory,
            settings=self._provider_configuration.client_settings)

        provider_metadata = provider_configuration.ensure_provider_metadata(
            self._client)
        self._client.handle_provider_config(
            ProviderConfigurationResponse(**provider_metadata.to_dict()),
            provider_metadata['issuer'])

        if self._provider_configuration.registered_client_metadata:
            client_metadata = self._provider_configuration.registered_client_metadata.to_dict(
            )
            client_metadata.update(redirect_uris=list(redirect_uri))
            self._store_registration_info(client_metadata)

        self._redirect_uri = redirect_uri
Exemplo n.º 12
0
    def test_authenticated(self):
        client = Client(**CLIENT_CONFIG)
        client.authorization_endpoint = "https://example.com/as"

        sid = rndstr(8)
        args = {"redirect_uri": "http://localhost:8087/authz", "state": sid, "response_type": "code"}

        url, body, ht_args, csi = client.request_info(AuthorizationRequest, "GET", request_args=args)

        resp = self.provider.authorization_endpoint(urlparse(url).query)
        assert resp.status == "302 Found"
        resp = urlparse(resp.message).query
        aresp = client.parse_authz_response(resp)

        assert isinstance(aresp, AuthorizationResponse)
        assert _eq(aresp.keys(), ["state", "code", "client_id", "iss"])
        assert _eq(client.grant[sid].keys(), ["tokens", "code", "exp_in", "seed", "id_token", "grant_expiration_time"])
Exemplo n.º 13
0
    def test_authenticated_token(self):
        client = Client(**CLIENT_CONFIG)
        client.authorization_endpoint = 'https://example.com/as'

        sid = rndstr(8)
        args = {'redirect_uri': "http://localhost:8087/authz", "state": sid,
                "response_type": 'token'}

        url, body, ht_args, csi = client.request_info(AuthorizationRequest,
                                                      'GET', request_args=args)

        QUERY_STRING = url.split("?")[1]
        resp = self.provider.authorization_endpoint(QUERY_STRING)
        auth_resp = parse_qs(urlparse(resp.message).fragment)

        assert "access_token" in auth_resp
        assert auth_resp["token_type"][0] == "Bearer"
Exemplo n.º 14
0
def test_pkce_verify_512(session_db_factory):
    _cli = Client(config={"code_challenge": {"method": "S512", "length": 96}})
    args, cv = _cli.add_code_challenge()

    authn_broker = AuthnBroker()
    authn_broker.add("UNDEFINED", DummyAuthn(None, "username"))
    _prov = Provider(
        "as",
        session_db_factory(SERVER_INFO["issuer"]),
        CDB,
        authn_broker,
        Implicit(),
        verify_client,
    )

    assert _prov.verify_code_challenge(cv, args["code_challenge"],
                                       "S512") is True
    resp = _prov.verify_code_challenge("XXX", args["code_challenge"])
    assert isinstance(resp, Response)
    assert resp.info()["status_code"] == 401
Exemplo n.º 15
0
    def test_authenticated(self):
        client = Client(**CLIENT_CONFIG)
        client.authorization_endpoint = 'https://example.com/as'

        sid = rndstr(8)
        args = {
            'redirect_uri': "http://localhost:8087/authz",
            "state": sid, "response_type": 'code'}

        url, body, ht_args, csi = client.request_info(
            AuthorizationRequest, 'GET', request_args=args)

        resp = self.provider.authorization_endpoint(urlparse(url).query)
        assert resp.status == "303 See Other"
        resp = urlparse(resp.message).query
        aresp = client.parse_authz_response(resp)

        assert isinstance(aresp, AuthorizationResponse)
        assert _eq(aresp.keys(), ['state', 'code', 'client_id', 'iss'])
        assert _eq(client.grant[sid].keys(), ['tokens', 'code', 'exp_in',
                                              'seed', 'id_token',
                                              'grant_expiration_time'])
Exemplo n.º 16
0
def test_pkce_create():
    _cli = Client(config={'code_challenge': {'method': 'S256', 'length': 64}})
    args, cv = _cli.add_code_challenge()
    assert args['code_challenge_method'] == 'S256'
    assert _eq(list(args.keys()), ['code_challenge_method', 'code_challenge'])
Exemplo n.º 17
0
class PyoidcFacade:
    """
    Wrapper around pyoidc library, coupled with config for a simplified API for flask-pyoidc.
    """
    def __init__(self, provider_configuration, redirect_uri):
        """
        Args:
            provider_configuration (flask_pyoidc.provider_configuration.ProviderConfiguration)
        """
        self._provider_configuration = provider_configuration
        self._client = Client(client_authn_method=CLIENT_AUTHN_METHOD,
                              settings=provider_configuration.client_settings)
        # Token Introspection is implemented under extension sub-package of
        # the client in pyoidc.
        self._client_extension = ClientExtension(
            client_authn_method=CLIENT_AUTHN_METHOD,
            settings=provider_configuration.client_settings)
        # Client Credentials Flow is implemented under oauth2 sub-package of
        # the client in pyoidc.
        self._oauth2_client = Oauth2Client(
            client_authn_method=CLIENT_AUTHN_METHOD,
            message_factory=CCMessageFactory,
            settings=self._provider_configuration.client_settings)

        provider_metadata = provider_configuration.ensure_provider_metadata(
            self._client)
        self._client.handle_provider_config(
            ProviderConfigurationResponse(**provider_metadata.to_dict()),
            provider_metadata['issuer'])

        if self._provider_configuration.registered_client_metadata:
            client_metadata = self._provider_configuration.registered_client_metadata.to_dict(
            )
            client_metadata.update(redirect_uris=list(redirect_uri))
            self._store_registration_info(client_metadata)

        self._redirect_uri = redirect_uri

    def _store_registration_info(self, client_metadata):
        registration_response = RegistrationResponse(**client_metadata)
        self._client.store_registration_info(registration_response)
        self._client_extension.store_registration_info(registration_response)
        # Set client_id and client_secret for _oauth2_client. This is used
        # by Client Credentials Flow.
        self._oauth2_client.client_id = registration_response['client_id']
        self._oauth2_client.client_secret = registration_response[
            'client_secret']

    def is_registered(self):
        return bool(self._provider_configuration.registered_client_metadata)

    def register(self):
        client_metadata = self._provider_configuration.register_client(
            self._client)
        logger.debug(f'client registration response: {client_metadata}')
        self._store_registration_info(client_metadata)

    def authentication_request(self, state, nonce, extra_auth_params):
        """

        Args:
            state (str): authentication request parameter 'state'
            nonce (str): authentication request parameter 'nonce'
            extra_auth_params (Mapping[str, str]): extra authentication request parameters
        Returns:
            AuthorizationRequest: the authentication request
        """
        args = {
            'client_id': self._client.client_id,
            'response_type': 'code',
            'scope': ['openid'],
            'redirect_uri': self._redirect_uri,
            'state': state,
            'nonce': nonce,
        }

        args.update(self._provider_configuration.auth_request_params)
        args.update(extra_auth_params)
        auth_request = self._client.construct_AuthorizationRequest(
            request_args=args)
        logger.debug('sending authentication request: %s',
                     auth_request.to_json())

        return auth_request

    def login_url(self, auth_request):
        """
        Args:
            auth_request (AuthorizationRequest): authentication request
        Returns:
            str: Authentication request as a URL to redirect the user to the provider.
        """
        return auth_request.request(self._client.authorization_endpoint)

    def parse_authentication_response(self, response_params):
        """
        Parameters
        ----------
        response_params: Mapping[str, str]
            authentication response parameters.

        Returns
        -------
        Union[AuthorizationResponse, AuthorizationErrorResponse]
            The parsed authorization response.
        """
        auth_resp = self._client.parse_response(AuthorizationResponse,
                                                info=response_params,
                                                sformat='dict')
        if 'id_token' in response_params:
            auth_resp['id_token_jwt'] = response_params['id_token']
        return auth_resp

    def exchange_authorization_code(self, authorization_code: str, state: str):
        """Requests tokens from an authorization code.

        Parameters
        ----------
        authorization_code: str
            authorization code issued to client after user authorization
        state: str
            state is used to keep track of responses to outstanding requests.

        Returns
        -------
        Union[AccessTokenResponse, TokenErrorResponse, None]
            The parsed token response, or None if no token request was performed.
        """
        if not self._client.token_endpoint:
            return None

        request_args = {
            'grant_type': 'authorization_code',
            'code': authorization_code,
            'redirect_uri': self._redirect_uri
        }
        logger.debug('making token request: %s', request_args)
        client_auth_method = self._client.registration_response.get(
            'token_endpoint_auth_method', 'client_secret_basic')
        token_response = self._client.do_access_token_request(
            state=state,
            request_args=request_args,
            authn_method=client_auth_method,
            endpoint=self._client.token_endpoint)
        logger.info('Received token response.')

        return token_response

    def verify_id_token(self, id_token, auth_request):
        """
        Verifies the ID Token.

        Args:
            id_token (Mapping[str, str]): ID token claims
            auth_request (Mapping[str, str]): original authentication request parameters to validate against
                (nonce, acr_values, max_age, etc.)

        Raises:
            PyoidcError: If the ID token is invalid.

        """
        self._client.verify_id_token(id_token, auth_request)

    def refresh_token(self, refresh_token: str):
        """Requests new tokens using a refresh token.

        Parameters
        ----------
        refresh_token: str
            refresh token issued to client after user authorization.

        Returns
        -------
        Union[AccessTokenResponse, TokenErrorResponse, None]
            The parsed token response, or None if no token request was performed.
        """
        request_args = {
            'grant_type': 'refresh_token',
            'refresh_token': refresh_token,
            'redirect_uri': self._redirect_uri
        }
        client_auth_method = self._client.registration_response.get(
            'token_endpoint_auth_method', 'client_secret_basic')
        return self._client.do_access_token_refresh(
            request_args=request_args,
            authn_method=client_auth_method,
            token=Token(resp={'refresh_token': refresh_token}),
            endpoint=self._client.token_endpoint)

    def userinfo_request(self, access_token: str):
        """Retrieves ID token.

        Parameters
        ----------
        access_token: str
            Bearer access token to use when fetching userinfo.

        Returns
        -------
        Union[OpenIDSchema, UserInfoErrorResponse, ErrorResponse, None]
        """
        http_method = self._provider_configuration.userinfo_endpoint_method
        if not access_token or http_method is None or not self._client.userinfo_endpoint:
            return None

        logger.debug('making userinfo request')
        userinfo_response = self._client.do_user_info_request(
            method=http_method, token=access_token)
        logger.debug('received userinfo response: %s', userinfo_response)

        return userinfo_response

    def _token_introspection_request(
            self, access_token: str) -> TokenIntrospectionResponse:
        """Make token introspection request.

        Parameters
        ----------
        access_token: str
            Access token to be validated.

        Returns
        -------
        TokenIntrospectionResponse
            Response object contains result of the token introspection.
        """
        request_args = {
            'token': access_token,
            'token_type_hint': 'access_token'
        }
        client_auth_method = self._client.registration_response.get(
            'introspection_endpoint_auth_method', 'client_secret_basic')
        logger.info('making token introspection request')
        token_introspection_response = self._client_extension.do_token_introspection(
            request_args=request_args,
            authn_method=client_auth_method,
            endpoint=self._client.introspection_endpoint)

        return token_introspection_response

    def client_credentials_grant(self,
                                 scope: list = None,
                                 **kwargs) -> AccessTokenResponse:
        """Public method to request access_token using client_credentials flow.
        This is useful for service to service communication where user-agent is
        not available which is required in authorization code flow. Your
        service can request access_token in order to access APIs of other
        services.

        On API call, token introspection will ensure that only valid token can
        be used to access your APIs.

        Parameters
        ----------
        scope: list, optional
            List of scopes to be requested.
        **kwargs : dict, optional
            Extra arguments to client credentials flow.

        Returns
        -------
        AccessTokenResponse

        Examples
        --------
        ::

            auth = OIDCAuthentication({'default': provider_config},
                                      access_token_required=True)
            auth.init_app(app)
            auth.clients['default'].client_credentials_grant()

        Optionally, you can specify scopes for the access token.

        ::

            auth.clients['default'].client_credentials_grant(
                scope=['read', 'write'])

        You can also specify extra keyword arguments to client credentials flow.

        ::

            auth.clients['default'].client_credentials_grant(
                scope=['read', 'write'], audience=['client_id1', 'client_id2'])
        """
        request_args = {'grant_type': 'client_credentials', **kwargs}
        if scope:
            request_args['scope'] = ' '.join(scope)
        client_auth_method = self._client.registration_response.get(
            'token_endpoint_auth_method', 'client_secret_basic')
        access_token = self._oauth2_client.do_access_token_request(
            request_args=request_args,
            authn_method=client_auth_method,
            endpoint=self._client.token_endpoint)
        return access_token

    @property
    def session_refresh_interval_seconds(self):
        return self._provider_configuration.session_refresh_interval_seconds

    @property
    def provider_end_session_endpoint(self):
        provider_metadata = self._provider_configuration.ensure_provider_metadata(
            self._client)
        return provider_metadata.get('end_session_endpoint')

    @property
    def post_logout_redirect_uris(self):
        return self._client.registration_response.get(
            'post_logout_redirect_uris')
Exemplo n.º 18
0
def test_pkce_create():
    _cli = Client(config={"code_challenge": {"method": "S256", "length": 64}})
    args, cv = _cli.add_code_challenge()
    assert args["code_challenge_method"] == "S256"
    assert _eq(list(args.keys()), ["code_challenge_method", "code_challenge"])
Exemplo n.º 19
0
def test_pkce_create():
    _cli = Client(config={'code_challenge': {'method': 'S256', 'length': 64}})
    args, cv = _cli.add_code_challenge()
    assert args['code_challenge_method'] == 'S256'
    assert _eq(list(args.keys()), ['code_challenge_method', 'code_challenge'])