def test_should_not_fetch_provider_metadata_if_given(self): provider_metadata = self.provider_metadata() provider_config = ProviderConfiguration( provider_metadata=provider_metadata, client_registration_info=ClientRegistrationInfo()) provider_config.ensure_provider_metadata(Client(CLIENT_AUTHN_METHOD)) assert provider_config._provider_metadata == provider_metadata
def test_should_raise_exception_for_non_registered_client_when_missing_registration_endpoint( self): provider_config = ProviderConfiguration( provider_metadata=self.provider_metadata(), client_registration_info=ClientRegistrationInfo()) with pytest.raises(ValueError) as exc_info: provider_config.register_client([]) assert 'registration_endpoint' in str(exc_info.value)
def test_should_not_register_dynamic_client_if_client_metadata_is_given( self): client_metadata = ClientMetadata(client_id='client1', client_secret='secret1') provider_config = ProviderConfiguration( provider_metadata=self.provider_metadata(), client_metadata=client_metadata) provider_config.register_client(None) assert provider_config._client_metadata == client_metadata
def test_should_raise_exception_for_non_registered_client_when_missing_registration_endpoint( self): provider_config = ProviderConfiguration( provider_metadata=self.provider_metadata(), client_registration_info=ClientRegistrationInfo()) assert provider_config._provider_metadata[ 'registration_endpoint'] is None with pytest.raises(ValueError): provider_config.register_client(None)
def test_is_registered(self): unregistered = ProviderConfiguration( provider_metadata=self.PROVIDER_METADATA, client_registration_info=ClientRegistrationInfo()) registered = ProviderConfiguration( provider_metadata=self.PROVIDER_METADATA, client_metadata=self.CLIENT_METADATA) assert PyoidcFacade(unregistered, REDIRECT_URI).is_registered() is False assert PyoidcFacade(registered, REDIRECT_URI).is_registered() is True
def test_should_not_register_dynamic_client_if_client_metadata_is_given( self): client_metadata = ClientMetadata( client_id='client1', client_secret='secret1', redirect_uris=['https://client.example.com/redirect']) provider_config = ProviderConfiguration( provider_metadata=self.provider_metadata(), client_metadata=client_metadata) provider_config.register_client([]) assert provider_config._client_metadata == client_metadata
def test_register(self): registration_endpoint = self.PROVIDER_BASEURL + '/register' redirect_uris = ['https://client.example.com/redirect'] post_logout_redirect_uris = ['https://client.example.com/logout'] client_registration_response = { 'client_id': 'client1', 'client_secret': 'secret1', 'client_name': 'Test Client', 'redirect_uris': redirect_uris, 'post_logout_redirect_uris': post_logout_redirect_uris } responses.add(responses.POST, registration_endpoint, json=client_registration_response) provider_metadata = self.PROVIDER_METADATA.copy( registration_endpoint=registration_endpoint) unregistered = ProviderConfiguration( provider_metadata=provider_metadata, client_registration_info=ClientRegistrationInfo( redirect_uris=redirect_uris, post_logout_redirect_uris=post_logout_redirect_uris)) facade = PyoidcFacade(unregistered, REDIRECT_URI) facade.register() assert facade.is_registered() is True
def login(): login_hint_token = request.args.get('login_hint_token') state = request.args.get('state') mccmnc = request.args.get('mccmnc') name = getNameForCurrentSession() if state != state_key: return redirect('/') elif name != None: return redirect('/logged-in') else: provider_metadata = get_provider_metadata(login_hint_token, mccmnc) client_metadata = get_client_metadata() if provider_metadata != None and client_metadata != None: # configure provider (i.e. MNO) and client config = ProviderConfiguration( client_metadata=client_metadata, auth_request_params=auth_params, provider_metadata=provider_metadata) # TODO: once dynamic discovery with XCI providers is working, touch this up / ensure this works to manage logging in with different providers url = url_for('login', _external=True) # use pyoidc internals to add provider / client to session, no need to store UserSession(flask.session, PROVIDER_NAME) # instantiate OIDC through facade client = PyoidcFacade(config, url) # authenticate return openid._authenticate(client) return render_template('home.html', error='Could not build a valid configuration for the selected provider. Error detail: MCC=%s, MNC=%s' % (mcc, mnc))
def test_missing_client_metadata_raises_exception(self): with pytest.raises(ValueError) as exc_info: ProviderConfiguration(issuer=self.PROVIDER_BASEURL) exc_message = str(exc_info.value) assert 'client_registration_info' in exc_message assert 'client_metadata' in exc_message
def test_authentication_request(self): extra_user_auth_params = {"foo": "bar", "abc": "xyz"} config = ProviderConfiguration( provider_metadata=self.PROVIDER_METADATA, client_metadata=self.CLIENT_METADATA, auth_request_params=extra_user_auth_params, ) state = "test_state" nonce = "test_nonce" facade = PyoidcFacade(config, self.REDIRECT_URI) extra_lib_auth_params = {"foo": "baz", "qwe": "rty"} auth_request = facade.authentication_request(state, nonce, self.FULL_REDIRECT_URI, extra_lib_auth_params) assert auth_request.startswith( self.PROVIDER_METADATA["authorization_endpoint"]) auth_request_params = dict(parse_qsl(urlparse(auth_request).query)) expected_auth_params = { "scope": "openid", "response_type": "code", "client_id": self.CLIENT_METADATA["client_id"], "redirect_uri": self.FULL_REDIRECT_URI, "state": state, "nonce": nonce, } expected_auth_params.update(extra_user_auth_params) expected_auth_params.update(extra_lib_auth_params) assert auth_request_params == expected_auth_params
def init_app(self, provider_metadata_extras=None, client_metadata_extras=None, **kwargs): required_provider_metadata = { "issuer": self.PROVIDER_BASEURL, "authorization_endpoint": self.PROVIDER_BASEURL + "/auth", "jwks_uri": self.PROVIDER_BASEURL + "/jwks", } if provider_metadata_extras: required_provider_metadata.update(provider_metadata_extras) provider_metadata = ProviderMetadata(**required_provider_metadata) required_client_metadata = { "client_id": self.CLIENT_ID, "client_secret": "secret1" } if client_metadata_extras: required_client_metadata.update(client_metadata_extras) client_metadata = ClientMetadata(**required_client_metadata) provider_configurations = { self.PROVIDER_NAME: ProviderConfiguration(provider_metadata=provider_metadata, client_metadata=client_metadata, **kwargs) } authn = OIDCAuthentication(provider_configurations) authn.init_app(self.app) return authn
def test_authentication_request(self): extra_user_auth_params = {'foo': 'bar', 'abc': 'xyz'} config = ProviderConfiguration( provider_metadata=self.PROVIDER_METADATA, client_metadata=self.CLIENT_METADATA, auth_request_params=extra_user_auth_params) state = 'test_state' nonce = 'test_nonce' facade = PyoidcFacade(config, REDIRECT_URI) extra_lib_auth_params = {'foo': 'baz', 'qwe': 'rty'} auth_request = facade.authentication_request(state, nonce, extra_lib_auth_params) assert auth_request.startswith( self.PROVIDER_METADATA['authorization_endpoint']) auth_request_params = dict(parse_qsl(urlparse(auth_request).query)) expected_auth_params = { 'scope': 'openid', 'response_type': 'code', 'client_id': self.CLIENT_METADATA['client_id'], 'redirect_uri': REDIRECT_URI, 'state': state, 'nonce': nonce } expected_auth_params.update(extra_user_auth_params) expected_auth_params.update(extra_lib_auth_params) assert auth_request_params == expected_auth_params
def test_parse_authentication_response_preserves_id_token_jwt(self): facade = PyoidcFacade( ProviderConfiguration(provider_metadata=self.PROVIDER_METADATA, client_metadata=self.CLIENT_METADATA), REDIRECT_URI) state = 'state-1234' now = int(time.time()) id_token, id_token_signing_key = signed_id_token({ 'iss': self.PROVIDER_METADATA['issuer'], 'sub': 'test_sub', 'aud': 'client1', 'exp': now + 1, 'iat': now }) responses.add(responses.GET, self.PROVIDER_METADATA['jwks_uri'], json={'keys': [id_token_signing_key.serialize()]}) auth_response = AuthorizationResponse(**{ 'state': state, 'id_token': id_token }) parsed_auth_response = facade.parse_authentication_response( auth_response) assert isinstance(parsed_auth_response, AuthorizationResponse) assert parsed_auth_response['state'] == state assert parsed_auth_response['id_token_jwt'] == id_token
def init_app(self, provider_metadata_extras=None, client_metadata_extras=None, **kwargs): required_provider_metadata = { 'issuer': self.PROVIDER_BASEURL, 'authorization_endpoint': self.PROVIDER_BASEURL + '/auth', 'jwks_uri': self.PROVIDER_BASEURL + '/jwks' } if provider_metadata_extras: required_provider_metadata.update(provider_metadata_extras) provider_metadata = ProviderMetadata(**required_provider_metadata) required_client_metadata = { 'client_id': self.CLIENT_ID, 'client_secret': 'secret1' } if client_metadata_extras: required_client_metadata.update(client_metadata_extras) client_metadata = ClientMetadata(**required_client_metadata) provider_configurations = { self.PROVIDER_NAME: ProviderConfiguration(provider_metadata=provider_metadata, client_metadata=client_metadata, **kwargs) } authn = OIDCAuthentication(provider_configurations) authn.init_app(self.app) return authn
def test_registered_client_metadata_is_forwarded_to_pyoidc(self): config = ProviderConfiguration( provider_metadata=self.PROVIDER_METADATA, client_metadata=self.CLIENT_METADATA, ) facade = PyoidcFacade(config, self.REDIRECT_URI) assert facade._client.registration_response
def test_no_registered_client_metadata_is_handled(self): config = ProviderConfiguration( provider_metadata=self.PROVIDER_METADATA, client_registration_info=ClientRegistrationInfo(), ) facade = PyoidcFacade(config, self.REDIRECT_URI) assert not facade._client.registration_response
def test_no_userinfo_request_is_made_if_no_userinfo_endpoint_is_configured( self): facade = PyoidcFacade( ProviderConfiguration(provider_metadata=self.PROVIDER_METADATA, client_metadata=self.CLIENT_METADATA), REDIRECT_URI) assert facade.userinfo_request('test_token') is None
def test_parse_authentication_response_handles_error_response(self): facade = PyoidcFacade(ProviderConfiguration(provider_metadata=self.PROVIDER_METADATA, client_metadata=self.CLIENT_METADATA), self.REDIRECT_URI) error_response = AuthorizationErrorResponse(**{'error': 'invalid_request', 'state': 'state-1234'}) parsed_auth_response = facade.parse_authentication_response(error_response) assert isinstance(parsed_auth_response, AuthorizationErrorResponse) assert parsed_auth_response.to_dict() == error_response.to_dict()
def test_missing_provider_metadata_raises_exception(self): with pytest.raises(ValueError) as exc_info: ProviderConfiguration( client_registration_info=ClientRegistrationInfo()) exc_message = str(exc_info.value) assert 'issuer' in exc_message assert 'provider_metadata' in exc_message
def test_register_client_should_register_dynamic_client_if_initial_access_token_present( self): registration_endpoint = self.PROVIDER_BASEURL + '/register' redirect_uris = [ 'https://client.example.com/redirect', 'https://client.example.com/redirect2' ] post_logout_redirect_uris = ['https://client.example.com/logout'] client_registration_response = { 'client_id': 'client1', 'client_secret': 'secret1', 'client_name': 'Test Client', 'redirect_uris': redirect_uris, 'post_logout_redirect_uris': post_logout_redirect_uris, 'registration_client_uri': 'https://op.example.com/register/client1', 'registration_access_token': 'registration_access_token1' } responses.add(responses.POST, registration_endpoint, json=client_registration_response) provider_config = ProviderConfiguration( provider_metadata=self.provider_metadata( registration_endpoint=registration_endpoint), client_registration_info=ClientRegistrationInfo( client_name='Test Client', redirect_uris=redirect_uris, post_logout_redirect_uris=post_logout_redirect_uris, registration_token='initial_access_token')) provider_config.register_client(Client(CLIENT_AUTHN_METHOD)) assert provider_config._client_metadata['client_id'] == 'client1' assert provider_config._client_metadata['client_secret'] == 'secret1' assert provider_config._client_metadata['client_name'] == 'Test Client' assert provider_config._client_metadata[ 'registration_client_uri'] == 'https://op.example.com/register/client1' assert provider_config._client_metadata[ 'registration_access_token'] == 'registration_access_token1' assert provider_config._client_metadata[ 'redirect_uris'] == redirect_uris assert provider_config._client_metadata[ 'post_logout_redirect_uris'] == post_logout_redirect_uris assert responses.calls[0].request.headers['Authorization'] == \ f"Bearer {base64.b64encode('initial_access_token'.encode()).decode()}"
def test_no_userinfo_request_is_made_if_no_access_token(self): provider_metadata = self.PROVIDER_METADATA.copy( userinfo_endpoint=self.PROVIDER_BASEURL + '/userinfo') facade = PyoidcFacade( ProviderConfiguration(provider_metadata=provider_metadata, client_metadata=self.CLIENT_METADATA), REDIRECT_URI) assert facade.userinfo_request(None) is None
def test_token_request_handles_missing_provider_token_endpoint(self): facade = PyoidcFacade( ProviderConfiguration( provider_metadata=self.PROVIDER_METADATA, client_metadata=self.CLIENT_METADATA, ), self.REDIRECT_URI, ) assert facade.token_request("1234") is None
def test_post_logout_redirect_uris(self): post_logout_redirect_uris = ['https://client.example.com/logout'] client_metadata = self.CLIENT_METADATA.copy( post_logout_redirect_uris=post_logout_redirect_uris) facade = PyoidcFacade( ProviderConfiguration(provider_metadata=self.PROVIDER_METADATA, client_metadata=client_metadata), REDIRECT_URI) assert facade.post_logout_redirect_uris == post_logout_redirect_uris
def test_token_request_handles_error_response(self): token_endpoint = self.PROVIDER_BASEURL + '/token' token_response = TokenErrorResponse(error='invalid_request', error_description='test error description') responses.add(responses.POST, token_endpoint, json=token_response.to_dict(), status=400) provider_metadata = self.PROVIDER_METADATA.copy(token_endpoint=token_endpoint) facade = PyoidcFacade(ProviderConfiguration(provider_metadata=provider_metadata, client_metadata=self.CLIENT_METADATA), self.REDIRECT_URI) assert facade.token_request('1234') == token_response
def test_register(self): registration_endpoint = self.PROVIDER_BASEURL + '/register' responses.add(responses.POST, registration_endpoint, json=self.CLIENT_METADATA.to_dict()) provider_metadata = self.PROVIDER_METADATA.copy(registration_endpoint=registration_endpoint) unregistered = ProviderConfiguration(provider_metadata=provider_metadata, client_registration_info=ClientRegistrationInfo()) facade = PyoidcFacade(unregistered, self.REDIRECT_URI) facade.register() assert facade.is_registered() is True
def provider_configuration(self): return ProviderConfiguration( client_metadata=self.client_metadata, provider_metadata=self.provider_metadata, session_refresh_interval_seconds=15 * 60, auth_request_params={ "audience": [self.oidc_config.lando_api_oidc_id()], "scope": ["openid", "profile", "email", "lando"], }, )
def test_parse_authentication_response(self): facade = PyoidcFacade(ProviderConfiguration(provider_metadata=self.PROVIDER_METADATA, client_metadata=self.CLIENT_METADATA), self.REDIRECT_URI) auth_code = 'auth_code-1234' state = 'state-1234' auth_response = AuthorizationResponse(**{'state': state, 'code': auth_code}) parsed_auth_response = facade.parse_authentication_response(auth_response.to_dict()) assert isinstance(parsed_auth_response, AuthorizationResponse) assert parsed_auth_response.to_dict() == auth_response.to_dict()
def test_configurable_userinfo_endpoint_method_is_used(self, userinfo_http_method): userinfo_endpoint = self.PROVIDER_BASEURL + '/userinfo' userinfo_response = OpenIDSchema(sub='user1') responses.add(userinfo_http_method, userinfo_endpoint, json=userinfo_response.to_dict()) provider_metadata = self.PROVIDER_METADATA.copy(userinfo_endpoint=userinfo_endpoint) facade = PyoidcFacade(ProviderConfiguration(provider_metadata=provider_metadata, client_metadata=self.CLIENT_METADATA, userinfo_http_method=userinfo_http_method), self.REDIRECT_URI) assert facade.userinfo_request('test_token') == userinfo_response
def test_should_register_client_if_not_registered_before( self, post_logout_redirect_uris): registration_endpoint = self.PROVIDER_BASEURL + '/register' provider_metadata = ProviderMetadata( self.PROVIDER_BASEURL, self.PROVIDER_BASEURL + '/auth', self.PROVIDER_BASEURL + '/jwks', registration_endpoint=registration_endpoint) client_metadata = {} if post_logout_redirect_uris: client_metadata[ 'post_logout_redirect_uris'] = post_logout_redirect_uris provider_configurations = { self.PROVIDER_NAME: ProviderConfiguration( provider_metadata=provider_metadata, client_registration_info=ClientRegistrationInfo( **client_metadata)) } authn = OIDCAuthentication(provider_configurations) authn.init_app(self.app) # register logout view to force 'post_logout_redirect_uris' to be included in registration request logout_view_mock = self.get_view_mock() self.app.add_url_rule('/logout', view_func=logout_view_mock) authn.oidc_logout(logout_view_mock) responses.add(responses.POST, registration_endpoint, json={ 'client_id': 'client1', 'client_secret': 'secret1' }) view_mock = self.get_view_mock() with self.app.test_request_context('/'): auth_redirect = authn.oidc_auth(self.PROVIDER_NAME)(view_mock)() self.assert_auth_redirect(auth_redirect) registration_request = json.loads( responses.calls[0].request.body.decode('utf-8')) with self.app.app_context(): full_redirect_uri = flask.url_for( registration_request['redirect_uris'][0]) registration_request['redirect_uris'] = full_redirect_uri expected_post_logout_redirect_uris = post_logout_redirect_uris if post_logout_redirect_uris else [ 'http://{}/logout'.format(self.CLIENT_DOMAIN) ] expected_registration_request = { 'redirect_uris': 'http://{}/redirect_uri'.format(self.CLIENT_DOMAIN), 'post_logout_redirect_uris': expected_post_logout_redirect_uris } assert registration_request == expected_registration_request
def test_no_userinfo_request_is_made_if_no_userinfo_http_method_is_configured( self): facade = PyoidcFacade( ProviderConfiguration( provider_metadata=self.PROVIDER_METADATA, client_metadata=self.CLIENT_METADATA, userinfo_http_method=None, ), self.REDIRECT_URI, ) assert facade.userinfo_request("test_token") is None