Exemplo n.º 1
0
    def test_mfa_token_verify_auth_fail(self, mock_request_user_sync, mock_get_postal_address):
        mock_get_postal_address.return_value = self.mock_address
        mock_request_user_sync.side_effect = self.request_user_sync

        credential = self.add_token_to_user(self.test_user_eppn, 'test', 'u2f')

        with self.session_cookie(self.browser, self.test_user_eppn) as browser:
            with browser.session_transaction() as sess:
                sess['eduidIdPCredentialsUsed'] = [credential.key, 'other_id']
                sess.persist()
                response = browser.get('/verify-token/{}?idp={}'.format(credential.key, self.test_idp))
                token = sess._session.token
                if isinstance(token, six.binary_type):
                    token = token.decode('ascii')
                authn_response = self.generate_auth_response(token, self.saml_response_tpl_fail,
                                                             self.test_user_wrong_nin)
                oq_cache = OutstandingQueriesCache(sess)
                oq_cache.set(token, '/')
                sess['post-authn-action'] = 'token-verify-action'
                sess['verify_token_action_credential_id'] = credential.key
                sess.persist()

                self.assertEqual(response.status_code, 302)

                data = {'SAMLResponse': base64.b64encode(authn_response), 'RelayState': '/'}
                browser.post('/saml2-acs', data=data)

                user = self.app.central_userdb.get_user_by_eppn(self.test_user_eppn)
                user_mfa_tokens = user.credentials.filter(FidoCredential).to_list()

                self.assertEqual(len(user_mfa_tokens), 1)
                self.assertEqual(user_mfa_tokens[0].is_verified, False)

                self.assertEqual(self.app.proofing_log.db_count(), 0)
Exemplo n.º 2
0
    def test_mfa_authentication_wrong_nin(self):
        user = self.app.central_userdb.get_user_by_eppn(self.test_user_eppn)
        self.assertNotEqual(user.nins.verified.count, 0)

        next_url = base64.b64encode(b'http://idp.localhost/action').decode('utf-8')

        with self.session_cookie(self.browser, self.test_user_eppn) as browser:
            with browser.session_transaction() as sess:
                response = browser.get('/mfa-authentication/?idp={}&next={}'.format(self.test_idp, next_url))
                self.assertEqual(response.status_code, 302)

                ps = urllib.parse.urlparse(response.location)
                qs = urllib.parse.parse_qs(ps.query)
                relay_state = qs['RelayState'][0]
                token = sess._session.token
                if isinstance(token, six.binary_type):
                    token = token.decode('ascii')
                authn_response = self.generate_auth_response(token, self.saml_response_tpl_success,
                                                             self.test_user_wrong_nin)
                oq_cache = OutstandingQueriesCache(sess)
                oq_cache.set(token, relay_state)
                sess['post-authn-action'] = 'mfa-authentication-action'
                sess['eidas_redirect_urls'] = {relay_state: next_url}

            data = {'SAMLResponse': base64.b64encode(authn_response), 'RelayState': relay_state}
            response = browser.post('/saml2-acs', data=data)

            self.assertEqual(response.status_code, 302)
            self.assertEqual(response.location,
                             'http://idp.localhost/action?msg=%3AERROR%3Aeidas.nin_not_matching')
Exemplo n.º 3
0
    def test_nin_verify_already_verified(self, mock_request_user_sync, mock_get_postal_address):
        mock_get_postal_address.return_value = self.mock_address
        mock_request_user_sync.side_effect = self.request_user_sync

        user = self.app.central_userdb.get_user_by_eppn(self.test_unverified_user_eppn)
        self.assertEqual(user.nins.verified.count, 0)

        with self.session_cookie(self.browser, self.test_user_eppn) as browser:
            with browser.session_transaction() as sess:
                response = browser.get('/verify-nin/?idp={}'.format(self.test_idp))
                token = sess._session.token
                if isinstance(token, six.binary_type):
                    token = token.decode('ascii')
                authn_response = self.generate_auth_response(token, self.saml_response_tpl_success, self.test_user_nin)
                oq_cache = OutstandingQueriesCache(sess)
                oq_cache.set(token, '/')
                sess['post-authn-action'] = 'nin-verify-action'
                sess.persist()

                self.assertEqual(response.status_code, 302)

                data = {'SAMLResponse': base64.b64encode(authn_response), 'RelayState': '/'}
                response = browser.post('/saml2-acs', data=data)

                self.assertEqual(response.status_code, 302)
                self.assertEqual(response.location,
                                 '{}/nins?msg=%3AERROR%3Aeidas.nin_already_verified'.format(
                                     self.app.config['DASHBOARD_URL']))
Exemplo n.º 4
0
def create_authn_request(relay_state, selected_idp, required_loa, force_authn=False):

    kwargs = {
        "force_authn": str(force_authn).lower(),
    }

    # LOA
    current_app.logger.debug('Requesting AuthnContext {}'.format(required_loa))
    loa_uri = current_app.config['AUTHENTICATION_CONTEXT_MAP'][required_loa]
    requested_authn_context = RequestedAuthnContext(authn_context_class_ref=AuthnContextClassRef(text=loa_uri),
                                                    comparison='exact')
    kwargs['requested_authn_context'] = requested_authn_context

    # Authn algorithms
    kwargs['sign_alg'] = current_app.config['AUTHN_SIGN_ALG']
    kwargs['digest_alg'] = current_app.config['AUTHN_DIGEST_ALG']

    client = Saml2Client(current_app.saml2_config)
    try:
        session_id, info = client.prepare_for_authenticate(entityid=selected_idp, relay_state=relay_state,
                                                           binding=BINDING_HTTP_REDIRECT, **kwargs)
    except TypeError:
        current_app.logger.error('Unable to know which IdP to use')
        raise

    oq_cache = OutstandingQueriesCache(session)
    oq_cache.set(session_id, relay_state)
    return info
Exemplo n.º 5
0
def parse_authn_response(saml_response):

    client = Saml2Client(current_app.saml2_config, identity_cache=IdentityCache(session))

    oq_cache = OutstandingQueriesCache(session)
    outstanding_queries = oq_cache.outstanding_queries()

    try:
        # process the authentication response
        response = client.parse_authn_request_response(saml_response, BINDING_HTTP_POST, outstanding_queries)
    except SAMLError as e:
        current_app.logger.error('SAML response is not verified: {}'.format(e))
        raise BadSAMLResponse(str(e))
    except ParseError as e:
        current_app.logger.error('SAML response is not correctly formatted: {}'.format(e))
        raise BadSAMLResponse('SAML response XML document could not be parsed: {}'.format(e))

    if response is None:
        current_app.logger.error('SAML response is None')
        raise BadSAMLResponse(
            "SAML response has errors. Please check the logs")

    session_id = response.session_id()
    oq_cache.delete(session_id)
    return response
Exemplo n.º 6
0
def create_authn_request(relay_state, selected_idp, required_loa, force_authn=False):

    kwargs = {
        "force_authn": str(force_authn).lower(),
    }

    # LOA
    current_app.logger.debug('Requesting AuthnContext {}'.format(required_loa))
    loa_uri = current_app.config.authentication_context_map[required_loa]
    requested_authn_context = RequestedAuthnContext(
        authn_context_class_ref=AuthnContextClassRef(text=loa_uri), comparison='exact'
    )
    kwargs['requested_authn_context'] = requested_authn_context

    # Authn algorithms
    kwargs['sign_alg'] = current_app.config.authn_sign_alg
    kwargs['digest_alg'] = current_app.config.authn_digest_alg

    client = Saml2Client(current_app.saml2_config)
    try:
        session_id, info = client.prepare_for_authenticate(
            entityid=selected_idp, relay_state=relay_state, binding=BINDING_HTTP_REDIRECT, **kwargs
        )
    except TypeError:
        current_app.logger.error('Unable to know which IdP to use')
        raise

    oq_cache = OutstandingQueriesCache(session)
    oq_cache.set(session_id, relay_state)
    return info
Exemplo n.º 7
0
    def test_mfa_token_verify_no_mfa_token_in_session(self):
        credential = self.add_token_to_user(self.test_user_eppn, 'test', 'webauthn')

        with self.session_cookie(self.browser, self.test_user_eppn) as browser:
            with browser.session_transaction() as sess:
                sess['eduidIdPCredentialsUsed'] = [credential.key, 'other_id']
                sess.persist()
                response = browser.get('/verify-token/{}?idp={}'.format(credential.key, self.test_idp))
                token = sess._session.token
                if isinstance(token, six.binary_type):
                    token = token.decode('ascii')
                authn_response = self.generate_auth_response(token, self.saml_response_tpl_success, self.test_user_nin)
                oq_cache = OutstandingQueriesCache(sess)
                oq_cache.set(token, '/')
                sess['post-authn-action'] = 'token-verify-action'
                sess['verify_token_action_credential_id'] = credential.key
                sess['eduidIdPCredentialsUsed'] = ['other_id']
                sess.persist()

                self.assertEqual(response.status_code, 302)

                data = {'SAMLResponse': base64.b64encode(authn_response), 'RelayState': '/'}
                response = browser.post('/saml2-acs', data=data)

                self.assertEqual(response.status_code, 302)
                self.assertEqual(response.location,
                                 '{}/security?msg=%3AERROR%3Aeidas.token_not_in_credentials_used'.format(
                                     self.app.config['DASHBOARD_URL']))
Exemplo n.º 8
0
    def test_outstanding_queries(self):

        oqc = OutstandingQueriesCache({})
        oqc._db['user'] = '******'
        oqc._db.sync()

        self.assertEqual(oqc.outstanding_queries(),
                         {'user': '******'})
Exemplo n.º 9
0
    def test_outstanding_queries(self):

        oqc = OutstandingQueriesCache({})
        oqc._db['user'] = '******'
        oqc._db.sync()

        self.assertEqual(oqc.outstanding_queries(), {'user':
                                                     '******'})
Exemplo n.º 10
0
    def test_delete(self):
        oqc = OutstandingQueriesCache({})
        oqc.set('session_id', '/next')
        self.assertEqual(oqc.outstanding_queries(), {'session_id': '/next'})

        oqc.delete('session_id')

        self.assertEqual(oqc.outstanding_queries(), {})
Exemplo n.º 11
0
    def test_mfa_token_verify_no_verified_nin(self, mock_request_user_sync,
                                              mock_get_postal_address):
        mock_get_postal_address.return_value = self.mock_address
        mock_request_user_sync.side_effect = self.request_user_sync

        credential = self.add_token_to_user(self.test_unverified_user_eppn,
                                            'test', 'webauthn')
        user = self.app.central_userdb.get_user_by_eppn(
            self.test_unverified_user_eppn)
        self.assertEqual(user.nins.verified.count, 0)

        with self.session_cookie(self.browser,
                                 self.test_unverified_user_eppn) as browser:
            with browser.session_transaction() as sess:
                sess['eduidIdPCredentialsUsed'] = [credential.key, 'other_id']
                sess.persist()
                response = browser.get('/verify-token/{}?idp={}'.format(
                    credential.key, self.test_idp))
                token = sess._session.token
                if isinstance(token, six.binary_type):
                    token = token.decode('ascii')
                authn_response = self.generate_auth_response(
                    token, self.saml_response_tpl_success, self.test_user_nin)
                oq_cache = OutstandingQueriesCache(sess)
                oq_cache.set(token, '/')
                sess['post-authn-action'] = 'token-verify-action'
                sess['verify_token_action_credential_id'] = credential.key
                sess.persist()

                self.assertEqual(response.status_code, 302)

                data = {
                    'SAMLResponse': base64.b64encode(authn_response),
                    'RelayState': '/'
                }
                browser.post('/saml2-acs', data=data)

                user = self.app.central_userdb.get_user_by_eppn(
                    self.test_unverified_user_eppn)
                user_mfa_tokens = user.credentials.filter(
                    FidoCredential).to_list()
                self.assertEqual(len(user_mfa_tokens), 1)
                self.assertEqual(user_mfa_tokens[0].is_verified, True)

                self.assertEqual(user.nins.verified.count, 1)
                self.assertEqual(user.nins.primary.number, self.test_user_nin)

                self.assertEqual(self.app.proofing_log.db_count(), 2)
Exemplo n.º 12
0
    def test_init(self):
        fake_session_dict = {
            'user': '******',
        }
        oqc = OutstandingQueriesCache(fake_session_dict)

        self.assertIsInstance(oqc._db, SessionCacheAdapter)
Exemplo n.º 13
0
    def add_outstanding_query(self, came_from):

        queryUtility = self.testapp.app.registry.queryUtility
        session_factory = queryUtility(ISessionFactory)
        request = self.dummy_request()
        session = session_factory(request)
        session.persist()

        oq_cache = OutstandingQueriesCache(session)
        oq_cache.set(session._session.token, came_from)

        session.persist()

        self.testapp.set_cookie(self.settings['session.key'], session._session.token)

        return session._session.token
Exemplo n.º 14
0
    def add_outstanding_query(self, came_from):
        """
        Add a SAML2 authentication query to the queries cache.
        To be used before accessing the assertion consumer service.

        :param came_from: url to redirect back the client
                          after finishing with the authn service.
        :type came_from: str

        :return: the session token corresponding to the query
        :rtype: str
        """
        with self.app.test_request_context('/login'):
            self.app.dispatch_request()
            oq_cache = OutstandingQueriesCache(session)
            oq_cache.set(session.token, came_from)
            session.persist()
            return session.token
Exemplo n.º 15
0
    def test_delete(self):
        oqc = OutstandingQueriesCache({})
        oqc.set('session_id', '/next')
        self.assertEqual(oqc.outstanding_queries(), {'session_id': '/next'})

        oqc.delete('session_id')

        self.assertEqual(oqc.outstanding_queries(), {})
Exemplo n.º 16
0
    def test_nin_staging_remap_verify(self, mock_request_user_sync,
                                      mock_get_postal_address):
        self.app.config.environment = 'staging'
        self.app.config.staging_nin_map = {self.test_user_nin: '190102031234'}

        mock_get_postal_address.return_value = self.mock_address
        mock_request_user_sync.side_effect = self.request_user_sync

        user = self.app.central_userdb.get_user_by_eppn(
            self.test_unverified_user_eppn)
        self.assertEqual(user.nins.verified.count, 0)

        with self.session_cookie(self.browser,
                                 self.test_unverified_user_eppn) as browser:
            with browser.session_transaction() as sess:
                response = browser.get('/verify-nin?idp={}'.format(
                    self.test_idp))
                token = sess._session.token
                if isinstance(token, six.binary_type):
                    token = token.decode('ascii')
                authn_response = self.generate_auth_response(
                    token, self.saml_response_tpl_success, self.test_user_nin)
                oq_cache = OutstandingQueriesCache(sess)
                oq_cache.set(token, '/')
                sess['post-authn-action'] = 'nin-verify-action'
                sess.persist()

                self.assertEqual(response.status_code, 302)

                data = {
                    'SAMLResponse': base64.b64encode(authn_response),
                    'RelayState': '/'
                }
                browser.post('/saml2-acs', data=data)

                user = self.app.central_userdb.get_user_by_eppn(
                    self.test_unverified_user_eppn)

                self.assertEqual(user.nins.verified.count, 1)
                self.assertEqual(user.nins.primary.number, '190102031234')

                self.assertEqual(self.app.proofing_log.db_count(), 1)
Exemplo n.º 17
0
    def test_assertion_consumer_service(self):
        came_from = '/afterlogin/'
        eppn = 'hubba-bubba'
        with self.app.test_client() as c:
            resp = c.get('/login')
            token = session._session.token
            authr = auth_response(token, eppn)

        with self.app.test_request_context('/saml2-acs', method='POST',
                                           data={'SAMLResponse': base64.b64encode(authr),
                                                 'RelayState': came_from}):

            oq_cache = OutstandingQueriesCache(session)
            oq_cache.set(token, came_from)

            resp = self.app.dispatch_request()

            self.assertEquals(resp.status_code, 302)
            self.assertEquals(resp.location, came_from)
            self.assertEquals(session['eduPersonPrincipalName'], eppn)
Exemplo n.º 18
0
    def acs(self, url, eppn, check_fn, came_from='/camefrom/'):
        """
        common code for the tests that need to access the assertion consumer service
        and then check the side effects of this access.

        :param url: the url of the desired authentication mode.
        :type url: str
        :param eppn: the eppn of the user to access the service
        :type eppn: str
        :param check_fn: the function that checks the side effects after accessing the acs
        :type check_fn: callable
        :param came_from: Relay state
        :type came_from: six.string_types
        """
        with self.app.test_client() as c:
            resp = c.get(url)
            cookie = resp.headers['Set-Cookie']
            token = session._session.token
            if isinstance(token, six.binary_type):
                token = token.decode('ascii')
            authr = auth_response(token, eppn).encode('utf-8')

        with self.app.test_request_context(
                '/saml2-acs',
                method='POST',
                headers={'Cookie': cookie},
                data={
                    'SAMLResponse': base64.b64encode(authr),
                    'RelayState': came_from
                },
        ):

            oq_cache = OutstandingQueriesCache(session)
            oq_cache.set(token, came_from)

            resp = self.app.dispatch_request()

            self.assertEqual(resp.status_code, 302)
            self.assertEqual(resp.location, came_from)
            check_fn()
Exemplo n.º 19
0
    def test_mfa_authentication_wrong_nin(self):
        user = self.app.central_userdb.get_user_by_eppn(self.test_user_eppn)
        self.assertNotEqual(user.nins.verified.count, 0)

        next_url = base64.b64encode(b'http://idp.localhost/action').decode(
            'utf-8')

        with self.session_cookie(self.browser, self.test_user_eppn) as browser:
            with browser.session_transaction() as sess:
                response = browser.get(
                    '/mfa-authentication/?idp={}&next={}'.format(
                        self.test_idp, next_url))
                self.assertEqual(response.status_code, 302)

                ps = urllib.parse.urlparse(response.location)
                qs = urllib.parse.parse_qs(ps.query)
                relay_state = qs['RelayState'][0]
                token = sess._session.token
                if isinstance(token, six.binary_type):
                    token = token.decode('ascii')
                authn_response = self.generate_auth_response(
                    token, self.saml_response_tpl_success,
                    self.test_user_wrong_nin)
                oq_cache = OutstandingQueriesCache(sess)
                oq_cache.set(token, relay_state)
                sess['post-authn-action'] = 'mfa-authentication-action'
                sess['eidas_redirect_urls'] = {relay_state: next_url}

            data = {
                'SAMLResponse': base64.b64encode(authn_response),
                'RelayState': relay_state
            }
            response = browser.post('/saml2-acs', data=data)

            self.assertEqual(response.status_code, 302)
            self.assertEqual(
                response.location,
                'http://idp.localhost/action?msg=%3AERROR%3Aeidas.nin_not_matching'
            )
Exemplo n.º 20
0
    def add_outstanding_query(self, came_from):
        """
        Add a SAML2 authentication query to the queries cache.
        To be used before accessing the assertion consumer service.

        :param came_from: url to redirect back the client
                          after finishing with the authn service.
        :type came_from: str

        :return: the session token corresponding to the query
        :rtype: str
        """
        with self.app.test_request_context('/login'):
            self.app.dispatch_request()
            oq_cache = OutstandingQueriesCache(session)
            token = session.token
            if isinstance(token, six.binary_type):
                token = token.decode('ascii')
            oq_cache.set(token, came_from)
            session.persist(
            )  # Explicit session.persist is needed when working within a test_request_context
            return token
Exemplo n.º 21
0
    def test_nin_verify_already_verified(self, mock_request_user_sync: Any,
                                         mock_get_postal_address: Any):
        mock_get_postal_address.return_value = self.mock_address
        mock_request_user_sync.side_effect = self.request_user_sync

        user = self.app.central_userdb.get_user_by_eppn(
            self.test_unverified_user_eppn)
        self.assertEqual(user.nins.verified.count, 0)

        with self.session_cookie(self.browser, self.test_user_eppn) as browser:
            with browser.session_transaction() as sess:
                response = browser.get('/verify-nin/?idp={}'.format(
                    self.test_idp))
                token = sess._session.token
                if isinstance(token, six.binary_type):
                    token = token.decode('ascii')
                authn_response = self.generate_auth_response(
                    token, self.saml_response_tpl_success, self.test_user_nin)
                oq_cache = OutstandingQueriesCache(sess)
                oq_cache.set(token, '/')
                sess['post-authn-action'] = 'nin-verify-action'
                sess.persist()

                self.assertEqual(response.status_code, 302)

                data = {
                    'SAMLResponse': base64.b64encode(authn_response),
                    'RelayState': '/'
                }
                response = browser.post('/saml2-acs', data=data)

                self.assertEqual(response.status_code, 302)
                self.assertEqual(
                    response.location,
                    '{}?msg=%3AERROR%3Aeidas.nin_already_verified'.format(
                        self.app.config.nin_verify_redirect_url),
                )
Exemplo n.º 22
0
def parse_authn_response(saml_response):

    client = Saml2Client(current_app.saml2_config, identity_cache=IdentityCache(session))

    oq_cache = OutstandingQueriesCache(session)
    outstanding_queries = oq_cache.outstanding_queries()

    try:
        # process the authentication response
        response = client.parse_authn_request_response(saml_response, BINDING_HTTP_POST, outstanding_queries)
    except SAMLError as e:
        current_app.logger.error('SAML response is not verified: {}'.format(e))
        raise BadSAMLResponse(str(e))
    except ParseError as e:
        current_app.logger.error('SAML response is not correctly formatted: {}'.format(e))
        raise BadSAMLResponse('SAML response XML document could not be parsed: {}'.format(e))

    if response is None:
        current_app.logger.error('SAML response is None')
        raise BadSAMLResponse("SAML response has errors. Please check the logs")

    session_id = response.session_id()
    oq_cache.delete(session_id)
    return response
Exemplo n.º 23
0
    def test_nin_staging_remap_verify(self, mock_request_user_sync, mock_get_postal_address):
        self.app.config['ENVIRONMENT'] = 'staging'
        self.app.config['STAGING_NIN_MAP'] = {
            self.test_user_nin: '190102031234'
        }

        mock_get_postal_address.return_value = self.mock_address
        mock_request_user_sync.side_effect = self.request_user_sync

        user = self.app.central_userdb.get_user_by_eppn(self.test_unverified_user_eppn)
        self.assertEqual(user.nins.verified.count, 0)

        with self.session_cookie(self.browser, self.test_unverified_user_eppn) as browser:
            with browser.session_transaction() as sess:
                response = browser.get('/verify-nin?idp={}'.format(self.test_idp))
                token = sess._session.token
                if isinstance(token, six.binary_type):
                    token = token.decode('ascii')
                authn_response = self.generate_auth_response(token, self.saml_response_tpl_success,
                                                             self.test_user_nin)
                oq_cache = OutstandingQueriesCache(sess)
                oq_cache.set(token, '/')
                sess['post-authn-action'] = 'nin-verify-action'
                sess.persist()

                self.assertEqual(response.status_code, 302)

                data = {'SAMLResponse': base64.b64encode(authn_response), 'RelayState': '/'}
                browser.post('/saml2-acs', data=data)

                user = self.app.central_userdb.get_user_by_eppn(self.test_unverified_user_eppn)

                self.assertEqual(user.nins.verified.count, 1)
                self.assertEqual(user.nins.primary.number, '190102031234')

                self.assertEqual(self.app.proofing_log.db_count(), 1)
Exemplo n.º 24
0
    def test_mfa_token_verify_no_mfa_token_in_session(self):
        credential = self.add_token_to_user(self.test_user_eppn, 'test',
                                            'webauthn')

        with self.session_cookie(self.browser, self.test_user_eppn) as browser:
            with browser.session_transaction() as sess:
                sess['eduidIdPCredentialsUsed'] = [credential.key, 'other_id']
                sess.persist()
                response = browser.get('/verify-token/{}?idp={}'.format(
                    credential.key, self.test_idp))
                token = sess._session.token
                if isinstance(token, six.binary_type):
                    token = token.decode('ascii')
                authn_response = self.generate_auth_response(
                    token, self.saml_response_tpl_success, self.test_user_nin)
                oq_cache = OutstandingQueriesCache(sess)
                oq_cache.set(token, '/')
                sess['post-authn-action'] = 'token-verify-action'
                sess['verify_token_action_credential_id'] = credential.key
                sess['eduidIdPCredentialsUsed'] = ['other_id']
                sess.persist()

                self.assertEqual(response.status_code, 302)

                data = {
                    'SAMLResponse': base64.b64encode(authn_response),
                    'RelayState': '/'
                }
                response = browser.post('/saml2-acs', data=data)

                self.assertEqual(response.status_code, 302)
                self.assertEqual(
                    response.location,
                    '{}?msg=%3AERROR%3Aeidas.token_not_in_credentials_used'.
                    format(self.app.config.token_verify_redirect_url),
                )