def test_unknown_input_data_allowed(self): one = copy.deepcopy(_one_dict) one['foo'] = 'bar' addr = Nin(data = one, raise_on_unknown = False) out = addr.to_dict() self.assertIn('foo', out) self.assertEqual(out['foo'], one['foo'])
def test_proofing_flow_previously_added_wrong_nin(self): # Send letter to correct nin self.send_letter(self.test_user_nin) # Remove correct unverified nin and add wrong nin user = self.app.central_userdb.get_user_by_eppn(self.test_user_eppn) user.nins.remove(self.test_user_nin) not_verified_nin = Nin.from_dict( dict(number=self.test_user_wrong_nin, created_by='test', verified=False, primary=False)) user.nins.add(not_verified_nin) self.app.central_userdb.save(user) # Time passes, user gets code in the mail. Enters code. proofing_state = self.app.proofing_statedb.get_state_by_eppn(user.eppn) response = self.verify_code(proofing_state.nin.verification_code, None) # Now check that the (now verified) NIN on the user is back to the one used to request the letter 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 get_user_set_nins(self, eppn, ninlist): """ Fetch a user from the FakeUserDb and set it's NINs to those in ninlist. :param eppn: eduPersonPrincipalName or email address :param ninlist: List of NINs to configure user with (all verified) :type eppn: str or unicode :type ninlist: [str or unicode] :return: IdPUser instance :rtype: IdPUser """ user = self.idp_userdb.lookup_user(eppn) [user.nins.remove(x) for x in user.nins.to_list()] for number in ninlist: this_nin = Nin.from_dict( dict( number=number, created_by='unittest', created_ts=True, verified=True, primary=user.nins.primary is None, )) user.nins.add(this_nin) return user
def test_verify_existant_nin(self): # Add a non-verified NIN to the user with no NINs email = self.no_nin_user_email user = self.userdb_new.get_user_by_mail(email) for nin in user.nins.to_list(): user.nins.remove(nin.number) new_nin = Nin(data={ 'number': '123456789050', 'verified': False, 'primary': False, }) user.nins.add(new_nin) self.userdb_new.save(user) # Set up a pending verfication verification_data = { '_id': ObjectId(), 'code': '123124', 'model_name': 'norEduPersonNIN', 'obj_id': '123456789050', 'user_oid': user.user_id, 'timestamp': datetime.utcnow(), 'verified': False, } self.db.verifications.insert(verification_data) self.set_logged(email) response = self.testapp.post('/profile/nins-actions/', { 'identifier': '123456789050 0', 'action': 'verify' }) response_json = json.loads(response.body) self.assertEqual(response_json['result'], 'getcode')
def test_proofing_flow_previously_added_wrong_nin(self): json_data = self.get_state() csrf_token = json_data['payload']['csrf_token'] # Send letter to correct nin self.send_letter(self.test_user_nin, csrf_token) # Remove correct unverified nin and add wrong nin user = self.app.central_userdb.get_user_by_eppn(self.test_user_eppn) user.nins.remove(self.test_user_nin) not_verified_nin = Nin(number=self.test_user_wrong_nin, application='test', verified=False, primary=False) user.nins.add(not_verified_nin) self.app.central_userdb.save(user) json_data = self.get_state() csrf_token = json_data['payload']['csrf_token'] with self.app.test_request_context(): user = self.app.central_userdb.get_user_by_eppn(self.test_user_eppn, raise_on_missing=True) proofing_state = self.app.proofing_statedb.get_state_by_eppn(user.eppn, raise_on_missing=False) json_data = self.verify_code(proofing_state.nin.verification_code, csrf_token) self.assertTrue(json_data['payload']['success']) 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 test_create_oidcproofingstate(self): """ { 'eduPersonPrincipalName': 'foob-arra', 'nin': { 'created_ts': datetime.datetime(2016, 11, 16, 14, 47, 0, 379810), 'verified': False, 'number': '190101021234', 'created_by': 'eduid_oidc_proofing' }, 'state': '2c84fedd-a694-46f0-b235-7c4dd7982852' 'nonce': 'bbca50f6-5213-4784-b6e6-289bd1debda5', 'token': 'de5b3f2a-14e9-49b8-9c78-a15fcf60d119', } """ nin = Nin(number='200102034567', application='eduid_oidc_proofing', verified=False, primary=False) state = OidcProofingState({ 'eduPersonPrincipalName': EPPN, 'nin': nin.to_dict(), 'state': '2c84fedd-a694-46f0-b235-7c4dd7982852', 'nonce': 'bbca50f6-5213-4784-b6e6-289bd1debda5', 'token': 'de5b3f2a-14e9-49b8-9c78-a15fcf60d119' }) state_dict = state.to_dict() self.assertItemsEqual(state_dict.keys(), [ '_id', 'nin', 'eduPersonPrincipalName', 'state', 'nonce', 'token' ]) self.assertItemsEqual( state_dict['nin'].keys(), ['created_by', 'created_ts', 'number', 'verified'])
def _add_nin_to_user(new_nin, user, created_ts=None): """ Add a NIN to a user. Part of set_nin_verified() above. """ primary = user.nins.verified.count == 0 new_nin_obj = Nin(number = new_nin, application = 'dashboard', verified = True, primary = primary, created_ts = created_ts, ) # Remove the NIN from the user if it is already there try: user.nins.remove(new_nin) except UserDBValueError: pass user.nins.add(new_nin_obj)
def test_proofing_flow_previously_added_nin(self): user = self.app.central_userdb.get_user_by_eppn(self.test_user_eppn) not_verified_nin = Nin.from_dict( dict(number=self.test_user_nin, created_by='test', verified=False, primary=False)) user.nins.add(not_verified_nin) self.app.central_userdb.save(user) self.send_letter(self.test_user_nin) proofing_state = self.app.proofing_statedb.get_state_by_eppn(user.eppn) self.verify_code(proofing_state.nin.verification_code, None) 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, not_verified_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 add_success_other(self, ninform): newnin = self.schema.serialize(ninform) newnin = newnin['norEduPersonNIN'] newnin = normalize_nin(newnin) old_user = self.request.db.profiles.find_one({ 'norEduPersonNIN': newnin }) if old_user: old_user = DashboardUser(data=old_user) retrieve_modified_ts(old_user, self.request.dashboard_userdb) old_user.nins.remove(newnin) self.context.save_dashboard_user(old_user) primary = False if self.user.nins.count == 0: primary = True newnin_obj = Nin(number=newnin, application='dashboard', verified=True, primary=primary) self.user.nins.add(newnin_obj) try: self.context.save_dashboard_user(self.user) except UserOutOfSync: message = _('Your user profile is out of sync. Please ' 'reload the page and try again.') else: message = _('Your national identity number has been confirmed') # Save the state in the verifications collection save_as_verified(self.request, 'norEduPersonNIN', self.user, newnin) self.request.session.flash( get_localizer(self.request).translate(message), queue='forms') self.request.stats.count('nin_add_other')
def test_freja_flow_previously_added_wrong_nin(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) not_verified_nin = Nin(number=self.test_user_wrong_nin, application='test', verified=False, primary=False) user.nins.add(not_verified_nin) self.app.central_userdb.save(user) with self.session_cookie(self.browser, self.test_user_eppn) as browser: response = json.loads(browser.get('/proofing').data) self.assertEqual(response['type'], 'GET_OIDC_PROOFING_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_wrong_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') # 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 test_seleg_flow_previously_added_wrong_nin(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) not_verified_nin = Nin(number=self.test_user_wrong_nin, application='test', verified=False, primary=False) user.nins.add(not_verified_nin) self.app.central_userdb.save(user) with self.session_cookie(self.browser, self.test_user_eppn) as browser: response = json.loads(browser.get('/proofing').data) self.assertEqual(response['type'], 'GET_OIDC_PROOFING_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_wrong_nin, 'csrf_token': csrf_token} response = browser.post('/proofing', data=json.dumps(data), content_type=self.content_type_json) response = json.loads(response.data) self.assertEqual(response['type'], 'POST_OIDC_PROOFING_PROOFING_SUCCESS') with self.session_cookie(self.browser, self.test_user_eppn) as browser: response = json.loads(browser.get('/proofing').data) self.assertEqual(response['type'], 'GET_OIDC_PROOFING_PROOFING_SUCCESS') # Fake callback from OP qrdata = json.loads(response['payload']['qr_code'][1:]) proofing_state = self.app.proofing_statedb.get_state_by_eppn( self.test_user_eppn) userinfo = { 'identity': self.test_user_nin, 'metadata': { 'score': 100, 'opaque': '1' + json.dumps({ 'nonce': proofing_state.nonce, 'token': proofing_state.token }), 'ra_app': 'App id for vetting app' } } self.mock_authorization_response(qrdata, 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 test_unknown_input_data(self): one = copy.deepcopy(_one_dict) one['foo'] = 'bar' with self.assertRaises(eduid_userdb.exceptions.UserHasUnknownData): Nin(data = one)
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'))) 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) 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 QR code 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)) 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 and where should be save from the token response 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)) return make_response('OK', 200) # 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)) return make_response('OK', 200) # Check proofed nin against self proclaimed OidcProofingState.nin.number number = userinfo['identity'] if proofing_state.nin.number == number: nin = Nin(data=proofing_state.nin.to_dict()) nin.verified = True # 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()) # Check if the user has more than one verified nin if user.nins.primary is None: # No primary NIN found, make the only verified NIN primary nin.is_primary = True user.nins.add(nin) # XXX: Send proofing data to some kind of proofing log # User from central db is as up to date as it can be no need to check for modified time user.modified_ts = True # Save user to private db current_app.proofing_userdb.save(user, check_sync=False) # TODO: Need to decide where to "steal" NIN if multiple users have the NIN verified # Ask am to sync user to central db try: current_app.logger.info('Request sync for user {!s}'.format(user)) result = current_app.am_relay.request_user_sync(user) current_app.logger.info('Sync result for user {!s}: {!s}'.format( user, result)) except Exception as e: current_app.logger.error( 'Sync request failed for user {!s}'.format(user)) current_app.logger.error('Exception: {!s}'.format(e)) # TODO: Need to able to retry this return make_response('OK', 200) # TODO: Remove saving of proof # Save proof for demo purposes proof_data = { 'eduPersonPrincipalName': proofing_state.eppn, 'authn_resp': authn_resp.to_dict(), 'token_resp': token_resp.to_dict(), 'userinfo': userinfo.to_dict() } current_app.proofdb.save(Proof(data=proof_data)) # Remove users proofing state current_app.proofing_statedb.remove_state(proofing_state) return make_response('OK', 200)
def proofing(user, nin): current_app.logger.debug('Getting state for user {!s}.'.format(user)) # TODO: Check if a user has a valid letter proofing # For now a user can just have one verified NIN if len(user.nins.to_list()) > 0: return {'_status': 'error', 'error': 'User is already verified'} proofing_state = current_app.proofing_statedb.get_state_by_eppn( user.eppn, raise_on_missing=False) if not proofing_state: current_app.logger.debug( 'No proofing state found for user {!s}. Initializing new proofing flow.' .format(user)) state = get_unique_hash() nonce = get_unique_hash() token = get_unique_hash() nin = Nin(number=nin, application='eduid_oidc_proofing', verified=False, primary=False) proofing_state = OidcProofingState({ 'eduPersonPrincipalName': user.eppn, 'nin': nin.to_dict(), 'state': state, 'nonce': nonce, 'token': token }) # Initiate proofing oidc_args = { 'client_id': current_app.oidc_client.client_id, 'response_type': 'code', 'scope': ['openid'], 'redirect_uri': url_for('oidc_proofing.authorization_response', _external=True), 'state': state, 'nonce': nonce, 'claims': ClaimsRequest(userinfo=Claims(identity=None)).to_json() } current_app.logger.debug('AuthenticationRequest args:') current_app.logger.debug(oidc_args) try: response = requests.post( current_app.oidc_client.authorization_endpoint, data=oidc_args) except requests.exceptions.ConnectionError as e: msg = 'No connection to authorization endpoint: {!s}'.format(e) current_app.logger.error(msg) return {'_status': 'error', 'error': msg} # If authentication request went well save user state if response.status_code == 200: current_app.logger.debug( 'Authentication request delivered to provider {!s}'.format( current_app.config['PROVIDER_CONFIGURATION_INFO'] ['issuer'])) current_app.proofing_statedb.save(proofing_state) current_app.logger.debug( 'Proofing state {!s} for user {!s} saved'.format( proofing_state.state, user)) else: current_app.logger.error( 'Bad response from OP: {!s} {!s} {!s}'.format( response.status_code, response.reason, response.content)) return { '_status': 'error', 'error': 'Temporary technical problems' } # Return nonce and nonce as qr code current_app.logger.debug('Returning nonce for user {!s}'.format(user)) buf = StringIO() # The "1" below denotes the version of the data exchanged, right now only version 1 is supported. qr_code = '1' + json.dumps({ 'nonce': proofing_state.nonce, 'token': proofing_state.token }) qrcode.make(qr_code).save(buf) qr_b64 = buf.getvalue().encode('base64') return { 'qr_code': qr_code, 'qr_img': 'data:image/png;base64, {!s}'.format(qr_b64), }
def verify_code(user, verification_code): user = ProofingUser(data=user.to_dict()) current_app.logger.info('Verifying code for user {!r}'.format(user)) proofing_state = current_app.proofing_statedb.get_state_by_eppn( user.eppn, raise_on_missing=False) if not proofing_state: return {'_status': 'error', 'message': 'No proofing state found'} # Check if provided code matches the one in the letter if not verification_code == proofing_state.nin.verification_code: current_app.logger.error( 'Verification code for user {!r} does not match'.format(user)) # TODO: Throttling to discourage an adversary to try brute force return {'_status': 'error', 'message': 'Wrong code'} # Update proofing state to use to create nin element proofing_state.nin.is_verified = True proofing_state.nin.verified_by = 'eduid-idproofing-letter' proofing_state.nin.verified_ts = True nin = Nin(data=proofing_state.nin.to_dict()) # Save user to private db if user.nins.primary is None: # No primary NIN found, make the only verified NIN primary nin.is_primary = True user.nins.add(nin) # XXX: Do not add letter_proofing_data after we update to new db models in central user db letter_proofing_data = proofing_state.nin.to_dict() letter_proofing_data[ 'official_address'] = proofing_state.proofing_letter.address letter_proofing_data[ 'transaction_id'] = proofing_state.proofing_letter.transaction_id user.add_letter_proofing_data(letter_proofing_data) # User from central db is as up to date as it can be no need to check for modified time user.modified_ts = True current_app.proofing_userdb.save(user, check_sync=False) # TODO: Need to decide where to "steal" NIN if multiple users have the NIN verified # Ask am to sync user to central db try: # XXX: Send proofing data to some kind of proofing log current_app.logger.info('Request sync for user {!s}'.format(user)) result = current_app.am_relay.request_user_sync(user) current_app.logger.info('Sync result for user {!s}: {!s}'.format( user, result)) except Exception as e: current_app.logger.error( 'Sync request failed for user {!s}'.format(user)) current_app.logger.error('Exception: {!s}'.format(e)) # XXX: Probably not str(e) as message? return {'_status': 'error', 'message': 'Sync request failed for user'} # XXX: Remove dumping data to log current_app.logger.info('Logging data for user: {!r}'.format(user)) current_app.logger.info( json.dumps( schemas.LetterProofingDataSchema().dump(letter_proofing_data))) current_app.logger.info('End data') current_app.logger.info('Verified code for user {!r}'.format(user)) # Remove proofing state current_app.proofing_statedb.remove_document( {'eduPersonPrincipalName': proofing_state.eppn}) return {'success': True}