def test_full_flow(self, context, frontend_with_extra_scopes): redirect_uri = "https://client.example.com/redirect" response_type = "code id_token token" mock_callback = Mock() frontend_with_extra_scopes.auth_req_callback_func = mock_callback # discovery http_response = frontend_with_extra_scopes.provider_config(context) provider_config = ProviderConfigurationResponse().deserialize(http_response.message, "json") # client registration registration_request = RegistrationRequest(redirect_uris=[redirect_uri], response_types=[response_type]) context.request = registration_request.to_dict() http_response = frontend_with_extra_scopes.client_registration(context) registration_response = RegistrationResponse().deserialize(http_response.message, "json") # authentication request authn_req = AuthorizationRequest( redirect_uri=redirect_uri, client_id=registration_response["client_id"], response_type=response_type, scope="openid email eduperson", state="state", nonce="nonce", ) context.request = dict(parse_qsl(authn_req.to_urlencoded())) frontend_with_extra_scopes.handle_authn_request(context) assert mock_callback.call_count == 1 # fake authentication response from backend internal_response = self.setup_for_authn_response( context, frontend_with_extra_scopes, authn_req ) http_response = frontend_with_extra_scopes.handle_authn_response( context, internal_response ) authn_resp = AuthorizationResponse().deserialize(urlparse(http_response.message).fragment, "urlencoded") assert "code" in authn_resp assert "access_token" in authn_resp assert "id_token" in authn_resp # token request context.request = AccessTokenRequest(redirect_uri=authn_req["redirect_uri"], code=authn_resp["code"]).to_dict() credentials = "{}:{}".format(registration_response["client_id"], registration_response["client_secret"]) basic_auth = urlsafe_b64encode(credentials.encode("utf-8")).decode("utf-8") context.request_authorization = "Basic {}".format(basic_auth) http_response = frontend_with_extra_scopes.token_endpoint(context) parsed = AccessTokenResponse().deserialize(http_response.message, "json") assert "access_token" in parsed assert "id_token" in parsed # userinfo request context.request = {} context.request_authorization = "Bearer {}".format(parsed["access_token"]) http_response = frontend_with_extra_scopes.userinfo_endpoint(context) parsed = OpenIDSchema().deserialize(http_response.message, "json") assert "email" in parsed assert "eduperson_principal_name" in parsed assert "eduperson_scoped_affiliation" in parsed
def test_complete_auth_token_idtoken_no_alg_config(self): _state = "state0" self.consumer.consumer_config["response_type"] = ["id_token", "token"] self.consumer.provider_info = ProviderConfigurationResponse( issuer="https://example.com") # abs min self.consumer.authz_req = {} # Store AuthzReq with state as key args = { "client_id": self.consumer.client_id, "response_type": self.consumer.consumer_config["response_type"], "scope": ["openid"], "nonce": "nonce", } token = IdToken( iss="https://example.com", aud="client_1", sub="some_sub", exp=1565348600, iat=1565348300, nonce="nonce", ) location = ( "https://example.com/cb?state=state0&access_token=token&token_type=bearer&" "scope=openid&id_token={}".format( token.to_jwt(key=[SYMKey(key="hemlig")], algorithm="HS256"))) with responses.RequestsMock() as rsps: rsps.add( responses.GET, "https://example.com/authorization", status=302, headers={"location": location}, ) result = self.consumer.do_authorization_request(state=_state, request_args=args) query = parse_qs(urlparse(result.request.url).query) assert query["client_id"] == ["client_1"] assert query["scope"] == ["openid"] assert query["response_type"] == ["id_token token"] assert query["state"] == ["state0"] assert query["nonce"] == ["nonce"] assert query["redirect_uri"] == ["https://example.com/cb"] parsed = urlparse(result.headers["location"]) with freeze_time("2019-08-09 11:00:00"): part = self.consumer.parse_authz(query=parsed.query, algs={"sign": "HS256"}) assert isinstance(part, tuple) auth = part[0] atr = part[1] idt = part[2] assert auth is None assert isinstance(atr, AccessTokenResponse) assert _eq( atr.keys(), ["access_token", "id_token", "token_type", "state", "scope"]) assert isinstance(idt, IdToken)
def test_token_endpoint_is_required_for_other_than_implicit_flow_only(self): provider_config = { "issuer": "https://server.example.com", "authorization_endpoint": "https://server.example.com/connect/authorize", "jwks_uri": "https://server.example.com/jwks.json", "response_types_supported": ["code", "id_token"], "subject_types_supported": ["public", "pairwise"], "id_token_signing_alg_values_supported": ["RS256", "ES256", "HS256"], } with pytest.raises(MissingRequiredAttribute): ProviderConfigurationResponse(**provider_config).verify()
def test_token_endpoint_is_not_required_for_implicit_flow_only(self): provider_config = { "issuer": "https://server.example.com", "authorization_endpoint": "https://server.example.com/connect/authorize", "jwks_uri": "https://server.example.com/jwks.json", "response_types_supported": ["id_token", "token id_token"], "subject_types_supported": ["public", "pairwise"], "id_token_signing_alg_values_supported": ["RS256", "ES256", "HS256"], } # should not raise an exception assert ProviderConfigurationResponse(**provider_config).verify()
def test_complete_auth_token_idtoken_none_cipher_token(self): _state = "state0" self.consumer.consumer_config["response_type"] = ["token"] self.consumer.registration_response = RegistrationResponse( id_token_signed_response_alg="none") self.consumer.provider_info = ProviderConfigurationResponse( issuer="https://example.com") # abs min self.consumer.authz_req = {} # Store AuthzReq with state as key self.consumer.sdb[_state] = {"redirect_uris": []} args = { "client_id": self.consumer.client_id, "response_type": self.consumer.consumer_config["response_type"], "scope": ["openid"], "nonce": "nonce", } token = IdToken( iss="https://example.com", aud="client_1", sub="some_sub", exp=1565348600, iat=1565348300, nonce="nonce", ) # Downgrade the algorithm to `none` location = ( "https://example.com/cb?state=state0&access_token=token&token_type=bearer&" "scope=openid&id_token={}".format( token.to_jwt(key=KC_RSA.keys(), algorithm="none"))) with responses.RequestsMock() as rsps: rsps.add( responses.GET, "https://example.com/authorization", status=302, headers={"location": location}, ) result = self.consumer.do_authorization_request(state=_state, request_args=args) query = parse_qs(urlparse(result.request.url).query) assert query["client_id"] == ["client_1"] assert query["scope"] == ["openid"] assert query["response_type"] == ["token"] assert query["state"] == ["state0"] assert query["nonce"] == ["nonce"] assert query["redirect_uri"] == ["https://example.com/cb"] parsed = urlparse(result.headers["location"]) with freeze_time("2019-08-09 11:00:00"): with pytest.raises(WrongSigningAlgorithm): self.consumer.parse_authz(query=parsed.query)
def setup_consumer(self, session_db_factory): client_id = "client_1" client_config = { "client_id": client_id, "client_authn_method": CLIENT_AUTHN_METHOD, } self.consumer = Consumer(DictSessionBackend(), CONFIG, client_config, SERVER_INFO) self.consumer.behaviour = { "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"] } self.consumer.keyjar = CLIKEYS self.consumer.redirect_uris = ["https://example.com/cb"] self.consumer.authorization_endpoint = "https://example.com/authorization" self.consumer.token_endpoint = "https://example.com/token" self.consumer.userinfo_endpoint = "https://example.com/userinfo" # type: ignore self.consumer.client_secret = "hemlig" self.consumer.secret_type = "basic" self.consumer.provider_info = ProviderConfigurationResponse( issuer="https://example.com") # abs min
def __init__(self, flask_app, client_registration_info=None, issuer=None, provider_configuration_info=None, userinfo_endpoint_method='POST', extra_request_args=None): self.app = flask_app self.userinfo_endpoint_method = userinfo_endpoint_method self.extra_request_args = extra_request_args or {} self.client = Client(client_authn_method=CLIENT_AUTHN_METHOD) if not issuer and not provider_configuration_info: raise ValueError( 'Either \'issuer\' (for dynamic discovery) or \'provider_configuration_info\' (for static configuration must be specified.' ) if issuer and not provider_configuration_info: self.client.provider_config(issuer) else: self.client.handle_provider_config( ProviderConfigurationResponse(**provider_configuration_info), provider_configuration_info['issuer']) self.client_registration_info = client_registration_info or {} # setup redirect_uri self.app.add_url_rule('/redirect_uri', 'redirect_uri', self._handle_authentication_response) with self.app.app_context(): self.client_registration_info['redirect_uris'] = url_for( 'redirect_uri') if client_registration_info and 'client_id' in client_registration_info: # static client info provided self.client.store_registration_info( RegistrationRequest(**client_registration_info)) self.logout_view = None self._error_view = None
def create_openid_client(oidc_provider_config, client_id, client_secret): """ Build an OIDC client using the oidc provider configuration """ # build our OpenID client openid_client = Client(client_authn_method=CLIENT_AUTHN_METHOD, client_id=client_id) # save the client information to the OIDC client client_registration_info = RegistrationResponse( **{ "client_id": client_id, "client_secret": client_secret }) openid_client.store_registration_info(client_registration_info) # save the provider config to the OIDC client provider_configuration = ProviderConfigurationResponse( **oidc_provider_config) openid_client.handle_provider_config(provider_configuration, provider_configuration['issuer'], True, True) return openid_client