Пример #1
0
    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."
Пример #2
0
    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)
Пример #3
0
 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
Пример #4
0
    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()))
Пример #5
0
    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()))
Пример #6
0
 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
Пример #7
0
    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
Пример #8
0
 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['id_token'] = {'exp': time.time() + 25}
         authn.oidc_auth(callback_mock)()
     assert not client_mock.construct_AuthorizationRequest.called
     assert callback_mock.called is True
Пример #9
0
 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
Пример #10
0
 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
Пример #11
0
    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
Пример #12
0
def get_auth(app):
    auth = OIDCAuthentication(
        app,
        issuer=app.config['OIDC_ISSUER'],
        client_registration_info=app.config['OIDC_CLIENT_CONFIG'],
    )
    return auth
Пример #13
0
    def auth(self, app):
        o = OIDCAuthentication(
            app,
            provider_configuration_info=self.provider_info(),
            client_registration_info=self.client_info())

        return o
Пример #14
0
    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
Пример #15
0
 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
Пример #16
0
 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
Пример #17
0
 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
Пример #18
0
 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
Пример #19
0
 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.'
Пример #20
0
 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
Пример #21
0
 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)
Пример #22
0
 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={'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)
Пример #23
0
 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
Пример #24
0
 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
Пример #25
0
    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 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
Пример #27
0
    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
Пример #28
0
    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
Пример #29
0
 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
Пример #30
0
 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
Пример #31
0
 def test_reauthenticate_silent_if_refresh_expired(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()
     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'] = 1
         authn.oidc_auth(callback_mock)()
     assert client_mock.construct_AuthorizationRequest.called is True
     assert callback_mock.called is False
Пример #32
0
    def test_unauthenticated_session_with_refresh(self):
        authn = OIDCAuthentication(
            self.app,
            provider_configuration_info={'issuer': ISSUER},
            client_registration_info={
                'client_id': 'foo',
                'session_refresh_interval_seconds': 300
            },
        )
        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'] = '/'

            authn.oidc_auth(callback_mock)()
            session = Session(
                flask_session=flask.session,
                client_registration_info=authn.client_registration_info)

            assert session.authenticated() is False
Пример #33
0
    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
Пример #34
0
    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'
Пример #35
0
    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
Пример #36
0
    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
Пример #37
0
def create_app(config=None):
    app = Flask(__name__, instance_relative_config=True)

    # Load application config from various sources
    # ------------------------------------------------------------------------------
    # 1. Defaults from this package
    app.config.from_object(DefaultConfig)

    # 2. From a config.py file in the application directory
    app.config.from_pyfile(filename=os.path.join(os.getcwd(), "config.py"),
                           silent=True)

    # 3. From a dynamically configurable file location
    if os.environ.get('CONFIG_LOCATION'):
        app.config.from_pyfile(filename=os.environ.get('CONFIG_LOCATION'),
                               silent=False)

    # 4. Testing config
    if config:
        app.config.from_mapping(config)

    # 5. Load some final computed config
    #    NOTE: This is placed here as it relies on other config values that
    #          may be configured after the user provided config for example.
    oidc_logout_redirect_uri = os.environ.get(
        'OIDC_LOGOUT_REDIRECT_URI',
        'https://' + app.config['SERVER_NAME'] + '/logout')
    oidc_auth_request_params = json.loads(
        os.environ.get('OIDC_AUTH_REQUEST_PARAMS', '{}'))
    if oidc_auth_request_params:
        if app.config['OIDC_EXTRA_AUTH_REQUEST_PARAMS']:
            app.logger.warning(
                'OIDC_EXTRA_AUTH_REQUEST_PARAMS is being overridden by OIDC_AUTH_REQUEST_PARAMS being explicitly set.'
            )
    else:
        oidc_auth_request_params['scope'] = " ".join(
            re.split(",| ", app.config['OIDC_SCOPE']))
        if app.config['OIDC_EXTRA_AUTH_REQUEST_PARAMS']:
            oidc_auth_request_params.update(
                app.config['OIDC_EXTRA_AUTH_REQUEST_PARAMS'])

    app.config.from_mapping({
        'OIDC_LOGOUT_REDIRECT_URI':
        oidc_logout_redirect_uri,
        'OIDC_CLIENT_METADATA': {
            'client_id': app.config['OIDC_CLIENT_ID'],
            'client_secret': app.config['OIDC_CLIENT_SECRET'],
            'post_logout_redirect_uris': str.split(oidc_logout_redirect_uri,
                                                   ",")
        },
        'OIDC_AUTH_REQUEST_PARAMS':
        oidc_auth_request_params,
    })

    # Initialize OpenID Connect extension
    # ------------------------------------------------------------------------------

    # The client metadata will be consumed no matter what...
    # https://github.com/zamzterz/Flask-pyoidc#dynamic-provider-configuration
    client_metadata = ClientMetadata(**app.config['OIDC_CLIENT_METADATA'])

    # ... but if explicit OIDC provider information is provided, we use that
    # instead of the information dynamically provided by the
    # .well-known/openid-configuration endpoint.
    if app.config['OIDC_PROVIDER_METADATA']:
        provider_metadata = ProviderMetadata(
            **app.config['OIDC_PROVIDER_METADATA'])
        provider = ProviderConfiguration(
            provider_metadata=provider_metadata,
            client_metadata=client_metadata,
            auth_request_params=app.config['OIDC_AUTH_REQUEST_PARAMS'],
        )
    else:
        provider = ProviderConfiguration(
            issuer=app.config['OIDC_ISSUER'],
            client_metadata=client_metadata,
            auth_request_params=app.config['OIDC_AUTH_REQUEST_PARAMS'],
        )

    auth = OIDCAuthentication(
        provider_configurations={
            'default': provider,
        },
        app=app,
    )

    # The /health endpoint returns a JSON string like...
    # {"hostname": "a3731af16461", "status": "success", "timestamp": 1551186453.8854501, "results": []}
    health = HealthCheck(app, "/health")

    # If an OAuth error response is received, either in the authentication or
    # token response, it will be passed to the "error view".
    @auth.error_view
    def error(error=None, error_description=None):
        return jsonify({'error': error, 'message': error_description})

    @app.route('/')
    def index():
        """
        If a user tries to access this application directly,
        just redirect them to Discourse.
        :return: Redirect to the configurated DISCOURSE_URL
        """
        return redirect(app.config.get('DISCOURSE_URL'), 302)

    @app.route('/sso/login')
    def payload_check():
        """
        Verify the payload and signature coming from a Discourse server and if
        correct redirect to the authentication page after saving the nonce in
        the session as discourse_nonce.
        
        :return: The redirection page to the authentication page
        """

        # Get payload and signature from Discourse request
        payload = request.args.get('sso', '')
        signature = request.args.get('sig', '')

        if not payload or not signature:
            app.logger.info(
                '/sso/login -> 400: missing payload="%s" or signature="%s"',
                payload, signature)
            abort(400)

        app.logger.debug('Request to login with payload="%s" signature="%s"',
                         payload, signature)
        app.logger.debug('Session Secret Key: %s', app.secret_key)
        app.logger.debug('SSO Secret Key: %s',
                         app.config.get('DISCOURSE_SECRET_KEY'))

        # Calculate and compare request signature
        dig = hmac.new(
            app.config.get('DISCOURSE_SECRET_KEY', '').encode('utf-8'),
            payload.encode('utf-8'), hashlib.sha256).hexdigest()
        app.logger.debug('Calculated hash: %s', dig)

        if dig != signature:
            app.logger.info(
                '/sso/login -> 400: dig / signature mismatch. dig="%s" and signature="%s"',
                dig, signature)
            abort(400)

        # Decode the payload and store in session
        decoded_msg = base64.b64decode(payload).decode('utf-8')
        session[
            'discourse_nonce'] = decoded_msg  # This can't just be 'nonce' as Flask-pyoidc will steamroll it

        # Redirect to authorization endpoint
        return redirect(url_for('sso_auth'))

    @app.route('/sso/auth')
    @auth.oidc_auth('default')
    def sso_auth():
        """
        Read the user attributes provided by Flask-pyoidc and
        create the payload to send to Discourse.
        :return: The redirection page to Discourse
        """

        # Check to make sure we have a valid session
        if 'discourse_nonce' not in session:
            app.logger.info(
                '/sso/auth -> 403: discourse_nonce not found in session, arriving here without coming from /sso/login?'
            )
            abort(403)

        attribute_map = app.config.get('USERINFO_SSO_MAP')

        sso_attributes = {}
        userinfo = session['userinfo']

        # Check if the provided userinfo should be used to set information to be
        # passed to discourse. Do it by checking if the userinfo field is...
        # 1. explicitly mapped using the provided map
        # 2. if it can match one of the known attributes with discourse_ prefixed
        # 3. if it can match one of the known attributes directly
        for userinfo_key, userinfo_value in userinfo.items():
            attribute_key = attribute_map.get(userinfo_key)

            if attribute_key:
                pass
            elif userinfo_key in [
                    "discourse_" + attr for attr in ALL_ATTRIBUTES
            ]:
                attribute_key = userinfo_key[len("discourse_"):]
            elif userinfo_key in ALL_ATTRIBUTES:
                attribute_key = userinfo_key

            if attribute_key:
                if attribute_key in BOOL_ATTRIBUTES:
                    userinfo_value = "false" if str.lower(
                        str(userinfo_value)) in ['false', 'f', '0'] else "true"
                sso_attributes[attribute_key] = userinfo_value

        # Check if we have a default value that should be used
        default_sso_attributes = app.config.get('DEFAULT_SSO_ATTRIBUTES')
        for default_attribute_key, default_attribute_value in default_sso_attributes.items(
        ):
            if default_attribute_key not in sso_attributes:
                sso_attributes[default_attribute_key] = default_attribute_value

        # Check if we got the required attributes
        for required_attribute in REQUIRED_ATTRIBUTES:
            if not sso_attributes.get(required_attribute):
                app.logger.info(
                    f'/sso/auth -> 403: {required_attribute} not found in userinfo: '
                    + json.dumps(session['userinfo']))
                abort(403)

        # All systems are go!
        app.logger.debug(
            f'Authenticating "{sso_attributes.get("external_id")}", named "{sso_attributes.get("name")}" with email: "{sso_attributes.get("email")}"'
        )

        # Construct the response inner query parameters
        query = session['discourse_nonce']
        for sso_attribute_key, sso_attribute_value in sso_attributes.items():
            query += f'&{sso_attribute_key}={quote(str(sso_attribute_value))}'
        app.logger.debug('Query string to return: %s', query)

        # Encode response
        query_b64 = base64.b64encode(query.encode('utf-8'))
        app.logger.debug('Base64 query string to return: %s', query_b64)

        # Build URL-safe response
        query_urlenc = quote(query_b64)
        app.logger.debug('URLEnc query string to return: %s', query_urlenc)

        # Generate signature for response
        sig = hmac.new(
            app.config.get('DISCOURSE_SECRET_KEY').encode('utf-8'),
            query_b64,
            hashlib.sha256,
        ).hexdigest()

        app.logger.debug('Signature: %s', sig)

        # Build redirect URL
        redirect_url = (app.config.get('DISCOURSE_URL') + '/session/sso_login?'
                        'sso=' + query_urlenc + '&sig=' + sig)

        # Redirect back to Discourse
        return redirect(redirect_url)

    @app.route('/logout')
    @auth.oidc_logout
    def logout():
        """
        Handle logging a user out. Flask-pyoidc does the heavy lifting here.
        :return: Redirect to the application index
        """
        return redirect(url_for('index'), 302)

    @app.errorhandler(403)
    def attribute_not_provided(error):
        """
        Render a custom error page in case the IdP authenticate the user but does
        not provide the requested attributes
        :type error: object
        """
        app.logger.info(f'403: error: "{error}"')
        return render_template('403.html'), 403

    return app
Пример #38
0
# Get app config from absolute file path
if os.path.exists(os.path.join(os.getcwd(), "config.py")):
    app.config.from_pyfile(os.path.join(os.getcwd(), "config.py"))
else:
    app.config.from_pyfile(os.path.join(os.getcwd(), "config.env.py"))

app.config["GIT_REVISION"] = subprocess.check_output(
    ['git', 'rev-parse', '--short', 'HEAD']).decode('utf-8').rstrip()

_config = ProviderConfiguration(
    app.config['OIDC_ISSUER'],
    client_metadata=ClientMetadata(
        app.config['OIDC_CLIENT_CONFIG']['client_id'],
        app.config['OIDC_CLIENT_CONFIG']['client_secret']))
auth = OIDCAuthentication({'default': _config}, app)

# Get s3 bucket for use in functions and templates
s3_bucket = get_bucket(app.config["S3_URL"], app.config["S3_KEY"],
                       app.config["S3_SECRET"], app.config["BUCKET_NAME"])

# Database setup
db = SQLAlchemy(app)
migrate = flask_migrate.Migrate(app, db)

# Import db models after instantiating db object
from audiophiler.models import File, Harold, Auth

# Create CSHLDAP connection
ldap = CSHLDAP(app.config["LDAP_BIND_DN"], app.config["LDAP_BIND_PW"])
Пример #39
0
 def test_reauthentication_necessary_with_None(self):
     authn = OIDCAuthentication(self.app, provider_configuration_info={'issuer': ISSUER},
                                client_registration_info={'client_id': 'foo'})
     assert authn._reauthentication_necessary(None) is True
Пример #40
0
 def test_reauthentication_necessary_with_valid_id_token(self):
     authn = OIDCAuthentication(self.app, provider_configuration_info={'issuer': ISSUER},
                                client_registration_info={'client_id': 'foo'})
     id_token = {'iss': ISSUER}
     assert authn._reauthentication_necessary(id_token) is False