def test_freja_flow(self, mock_request_user_sync, mock_get_postal_address, mock_oidc_call): mock_oidc_call.return_value = True 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_user_eppn) with self.session_cookie(self.browser, self.test_user_eppn) as browser: response = json.loads(browser.get('/freja/proofing').data) self.assertEqual(response['type'], 'GET_OIDC_PROOFING_FREJA_PROOFING_SUCCESS') csrf_token = response['payload']['csrf_token'] with self.session_cookie(self.browser, self.test_user_eppn) as browser: data = {'nin': self.test_user_nin, 'csrf_token': csrf_token} response = browser.post('/freja/proofing', data=json.dumps(data), content_type=self.content_type_json) response = json.loads(response.data) self.assertEqual(response['type'], 'POST_OIDC_PROOFING_FREJA_PROOFING_SUCCESS') with self.session_cookie(self.browser, self.test_user_eppn) as browser: response = json.loads(browser.get('/freja/proofing').data) self.assertEqual(response['type'], 'GET_OIDC_PROOFING_FREJA_PROOFING_SUCCESS') # No actual oidc flow tested here proofing_state = self.app.proofing_statedb.get_state_by_eppn( self.test_user_eppn) userinfo = { 'results': { 'freja_eid': { 'vetting_time': time.time(), 'ref': '1234.5678.9012.3456', 'opaque': '1' + json.dumps({ 'nonce': proofing_state.nonce, 'token': proofing_state.token }), 'country': 'SE', 'ssn': self.test_user_nin, } } } with self.app.app_context(): handle_freja_eid_userinfo(user, proofing_state, userinfo) user = self.app.private_userdb.get_user_by_eppn(self.test_user_eppn) self.assertEqual(user.nins.primary.number, self.test_user_nin) self.assertEqual(user.nins.primary.created_by, proofing_state.nin.created_by) self.assertEqual(user.nins.primary.verified_by, proofing_state.nin.created_by) self.assertEqual(user.nins.primary.is_verified, True) self.assertEqual(self.app.proofing_log.db_count(), 1)
def authorization_response(): # parse authentication response query_string = request.query_string.decode('utf-8') current_app.logger.debug('query_string: {!s}'.format(query_string)) authn_resp = current_app.oidc_client.parse_response(AuthorizationResponse, info=query_string, sformat='urlencoded') current_app.logger.debug( 'Authorization response received: {!s}'.format(authn_resp)) if authn_resp.get('error'): current_app.logger.error( 'AuthorizationError from {}: {} - {} ({})'.format( request.host, authn_resp['error'], authn_resp.get('error_message'), authn_resp.get('error_uri'))) current_app.stats.count(name='authn_response_op_error') return make_response('OK', 200) user_oidc_state = authn_resp['state'] proofing_state = current_app.proofing_statedb.get_state_by_oidc_state( user_oidc_state) if not proofing_state: msg = 'The \'state\' parameter ({!s}) does not match a user state.'.format( user_oidc_state) current_app.logger.error(msg) current_app.stats.count(name='authn_response_proofing_state_missing') return make_response('OK', 200) current_app.logger.debug('Proofing state {!s} for user {!s} found'.format( proofing_state.state, proofing_state.eppn)) # Check if the token from the authn response matches the token we created when making the auth request authorization_header = request.headers.get('Authorization') if authorization_header != 'Bearer {}'.format(proofing_state.token): current_app.logger.error( 'The authorization token ({!s}) did not match the expected'.format( authorization_header)) current_app.stats.count(name='authn_response_authn_failure') return make_response('FORBIDDEN', 403) # TODO: We should save the auth response code to the proofing state to be able to continue a failed attempt # do token request args = { 'code': authn_resp['code'], 'redirect_uri': url_for('oidc_proofing.authorization_response', _external=True) } current_app.logger.debug('Trying to do token request: {!s}'.format(args)) # TODO: What should be saved from the token response and where? token_resp = current_app.oidc_client.do_access_token_request( scope='openid', state=authn_resp['state'], request_args=args, authn_method='client_secret_basic') current_app.logger.debug( 'token response received: {!s}'.format(token_resp)) id_token = token_resp['id_token'] if id_token['nonce'] != proofing_state.nonce: current_app.logger.error( 'The \'nonce\' parameter does not match for user {!s}.'.format( proofing_state.eppn)) current_app.stats.count(name='authn_response_token_request_failure') return make_response('OK', 200) current_app.stats.count(name='authn_response_token_request_success') # do userinfo request current_app.logger.debug('Trying to do userinfo request:') # TODO: Do we need to save anything else from the userinfo response userinfo = current_app.oidc_client.do_user_info_request( method=current_app.config.userinfo_endpoint_method, state=authn_resp['state']) current_app.logger.debug('userinfo received: {!s}'.format(userinfo)) if userinfo['sub'] != id_token['sub']: current_app.logger.error( 'The \'sub\' of userinfo does not match \'sub\' of ID Token for user {!s}.' .format(proofing_state.eppn)) current_app.stats.count(name='authn_response_userinfo_request_failure') return make_response('OK', 200) current_app.stats.count(name='authn_response_userinfo_request_success') # TODO: Break out in parts to be able to continue the proofing process after a successful authorization response # TODO: even if the token request, userinfo request or something internal fails am_user = current_app.central_userdb.get_user_by_eppn(proofing_state.eppn) user = ProofingUser.from_user(am_user, current_app.private_userdb) try: # Handle userinfo differently depending on data in userinfo if userinfo.get('identity'): current_app.logger.info( 'Handling userinfo as generic seleg vetting for user {}'. format(user)) current_app.stats.count(name='seleg.authn_response_received') helpers.handle_seleg_userinfo(user, proofing_state, userinfo) elif userinfo.get('results'): current_app.logger.info( 'Handling userinfo as freja vetting for user {}'.format(user)) current_app.stats.count(name='freja.authn_response_received') helpers.handle_freja_eid_userinfo(user, proofing_state, userinfo) except (TaskFailed, KeyError) as e: current_app.logger.error( 'Failed to handle userinfo for user {}'.format(user)) current_app.logger.error('Exception: {}'.format(e)) current_app.stats.count(name='authn_response_handling_failure') finally: # Remove users proofing state current_app.proofing_statedb.remove_state(proofing_state) return make_response('OK', 200)
def authorization_response(): # parse authentication response query_string = request.query_string.decode('utf-8') current_app.logger.debug('query_string: {!s}'.format(query_string)) authn_resp = current_app.oidc_client.parse_response(AuthorizationResponse, info=query_string, sformat='urlencoded') current_app.logger.debug('Authorization response received: {!s}'.format(authn_resp)) if authn_resp.get('error'): current_app.logger.error('AuthorizationError {!s} - {!s} ({!s})'.format(request.host, authn_resp['error'], authn_resp.get('error_message'), authn_resp.get('error_uri'))) current_app.stats.count(name='authn_response_op_error') return make_response('OK', 200) user_oidc_state = authn_resp['state'] proofing_state = current_app.proofing_statedb.get_state_by_oidc_state(user_oidc_state) if not proofing_state: msg = 'The \'state\' parameter ({!s}) does not match a user state.'.format(user_oidc_state) current_app.logger.error(msg) current_app.stats.count(name='authn_response_proofing_state_missing') return make_response('OK', 200) current_app.logger.debug('Proofing state {!s} for user {!s} found'.format(proofing_state.state, proofing_state.eppn)) # Check if the token from the authn response matches the token we created when making the auth request authorization_header = request.headers.get('Authorization') if authorization_header != 'Bearer {}'.format(proofing_state.token): current_app.logger.error('The authorization token ({!s}) did not match the expected'.format( authorization_header)) current_app.stats.count(name='authn_response_authn_failure') return make_response('FORBIDDEN', 403) # TODO: We should save the auth response code to the proofing state to be able to continue a failed attempt # do token request args = { 'code': authn_resp['code'], 'redirect_uri': url_for('oidc_proofing.authorization_response', _external=True) } current_app.logger.debug('Trying to do token request: {!s}'.format(args)) # TODO: What should be saved from the token response and where? token_resp = current_app.oidc_client.do_access_token_request(scope='openid', state=authn_resp['state'], request_args=args, authn_method='client_secret_basic') current_app.logger.debug('token response received: {!s}'.format(token_resp)) id_token = token_resp['id_token'] if id_token['nonce'] != proofing_state.nonce: current_app.logger.error('The \'nonce\' parameter does not match for user {!s}.'.format(proofing_state.eppn)) current_app.stats.count(name='authn_response_token_request_failure') return make_response('OK', 200) current_app.stats.count(name='authn_response_token_request_success') # do userinfo request current_app.logger.debug('Trying to do userinfo request:') # TODO: Do we need to save anything else from the userinfo response userinfo = current_app.oidc_client.do_user_info_request(method=current_app.config['USERINFO_ENDPOINT_METHOD'], state=authn_resp['state']) current_app.logger.debug('userinfo received: {!s}'.format(userinfo)) if userinfo['sub'] != id_token['sub']: current_app.logger.error('The \'sub\' of userinfo does not match \'sub\' of ID Token for user {!s}.'.format( proofing_state.eppn)) current_app.stats.count(name='authn_response_userinfo_request_failure') return make_response('OK', 200) current_app.stats.count(name='authn_response_userinfo_request_success') # TODO: Break out in parts to be able to continue the proofing process after a successful authorization response # TODO: even if the token request, userinfo request or something internal fails am_user = current_app.central_userdb.get_user_by_eppn(proofing_state.eppn) user = ProofingUser(data=am_user.to_dict()) try: # Handle userinfo differently depending on data in userinfo if userinfo.get('identity'): current_app.logger.info('Handling userinfo as generic seleg vetting for user {}'.format(user)) current_app.stats.count(name='seleg.authn_response_received') helpers.handle_seleg_userinfo(user, proofing_state, userinfo) elif userinfo.get('results'): current_app.logger.info('Handling userinfo as freja vetting for user {}'.format(user)) current_app.stats.count(name='freja.authn_response_received') helpers.handle_freja_eid_userinfo(user, proofing_state, userinfo) except (TaskFailed, KeyError) as e: current_app.logger.error('Failed to handle userinfo for user {}'.format(user)) current_app.logger.error('Exception: {}'.format(e)) current_app.stats.count(name='authn_response_handling_failure') finally: # Remove users proofing state current_app.proofing_statedb.remove_state(proofing_state) return make_response('OK', 200)