def test_token_error_reponse_returns_default_error_if_no_error_view_set( self): token_endpoint = ISSUER + '/token' error_response = { 'error': 'invalid_request', 'error_description': 'test error' } responses.add(responses.POST, token_endpoint, body=json.dumps(error_response), content_type='application/json') authn = OIDCAuthentication(self.app, provider_configuration_info={ 'issuer': ISSUER, 'token_endpoint': token_endpoint }, client_registration_info=dict( client_id='abc', client_secret='foo')) state = 'test_tate' with self.app.test_request_context('/redirect_uri?code=foo&state=' + state): flask.session['state'] = state response = authn._handle_authentication_response() assert response == 'Something went wrong with the authentication, please try to login again.'
def test_reauthentication_necessary_with_valid_access_token(self): authn = OIDCAuthentication( self.app, provider_configuration_info={'issuer': ISSUER}, client_registration_info={'client_id': 'foo'}) access_token = 'test token' assert authn._reauthentication_necessary(access_token) is False
def test_configurable_userinfo_endpoint_method_is_used(self, method): state = 'state' nonce = 'nonce' sub = 'foobar' authn = OIDCAuthentication( self.app, provider_configuration_info={ 'issuer': ISSUER, 'token_endpoint': '/token' }, client_registration_info={'client_id': 'foo'}, userinfo_endpoint_method=method) authn.client.do_access_token_request = MagicMock( return_value=AccessTokenResponse( **{ 'id_token': IdToken(**{ 'sub': sub, 'nonce': nonce }), 'access_token': 'access_token' })) userinfo_request_mock = MagicMock(return_value=OpenIDSchema( **{'sub': sub})) authn.client.do_user_info_request = userinfo_request_mock with self.app.test_request_context('/redirect_uri?code=foo&state=' + state): flask.session['state'] = state flask.session['nonce'] = nonce flask.session['destination'] = '/' authn._handle_authentication_response() userinfo_request_mock.assert_called_with(method=method, state=state)
def auth(self, app): """ Creates the OIDCAuthentication object to be used to protect routes. Authentication is requested with the following audiences: - lando-api: The LANDO_API_OIDC_IDENTIFIER environment variable will be included as an audience. This allows lando-api to verify that tokens created by lando-ui were intended to be used by the api. Authentication is requested with the following scopes: - openid - Permission to get a unique identifier for the user. This also permits querying Auth0 at https://OIDC_DOMAIN/userinfo for additional user information. - email - Permission to get the user's email address. - profile - Permission to get additional information about the user such as their real name, picture url, and LDAP information. """ oidc = OIDCAuthentication( app, provider_configuration_info=self.provider_info(), client_registration_info=self.client_info(), extra_request_args={ "audience": [self.oidc_config.lando_api_oidc_id()], 'scope': ['openid', 'profile', 'email'] }) return oidc
def test_does_not_need_refresh(self): authn = OIDCAuthentication( self.app, provider_configuration_info={'issuer': ISSUER}, client_registration_info={ 'client_id': 'foo', 'session_refresh_interval_seconds': 1 }, ) client_mock = MagicMock() callback_mock = MagicMock() now = time.time() callback_mock.__name__ = 'test_callback' # required for Python 2 authn.client = client_mock id_token = IdToken(**{'sub': 'sub1', 'nonce': 'nonce', 'exp': 0}) with self.app.test_request_context('/'): flask.session['destination'] = '/' flask.session['access_token'] = 'test token' flask.session['id_token'] = id_token.to_dict() flask.session['id_token_jwt'] = id_token.to_jwt() flask.session['last_authenticated'] = now + 100 authn.oidc_auth(callback_mock)() session = Session( flask_session=flask.session, client_registration_info=authn.client_registration_info) assert session.needs_refresh() is False
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 get_auth(app): auth = OIDCAuthentication( app, issuer=app.config['OIDC_ISSUER'], client_registration_info=app.config['OIDC_CLIENT_CONFIG'], ) return auth
def auth(self, app): o = OIDCAuthentication( app, provider_configuration_info=self.provider_info(), client_registration_info=self.client_info()) return o
def test_token_error_reponse_calls_to_error_view_if_set(self): token_endpoint = ISSUER + '/token' error_response = { 'error': 'invalid_request', 'error_description': 'test error' } responses.add(responses.POST, token_endpoint, body=json.dumps(error_response), content_type='application/json') authn = OIDCAuthentication(self.app, provider_configuration_info={ 'issuer': ISSUER, 'token_endpoint': token_endpoint }, client_registration_info=dict( client_id='abc', client_secret='foo')) error_view_mock = MagicMock() authn._error_view = error_view_mock state = 'test_tate' with self.app.test_request_context('/redirect_uri?code=foo&state=' + state): flask.session['state'] = state authn._handle_authentication_response() error_view_mock.assert_called_with(**error_response)
def get_oidc(self, app): extra_request_args = {"scope": ["openid", "profile"]} o = OIDCAuthentication( app, issuer="https://{DOMAIN}".format( DOMAIN=self.oidc_config.OIDC_DOMAIN), client_registration_info=self.client_info(), extra_request_args=extra_request_args, ) return o
def auth(self, app): o = OIDCAuthentication(app, issuer='https://' + self.provider_info()['issuer'], client_registration_info=self.client_info()) """ Patch rewrites redirect_uri to only SSL if running in production or stage. """ if os.getenv('environment', 'production') is not 'development': redirect_uri = o.client.registration_response['redirect_uris'][0] o.client.registration_response['redirect_uris'][0] = \ redirect_uri.replace('http', 'http') return o
def test_no_userinfo_request_is_done_if_no_userinfo_endpoint_method_is_specified( self): state = 'state' authn = OIDCAuthentication( self.app, provider_configuration_info={'issuer': ISSUER}, client_registration_info={'client_id': 'foo'}, userinfo_endpoint_method=None) userinfo_request_mock = MagicMock() authn.client.do_user_info_request = userinfo_request_mock authn._do_userinfo_request(state, None) assert not userinfo_request_mock.called
def test_authenticatate_with_extra_request_parameters(self): extra_params = {"foo": "bar", "abc": "xyz"} authn = OIDCAuthentication( self.app, provider_configuration_info={'issuer': ISSUER}, client_registration_info={'client_id': 'foo'}, extra_request_args=extra_params) with self.app.test_request_context('/'): a = authn._authenticate() request_params = dict(parse_qsl(urlparse(a.location).query)) assert set(extra_params.items()).issubset(set(request_params.items()))
def auth(self, app): o = OIDCAuthentication( app, provider_configuration_info=self.provider_info(), client_registration_info=self.client_info()) """ Patch rewrites redirect_uri to only SSL if running in production or stage. """ if os.environ['ENVIRONMENT'] == 'Production': redirect_uri = o.client.registration_response['redirect_uris'][0] o.client.registration_response['redirect_uris'][0] = \ redirect_uri.replace('http', 'https') return o
def test_session_expiration_set_to_id_token_exp(self): token_endpoint = ISSUER + '/token' userinfo_endpoint = ISSUER + '/userinfo' exp_time = 10 epoch_int = int(time.mktime(datetime(2017, 1, 1).timetuple())) id_token = IdToken( **{ 'sub': 'sub1', 'iat': epoch_int, 'iss': ISSUER, 'aud': 'foo', 'nonce': 'test', 'exp': epoch_int + exp_time }) token_response = { 'access_token': 'test', 'token_type': 'Bearer', 'id_token': id_token.to_jwt() } userinfo_response = {'sub': 'sub1'} responses.add(responses.POST, token_endpoint, body=json.dumps(token_response), content_type='application/json') responses.add(responses.POST, userinfo_endpoint, body=json.dumps(userinfo_response), content_type='application/json') authn = OIDCAuthentication( self.app, provider_configuration_info={ 'issuer': ISSUER, 'token_endpoint': token_endpoint, 'userinfo_endpoint': userinfo_endpoint }, client_registration_info={ 'client_id': 'foo', 'client_secret': 'foo' }, ) self.app.config.update({'SESSION_PERMANENT': True}) with self.app.test_request_context( '/redirect_uri?state=test&code=test'): flask.session['destination'] = '/' flask.session['state'] = 'test' flask.session['nonce'] = 'test' flask.session['id_token'] = id_token.to_dict() flask.session['id_token_jwt'] = id_token.to_jwt() authn._handle_authentication_response() assert flask.session.permanent is True assert int(flask.session.permanent_session_lifetime) == exp_time
def test_reauthenticate_if_no_session(self): authn = OIDCAuthentication( self.app, provider_configuration_info={'issuer': ISSUER}, client_registration_info={'client_id': 'foo'}) client_mock = MagicMock() callback_mock = MagicMock() callback_mock.__name__ = 'test_callback' # required for Python 2 authn.client = client_mock with self.app.test_request_context('/'): authn.oidc_auth(callback_mock)() assert client_mock.construct_AuthorizationRequest.called assert not callback_mock.called
def auth(self, app): if config.fake_account: return FakeOIDCAuthentication() oidc = OIDCAuthentication( app, issuer='https://{DOMAIN}/'.format(DOMAIN=config.oidc_domain), client_registration_info=self.client_info(), extra_request_args={ 'scope': ['openid', 'profile', 'email'], }, ) return oidc
def test_store_internal_redirect_uri_on_static_client_reg(self): responses.add(responses.GET, ISSUER + '/.well-known/openid-configuration', body=json.dumps( dict(issuer=ISSUER, token_endpoint=ISSUER + '/token')), content_type='application/json') authn = OIDCAuthentication(self.app, issuer=ISSUER, client_registration_info=dict( client_id='abc', client_secret='foo')) assert len(authn.client.registration_response['redirect_uris']) == 1 assert authn.client.registration_response['redirect_uris'][ 0] == 'http://localhost/redirect_uri'
def test_dont_reauthenticate_with_valid_id_token(self): authn = OIDCAuthentication( self.app, provider_configuration_info={'issuer': ISSUER}, client_registration_info={'client_id': 'foo'}) client_mock = MagicMock() callback_mock = MagicMock() callback_mock.__name__ = 'test_callback' # required for Python 2 authn.client = client_mock with self.app.test_request_context('/'): flask.session['destination'] = '/' flask.session['access_token'] = 'test token' authn.oidc_auth(callback_mock)() assert not client_mock.construct_AuthorizationRequest.called assert callback_mock.called is True
def test_dont_reauthenticate_silent_if_authentication_not_expired(self): authn = OIDCAuthentication( self.app, provider_configuration_info={'issuer': ISSUER}, client_registration_info={ 'client_id': 'foo', 'session_refresh_interval_seconds': 999 }) client_mock = MagicMock() callback_mock = MagicMock() callback_mock.__name__ = 'test_callback' # required for Python 2 authn.client = client_mock with self.app.test_request_context('/'): flask.session['last_authenticated'] = time.time( ) # freshly authenticated authn.oidc_auth(callback_mock)() assert not client_mock.construct_AuthorizationRequest.called assert callback_mock.called
def test_reauthenticate_if_no_session(self): authn = OIDCAuthentication( self.app, provider_configuration_info={'issuer': ISSUER}, client_registration_info={'client_id': 'foo'}, ) client_mock = MagicMock() callback_mock = MagicMock() callback_mock.__name__ = 'test_callback' # required for Python 2 authn.client = client_mock id_token = IdToken(**{'sub': 'sub1', 'nonce': 'nonce'}) with self.app.test_request_context('/'): flask.session['destination'] = '/' flask.session['access_token'] = None flask.session['id_token_jwt'] = None authn.oidc_auth(callback_mock)() assert client_mock.construct_AuthorizationRequest.called is True assert callback_mock.called is False
def test_authentication_error_reponse_returns_default_error_if_no_error_view_set( self): state = 'test_tate' error_response = { 'error': 'invalid_request', 'error_description': 'test error' } authn = OIDCAuthentication( self.app, provider_configuration_info={'issuer': ISSUER}, client_registration_info=dict(client_id='abc', client_secret='foo')) with self.app.test_request_context( '/redirect_uri?{error}&state={state}'.format( error=urlencode(error_response), state=state)): flask.session['state'] = state response = authn._handle_authentication_response() assert response == 'Something went wrong with the authentication, please try to login again.'
def test_authentication_error_reponse_calls_to_error_view_if_set(self): state = 'test_tate' error_response = { 'error': 'invalid_request', 'error_description': 'test error' } authn = OIDCAuthentication( self.app, provider_configuration_info={'issuer': ISSUER}, client_registration_info=dict(client_id='abc', client_secret='foo')) error_view_mock = MagicMock() authn._error_view = error_view_mock with self.app.test_request_context( '/redirect_uri?{error}&state={state}'.format( error=urlencode(error_response), state=state)): flask.session['state'] = state authn._handle_authentication_response() error_view_mock.assert_called_with(**error_response)
def test_should_register_client_if_not_registered_before(self): registration_endpoint = self.PROVIDER_BASEURL + '/register' provider_metadata = ProviderMetadata( self.PROVIDER_BASEURL, self.PROVIDER_BASEURL + '/auth', self.PROVIDER_BASEURL + '/jwks', registration_endpoint=registration_endpoint) provider_configurations = { self.PROVIDER_NAME: ProviderConfiguration( provider_metadata=provider_metadata, client_registration_info=ClientRegistrationInfo()) } 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')) expected_registration_request = { 'redirect_uris': ['http://{}/redirect_uri'.format(self.CLIENT_DOMAIN)], 'post_logout_redirect_uris': ['http://{}/logout'.format(self.CLIENT_DOMAIN)] } assert registration_request == expected_registration_request
def auth(self, app): """ Creates the OIDCAuthentication object to be used to protect routes. Authentication is requested with the following audiences: - lando-api: The LANDO_API_OIDC_IDENTIFIER environment variable will be included as an audience. This allows lando-api to verify that tokens created by lando-ui were intended to be used by the api. Authentication is requested with the following scopes: - openid - Permission to get a unique identifier for the user. This also permits querying Auth0 at https://OIDC_DOMAIN/userinfo for additional user information. - email - Permission to get the user's email address. - profile - Permission to get additional information about the user such as their real name, picture url, and LDAP information. """ oidc = OIDCAuthentication({"AUTH0": self.provider_configuration}, app=app) auth0 = oidc.clients["AUTH0"] oidc.clients["AUTH0"]._parse_response = parse_response_wrapper(auth0) oidc.clients["AUTH0"].userinfo_request = userinfo_request_wrapper(auth0) return oidc
def test_logout_handles_provider_without_end_session_endpoint(self): post_logout_uri = 'https://client.example.com/post_logout' authn = OIDCAuthentication( self.app, provider_configuration_info={'issuer': ISSUER}, client_registration_info={ 'client_id': 'foo', 'post_logout_redirect_uris': [post_logout_uri] }) id_token = IdToken(**{'sub': 'sub1', 'nonce': 'nonce'}) with self.app.test_request_context('/logout'): flask.session['access_token'] = 'abcde' flask.session['userinfo'] = {'foo': 'bar', 'abc': 'xyz'} flask.session['id_token'] = id_token.to_dict() flask.session['id_token_jwt'] = id_token.to_jwt() end_session_redirect = authn._logout() assert all( k not in flask.session for k in ['access_token', 'userinfo', 'id_token', 'id_token_jwt']) assert end_session_redirect is None
def test_authenticated_session(self): authn = OIDCAuthentication( self.app, provider_configuration_info={'issuer': ISSUER}, client_registration_info={'client_id': 'foo'}, ) client_mock = MagicMock() callback_mock = MagicMock() callback_mock.__name__ = 'test_callback' # required for Python 2 authn.client = client_mock id_token = IdToken(**{'sub': 'sub1', 'nonce': 'nonce', 'exp': 0}) with self.app.test_request_context('/'): flask.session['destination'] = '/' flask.session['access_token'] = 'test token' flask.session['id_token'] = id_token.to_dict() flask.session['id_token_jwt'] = id_token.to_jwt() authn.oidc_auth(callback_mock)() session = Session( flask_session=flask.session, client_registration_info=authn.client_registration_info) assert session.authenticated() is True
def test_logout(self): end_session_endpoint = 'https://provider.example.com/end_session' post_logout_uri = 'https://client.example.com/post_logout' authn = OIDCAuthentication(self.app, provider_configuration_info={ 'issuer': ISSUER, 'end_session_endpoint': end_session_endpoint }, client_registration_info={ 'client_id': 'foo', 'post_logout_redirect_uris': [post_logout_uri] }) id_token = IdToken(**{'sub': 'sub1', 'nonce': 'nonce'}) with self.app.test_request_context('/logout'): flask.session['access_token'] = 'abcde' flask.session['userinfo'] = {'foo': 'bar', 'abc': 'xyz'} flask.session['id_token'] = id_token.to_dict() flask.session['id_token_jwt'] = id_token.to_jwt() end_session_redirect = authn._logout() assert all( k not in flask.session for k in ['access_token', 'userinfo', 'id_token', 'id_token_jwt']) assert end_session_redirect.status_code == 303 assert end_session_redirect.headers['Location'].startswith( end_session_endpoint) parsed_request = dict( parse_qsl( urlparse(end_session_redirect.headers['Location']).query)) assert parsed_request['state'] == flask.session[ 'end_session_state'] assert parsed_request['id_token_hint'] == id_token.to_jwt() assert parsed_request[ 'post_logout_redirect_uri'] == post_logout_uri
def test_oidc_logout_handles_redirects_from_provider(self): end_session_endpoint = 'https://provider.example.com/end_session' post_logout_uri = 'https://client.example.com/post_logout' authn = OIDCAuthentication(self.app, provider_configuration_info={ 'issuer': ISSUER, 'end_session_endpoint': end_session_endpoint }, client_registration_info={ 'client_id': 'foo', 'post_logout_redirect_uris': [post_logout_uri] }) callback_mock = MagicMock() callback_mock.__name__ = 'test_callback' # required for Python 2 state = 'end_session_123' with self.app.test_request_context('/logout?state=' + state): flask.session['end_session_state'] = state authn.oidc_logout(callback_mock)() assert 'end_session_state' not in flask.session assert callback_mock.called
def test_oidc_logout_redirects_to_provider(self): end_session_endpoint = 'https://provider.example.com/end_session' post_logout_uri = 'https://client.example.com/post_logout' authn = OIDCAuthentication(self.app, provider_configuration_info={ 'issuer': ISSUER, 'end_session_endpoint': end_session_endpoint }, client_registration_info={ 'client_id': 'foo', 'post_logout_redirect_uris': [post_logout_uri] }) callback_mock = MagicMock() callback_mock.__name__ = 'test_callback' # required for Python 2 id_token = IdToken(**{'sub': 'sub1', 'nonce': 'nonce'}) with self.app.test_request_context('/logout'): flask.session['id_token_jwt'] = id_token.to_jwt() resp = authn.oidc_logout(callback_mock)() assert resp.status_code == 303 assert not callback_mock.called