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']))
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)
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')
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
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
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']))
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(), {})
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)
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
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
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)
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)
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()
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' )
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
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), )
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)
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), )