def test_remove_orcid(self): for context in self.plugin_contexts: proofing_user = ProofingUser(data=self.user_data) proofing_user.orcid = None context.private_db.save(proofing_user) self.assertDictEqual( attribute_fetcher(context, proofing_user.user_id), { '$unset': { 'orcid': None } } )
def post_email(user, email, verified, primary): proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to save unconfirmed email {!r} ' 'for user {}'.format(email, proofing_user)) new_mail = MailAddress.from_dict( dict(email=email, created_by='email', verified=False, primary=False)) try: proofing_user.mail_addresses.add(new_mail) except DuplicateElementViolation: return error_response(message=EmailMsg.dupe) try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Couldnt save email {} for user {}, ' 'data out of sync'.format( email, proofing_user)) return error_response(message=CommonMsg.out_of_sync) current_app.logger.info('Saved unconfirmed email {!r} ' 'for user {}'.format(email, proofing_user)) current_app.stats.count(name='email_save_unconfirmed_email', value=1) sent = send_verification_code(email, proofing_user) emails = {'emails': proofing_user.mail_addresses.to_list_of_dicts()} email_list = EmailListPayload().dump(emails) if not sent: return success_response(payload=email_list, message=EmailMsg.added_and_throttled) current_app.stats.count(name='email_send_verification_code', value=1) return success_response(payload=email_list, message=EmailMsg.saved)
def post_phone(user, number, verified, primary): """ view to add a new phone to the user data of the currently logged in user. Returns a listing of all phones for the logged in user. """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to save unconfirmed phone number {!r} ' 'for user {}'.format(number, proofing_user)) new_phone = PhoneNumber.from_dict( dict(number=number, created_by='phone', verified=False, primary=False)) proofing_user.phone_numbers.add(new_phone) try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Couldnt save phone number {!r} for user {}, ' 'data out of sync'.format( number, proofing_user)) return error_response(message=CommonMsg.out_of_sync) current_app.logger.info('Saved unconfirmed phone number {!r} ' 'for user {}'.format(number, proofing_user)) current_app.stats.count(name='mobile_save_unconfirmed_mobile', value=1) send_verification_code(proofing_user, number) current_app.stats.count(name='mobile_send_verification_code', value=1) phones = {'phones': proofing_user.phone_numbers.to_list_of_dicts()} return success_response(payload=phones, message=PhoneMsg.save_success)
def post_email(user, email, verified, primary): proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to save unconfirmed email {!r} ' 'for user {}'.format(email, proofing_user)) new_mail = MailAddress(email=email, application='email', verified=False, primary=False) try: proofing_user.mail_addresses.add(new_mail) except DuplicateElementViolation: return {'_status': 'error', 'message': 'emails.duplicated'} try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Couldnt save email {} for user {}, ' 'data out of sync'.format( email, proofing_user)) return {'_status': 'error', 'message': 'user-out-of-sync'} current_app.logger.info('Saved unconfirmed email {!r} ' 'for user {}'.format(email, proofing_user)) current_app.stats.count(name='email_save_unconfirmed_email', value=1) send_verification_code(email, proofing_user) current_app.stats.count(name='email_send_verification_code', value=1) emails = { 'emails': proofing_user.mail_addresses.to_list_of_dicts(), 'message': 'emails.save-success' } return EmailListPayload().dump(emails).data
def test_remove_orcid(self, mock_request_user_sync): mock_request_user_sync.side_effect = self.request_user_sync user = self.app.central_userdb.get_user_by_eppn(self.test_user_eppn) proofing_user = ProofingUser.from_user(user, self.app.private_userdb) proofing_user.orcid = self.orcid_element self.request_user_sync(proofing_user) with self.session_cookie(self.browser, self.test_user_eppn) as browser: response = browser.get('/') self.assertEqual(response.status_code, 200) response = json.loads(response.data) self.assertEqual(response['type'], 'GET_ORCID_SUCCESS') csrf_token = response['payload']['csrf_token'] with self.session_cookie(self.browser, self.test_user_eppn) as browser: response = browser.post('/remove', data=json.dumps({'csrf_token': csrf_token}), content_type=self.content_type_json) self.assertEqual(response.status_code, 200) response = json.loads(response.data) self.assertEqual(response['type'], 'POST_ORCID_REMOVE_SUCCESS') user = self.app.central_userdb.get_user_by_eppn(self.test_user_eppn) self.assertEqual(user.orcid, None)
def remove_orcid(user): current_app.logger.info('Removing ORCID data for user') proofing_user = ProofingUser.from_user(user, current_app.private_userdb) proofing_user.orcid = None save_and_sync_user(proofing_user) current_app.logger.info('ORCID data removed for user') return proofing_user.to_dict()
def verify(user, code, number): """ view to mark one of the (unverified) phone numbers of the logged in user as verified. Returns a listing of all phones for the logged in user. """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to save phone number {} as verified'.format(number, proofing_user)) db = current_app.proofing_statedb try: state = db.get_state_by_eppn_and_mobile(proofing_user.eppn, number) timeout = current_app.config['PHONE_VERIFICATION_TIMEOUT'] if state.is_expired(timeout): current_app.logger.info("Proofing state is expired. Removing the state.") current_app.logger.debug("Proofing state: {!r}".format(state)) current_app.proofing_statedb.remove_state(state) return { '_status': 'error', 'message': 'phones.code_invalid_or_expired' } except DocumentDoesNotExist: current_app.logger.info("Could not find proofing state for number {}".format(number)) return { '_status': 'error', 'message': 'phones.unknown_phone' } if code == state.verification.verification_code: try: verify_phone_number(state, proofing_user) current_app.logger.info('Phone number successfully verified') current_app.logger.debug('Phone number: {}'.format(number)) phones = { 'phones': proofing_user.phone_numbers.to_list_of_dicts(), 'message': 'phones.verification-success' } return PhoneListPayload().dump(phones).data except UserOutOfSync: current_app.logger.info('Could not confirm phone number, data out of sync') current_app.logger.debug('Phone number: {}'.format(number)) return { '_status': 'error', 'message': 'user-out-of-sync' } current_app.logger.info("Invalid verification code") current_app.logger.debug("Proofing state: {!r}".format(state)) return { '_status': 'error', 'message': 'phones.code_invalid_or_expired' }
def post_remove(user, email): proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to remove email address {!r} ' 'from user {}'.format(email, proofing_user)) emails = proofing_user.mail_addresses.to_list() verified_emails = proofing_user.mail_addresses.verified.to_list() # Do not let the user remove all mail addresses if len(emails) == 1: current_app.logger.debug('Cannot remove the last address: {}'.format(email)) return { '_status': 'error', 'message': 'emails.cannot_remove_unique' } # Do not let the user remove all verified mail addresses if len(verified_emails) == 1 and verified_emails[0].email == email: current_app.logger.debug('Cannot remove last verified address: {}'.format(email)) return { '_status': 'error', 'message': 'emails.cannot_remove_unique_verified' } try: proofing_user.mail_addresses.remove(email) except PrimaryElementViolation: # Trying to remove the primary mail address, set next verified mail address as primary other_verified = [address for address in verified_emails if address.email != email] proofing_user.mail_addresses.primary = other_verified[0].email # Now remove the unwanted and previous primary mail address proofing_user.mail_addresses.remove(email) try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Could not remove email {} for user, data out of sync'.format(email)) return { '_status': 'error', 'message': 'user-out-of-sync' } current_app.logger.info('Email address {} removed'.format(email)) current_app.stats.count(name='email_remove_success', value=1) emails = { 'emails': proofing_user.mail_addresses.to_list_of_dicts(), 'message': 'emails.removal-success' } return EmailListPayload().dump(emails).data
def setUp(self): am_settings = { 'WANT_MONGO_URI': True } super(AttributeFetcherPhoneProofingTests, self).setUp(init_am=True, am_settings=am_settings) self.user_data = deepcopy(USER_DATA) self.plugin_contexts = [ phone_plugin_init(self.am_settings), ] for userdoc in self.amdb._get_all_docs(): proofing_user = ProofingUser(data=userdoc) for context in self.plugin_contexts: context.private_db.save(proofing_user, check_sync=False) self.maxDiff = None
def test_get_orcid(self): user = self.app.central_userdb.get_user_by_eppn(self.test_user_eppn) proofing_user = ProofingUser.from_user(user, self.app.private_userdb) proofing_user.orcid = self.orcid_element self.request_user_sync(proofing_user) with self.session_cookie(self.browser, self.test_user_eppn) as browser: response = browser.get('/') self.assertEqual(response.status_code, 200) response = json.loads(response.data) self.assertEqual(response['type'], 'GET_ORCID_SUCCESS') self.assertEqual(response['payload']['orcid']['id'], self.orcid_element.id) self.assertEqual(response['payload']['orcid']['name'], self.orcid_element.name) self.assertEqual(response['payload']['orcid']['given_name'], self.orcid_element.given_name) self.assertEqual(response['payload']['orcid']['family_name'], self.orcid_element.family_name)
def verify(user, code, email): """ """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to save email address {} as verified'.format(email)) db = current_app.proofing_statedb try: state = db.get_state_by_eppn_and_email(proofing_user.eppn, email) timeout = current_app.config['EMAIL_VERIFICATION_TIMEOUT'] if state.is_expired(timeout): current_app.logger.info("Verification code is expired. Removing the state") current_app.logger.debug("Proofing state: {}".format(state)) current_app.proofing_statedb.remove_state(state) return { '_status': 'error', 'message': 'emails.code_invalid_or_expired' } except DocumentDoesNotExist: current_app.logger.info('Could not find proofing state for email {}'.format(email)) return { '_status': 'error', 'message': 'emails.unknown_email' } if code == state.verification.verification_code: try: verify_mail_address(state, proofing_user) current_app.logger.info('Email successfully verified') current_app.logger.debug('Email address: {}'.format(email)) emails = { 'emails': proofing_user.mail_addresses.to_list_of_dicts(), 'message': 'emails.verification-success' } return EmailListPayload().dump(emails).data except UserOutOfSync: current_app.logger.info('Could not confirm email, data out of sync') current_app.logger.debug('Mail address: {}'.format(email)) return { '_status': 'error', 'message': 'user-out-of-sync' } current_app.logger.info("Invalid verification code") current_app.logger.debug("Email address: {}".format(state.verification.email)) return { '_status': 'error', 'message': 'emails.code_invalid_or_expired' }
def verify(user, code, number): """ view to mark one of the (unverified) phone numbers of the logged in user as verified. Returns a listing of all phones for the logged in user. """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug( 'Trying to save phone number {} as verified'.format( number, proofing_user)) db = current_app.proofing_statedb try: state = db.get_state_by_eppn_and_mobile(proofing_user.eppn, number) timeout = current_app.config['PHONE_VERIFICATION_TIMEOUT'] if state.is_expired(timeout): current_app.logger.info( "Proofing state is expired. Removing the state.") current_app.logger.debug("Proofing state: {!r}".format(state)) current_app.proofing_statedb.remove_state(state) return { '_status': 'error', 'message': 'phones.code_invalid_or_expired' } except DocumentDoesNotExist: current_app.logger.info( "Could not find proofing state for number {}".format(number)) return {'_status': 'error', 'message': 'phones.unknown_phone'} if code == state.verification.verification_code: try: verify_phone_number(state, proofing_user) current_app.logger.info('Phone number successfully verified') current_app.logger.debug('Phone number: {}'.format(number)) phones = { 'phones': proofing_user.phone_numbers.to_list_of_dicts(), 'message': 'phones.verification-success' } return PhoneListPayload().dump(phones).data except UserOutOfSync: current_app.logger.info( 'Could not confirm phone number, data out of sync') current_app.logger.debug('Phone number: {}'.format(number)) return {'_status': 'error', 'message': 'user-out-of-sync'} current_app.logger.info("Invalid verification code") current_app.logger.debug("Proofing state: {!r}".format(state)) return {'_status': 'error', 'message': 'phones.code_invalid_or_expired'}
def verify_link(user): """ Used for verifying an e-mail address when the user clicks the link in the verification mail. """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) code = request.args.get('code') email = request.args.get('email') if code and email: current_app.logger.debug( 'Trying to save email address {} as verified for user {}'.format( email, proofing_user)) redirect_url = current_app.config.email_verify_redirect_url try: state = current_app.proofing_statedb.get_state_by_eppn_and_email( proofing_user.eppn, email) timeout = current_app.config.email_verification_timeout if state.is_expired(timeout): current_app.logger.info( "Verification code is expired. Removing the state") current_app.logger.debug("Proofing state: {}".format(state)) current_app.proofing_statedb.remove_state(state) return redirect_with_msg(redirect_url, EmailMsg.invalid_code) except DocumentDoesNotExist: current_app.logger.info( 'Could not find proofing state for email {}'.format(email)) return redirect_with_msg(redirect_url, EmailMsg.unknown_email) if code == state.verification.verification_code: try: verify_mail_address(state, proofing_user) current_app.logger.info('Email successfully verified') current_app.logger.debug('Email address: {}'.format(email)) return redirect_with_msg(redirect_url, EmailMsg.verify_success, error=False) except UserOutOfSync: current_app.logger.info( 'Could not confirm email, data out of sync') current_app.logger.debug('Mail address: {}'.format(email)) return redirect_with_msg(redirect_url, CommonMsg.out_of_sync) current_app.logger.info("Invalid verification code") current_app.logger.debug("Email address: {}".format( state.verification.email)) return redirect_with_msg(redirect_url, EmailMsg.invalid_code) abort(400)
def verify_link(user): """ Used for verifying an e-mail address when the user clicks the link in the verification mail. """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) code = request.args.get('code') email = request.args.get('email') if code and email: current_app.logger.debug('Trying to save email address {} as verified for user {}'.format(email, proofing_user)) url = urlappend(current_app.config['DASHBOARD_URL'], 'emails') scheme, netloc, path, query_string, fragment = urlsplit(url) try: state = current_app.proofing_statedb.get_state_by_eppn_and_email(proofing_user.eppn, email) timeout = current_app.config.get('EMAIL_VERIFICATION_TIMEOUT', 24) if state.is_expired(timeout): current_app.logger.info("Verification code is expired. Removing the state") current_app.logger.debug("Proofing state: {}".format(state)) current_app.proofing_statedb.remove_state(state) new_query_string = urlencode({'msg': ':ERROR:emails.code_invalid_or_expired'}) url = urlunsplit((scheme, netloc, path, new_query_string, fragment)) return redirect(url) except DocumentDoesNotExist: current_app.logger.info('Could not find proofing state for email {}'.format(email)) new_query_string = urlencode({'msg': ':ERROR:emails.unknown_email'}) url = urlunsplit((scheme, netloc, path, new_query_string, fragment)) return redirect(url) if code == state.verification.verification_code: try: verify_mail_address(state, proofing_user) current_app.logger.info('Email successfully verified') current_app.logger.debug('Email address: {}'.format(email)) new_query_string = urlencode({'msg': 'emails.verification-success'}) url = urlunsplit((scheme, netloc, path, new_query_string, fragment)) return redirect(url) except UserOutOfSync: current_app.logger.info('Could not confirm email, data out of sync') current_app.logger.debug('Mail address: {}'.format(email)) new_query_string = urlencode({'msg': ':ERROR:user-out-of-sync'}) url = urlunsplit((scheme, netloc, path, new_query_string, fragment)) return redirect(url) current_app.logger.info("Invalid verification code") current_app.logger.debug("Email address: {}".format(state.verification.email)) new_query_string = urlencode({'msg': ':ERROR:emails.code_invalid_or_expired'}) url = urlunsplit((scheme, netloc, path, new_query_string, fragment)) return redirect(url) abort(400)
def post_remove(user, email): proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to remove email address {!r} ' 'from user {}'.format(email, proofing_user)) emails = proofing_user.mail_addresses.to_list() verified_emails = proofing_user.mail_addresses.verified.to_list() # Do not let the user remove all mail addresses if len(emails) == 1: current_app.logger.debug( 'Cannot remove the last address: {}'.format(email)) return error_response(message=EmailMsg.cannot_remove_last) # Do not let the user remove all verified mail addresses if len(verified_emails) == 1 and verified_emails[0].email == email: current_app.logger.debug( 'Cannot remove last verified address: {}'.format(email)) return error_response(message=EmailMsg.cannot_remove_last_verified) try: proofing_user.mail_addresses.remove(email) except PrimaryElementViolation: # Trying to remove the primary mail address, set next verified mail address as primary other_verified = [ address for address in verified_emails if address.email != email ] proofing_user.mail_addresses.primary = other_verified[0].email # Now remove the unwanted and previous primary mail address proofing_user.mail_addresses.remove(email) try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug( 'Could not remove email {} for user, data out of sync'.format( email)) return error_response(message=CommonMsg.out_of_sync) current_app.logger.info('Email address {} removed'.format(email)) current_app.stats.count(name='email_remove_success', value=1) emails = {'emails': proofing_user.mail_addresses.to_list_of_dicts()} email_list = EmailListPayload().dump(emails) return success_response(payload=email_list, message=EmailMsg.removal_success)
def test_fillup_attributes(self): for context in self.plugin_contexts: proofing_user = ProofingUser(data=self.user_data) context.private_db.save(proofing_user) self.assertDictEqual( attribute_fetcher(context, proofing_user.user_id), { '$set': { 'phone': [{ 'verified': True, 'number': '+46700011336', 'primary': True }], }, } )
def test_fillup_attributes(self): for context in self.plugin_contexts: proofing_user = ProofingUser(data=self.user_data) context.private_db.save(proofing_user) self.assertDictEqual( attribute_fetcher(context, proofing_user.user_id), { '$set': { "givenName": u"Testaren", "surname": u"Testsson", "displayName": u"John", 'nins': [{'number': '123456781235', 'verified': True, 'primary': True}], }, } )
def post_primary(user, number): """ view to mark one of the (verified) phone numbers of the logged in user as the primary phone number. Returns a listing of all phones for the logged in user. """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to save phone number {!r} as primary ' 'for user {}'.format(number, proofing_user)) try: phone_element = proofing_user.phone_numbers.find(number) except IndexError: current_app.logger.debug( 'Couldnt save phone number {!r} as primary for user' ' {}, data out of sync'.format(number, proofing_user)) return {'_status': 'error', 'message': 'user-out-of-sync'} if not phone_element.is_verified: current_app.logger.debug( 'Couldnt save phone number {!r} as primary for user' ' {}, phone number unconfirmed'.format(number, proofing_user)) return { '_status': 'error', 'message': 'phones.unconfirmed_number_not_primary' } proofing_user.phone_numbers.primary = phone_element.number try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug( 'Couldnt save phone number {!r} as primary for user' ' {}, data out of sync'.format(number, proofing_user)) return {'_status': 'error', 'message': 'user-out-of-sync'} current_app.logger.info('Phone number {!r} made primary ' 'for user {}'.format(number, proofing_user)) current_app.stats.count(name='mobile_set_primary', value=1) phones = { 'phones': proofing_user.phone_numbers.to_list_of_dicts(), 'message': 'phones.primary-success' } return PhoneListPayload().dump(phones).data
def post_remove(user, number): """ view to remove one of the phone numbers of the logged in user. Returns a listing of all phones for the logged in user. """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to remove phone number {!r} ' 'from user {}'.format(number, proofing_user)) try: proofing_user.phone_numbers.remove(number) except PrimaryElementViolation: current_app.logger.info('Removing primary phone number') current_app.logger.debug('Phone number: {}.'.format(number)) verified = proofing_user.phone_numbers.verified.to_list() new_index = 1 if verified[0].number == number else 0 proofing_user.phone_numbers.primary = verified[new_index].number proofing_user.phone_numbers.remove(number) except UserDBValueError: current_app.logger.info('Tried to remove a non existing phone number') current_app.logger.debug('Phone number: {}.'.format(number)) return { '_status': 'error', 'message': 'phones.unknown_phone' } try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Couldnt remove phone number {!r} for user' ' {}, data out of sync'.format(number, proofing_user)) return { '_status': 'error', 'message': 'user-out-of-sync' } current_app.logger.info('Phone number {!r} removed ' 'for user {}'.format(number, proofing_user)) current_app.stats.count(name='mobile_remove_success', value=1) phones = { 'phones': proofing_user.phone_numbers.to_list_of_dicts(), 'message': 'phones.removal-success' } return PhoneListPayload().dump(phones).data
def test_existing_user(self): for context in self.plugin_contexts: proofing_user = ProofingUser(data=self.user_data) context.private_db.save(proofing_user) self.assertDictEqual( attribute_fetcher(context, proofing_user.user_id), { '$set': { 'mailAliases': [{ 'email': '*****@*****.**', 'verified': True, 'primary': True, }], }, } )
def verify(user, code, email): """ """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug( 'Trying to save email address {} as verified'.format(email)) db = current_app.proofing_statedb try: state = db.get_state_by_eppn_and_email(proofing_user.eppn, email) timeout = current_app.config['EMAIL_VERIFICATION_TIMEOUT'] if state.is_expired(timeout): current_app.logger.info( "Verification code is expired. Removing the state") current_app.logger.debug("Proofing state: {}".format(state)) current_app.proofing_statedb.remove_state(state) return { '_status': 'error', 'message': 'emails.code_invalid_or_expired' } except DocumentDoesNotExist: current_app.logger.info( 'Could not find proofing state for email {}'.format(email)) return {'_status': 'error', 'message': 'emails.unknown_email'} if code == state.verification.verification_code: try: verify_mail_address(state, proofing_user) current_app.logger.info('Email successfully verified') current_app.logger.debug('Email address: {}'.format(email)) emails = { 'emails': proofing_user.mail_addresses.to_list_of_dicts(), 'message': 'emails.verification-success' } return EmailListPayload().dump(emails).data except UserOutOfSync: current_app.logger.info( 'Could not confirm email, data out of sync') current_app.logger.debug('Mail address: {}'.format(email)) return {'_status': 'error', 'message': 'user-out-of-sync'} current_app.logger.info("Invalid verification code") current_app.logger.debug("Email address: {}".format( state.verification.email)) return {'_status': 'error', 'message': 'emails.code_invalid_or_expired'}
def verify(user, code, number): """ view to mark one of the (unverified) phone numbers of the logged in user as verified. Returns a listing of all phones for the logged in user. """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug( 'Trying to save phone number {} as verified'.format(number)) db = current_app.proofing_statedb try: state = db.get_state_by_eppn_and_mobile(proofing_user.eppn, number) timeout = current_app.config.phone_verification_timeout if state.is_expired(timeout): current_app.logger.info( "Proofing state is expired. Removing the state.") current_app.logger.debug("Proofing state: {!r}".format(state)) current_app.proofing_statedb.remove_state(state) return error_response(message=PhoneMsg.code_invalid) except DocumentDoesNotExist: current_app.logger.info( "Could not find proofing state for number {}".format(number)) return error_response(message=PhoneMsg.unknown_phone) if code == state.verification.verification_code: try: verify_phone_number(state, proofing_user) current_app.logger.info('Phone number successfully verified') current_app.logger.debug('Phone number: {}'.format(number)) phones = { 'phones': proofing_user.phone_numbers.to_list_of_dicts(), } return success_response(payload=phones, message=PhoneMsg.verify_success) except UserOutOfSync: current_app.logger.info( 'Could not confirm phone number, data out of sync') current_app.logger.debug('Phone number: {}'.format(number)) return error_response(message=CommonMsg.out_of_sync) current_app.logger.info("Invalid verification code") current_app.logger.debug("Proofing state: {!r}".format(state)) return error_response(message=PhoneMsg.code_invalid)
def verify(user, code, email): """ """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug( 'Trying to save email address {} as verified'.format(email)) db = current_app.proofing_statedb try: state = db.get_state_by_eppn_and_email(proofing_user.eppn, email) timeout = current_app.config.email_verification_timeout if state.is_expired(timeout): current_app.logger.info( "Verification code is expired. Removing the state") current_app.logger.debug("Proofing state: {}".format(state)) current_app.proofing_statedb.remove_state(state) return error_response(message=EmailMsg.invalid_code) except DocumentDoesNotExist: current_app.logger.info( 'Could not find proofing state for email {}'.format(email)) return error_response(message=EmailMsg.unknown_email) if code == state.verification.verification_code: try: verify_mail_address(state, proofing_user) current_app.logger.info('Email successfully verified') current_app.logger.debug('Email address: {}'.format(email)) emails = { 'emails': proofing_user.mail_addresses.to_list_of_dicts(), } email_list = EmailListPayload().dump(emails) return success_response(payload=email_list, message=EmailMsg.verify_success) except UserOutOfSync: current_app.logger.info( 'Could not confirm email, data out of sync') current_app.logger.debug('Mail address: {}'.format(email)) return error_response(message=CommonMsg.out_of_sync) current_app.logger.info("Invalid verification code") current_app.logger.debug("Email address: {}".format( state.verification.email)) return error_response(message=EmailMsg.invalid_code)
def post_primary(user, number): """ view to mark one of the (verified) phone numbers of the logged in user as the primary phone number. Returns a listing of all phones for the logged in user. """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to save phone number {} as primary'.format(number)) phone_element = proofing_user.phone_numbers.find(number) if not phone_element: current_app.logger.debug('Could not save phone number {} as primary, data out of sync'.format(number)) return { '_status': 'error', 'message': 'user-out-of-sync' } if not phone_element.is_verified: current_app.logger.debug('Could not save phone number {} as primary, phone number unconfirmed'.format(number)) return { '_status': 'error', 'message': 'phones.unconfirmed_number_not_primary' } proofing_user.phone_numbers.primary = phone_element.number try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Could not save phone number {} as primary, data out of sync'.format(number)) return { '_status': 'error', 'message': 'user-out-of-sync' } current_app.logger.info('Phone number {} made primary'.format(number)) current_app.stats.count(name='mobile_set_primary', value=1) phones = { 'phones': proofing_user.phone_numbers.to_list_of_dicts(), 'message': 'phones.primary-success' } return PhoneListPayload().dump(phones).data
def post_primary(user, email): proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to save email address {!r} as primary ' 'for user {}'.format(email, proofing_user)) try: mail = proofing_user.mail_addresses.find(email) except IndexError: current_app.logger.debug('Couldnt save email {!r} as primary for user' ' {}, data out of sync'.format(email, proofing_user)) return { '_status': 'error', 'message': 'user-out-of-sync' } if not mail.is_verified: current_app.logger.debug('Couldnt save email {!r} as primary for user' ' {}, email unconfirmed'.format(email, proofing_user)) return { '_status': 'error', 'message': 'emails.unconfirmed_address_not_primary' } proofing_user.mail_addresses.primary = mail.email try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Couldnt save email {!r} as primary for user' ' {}, data out of sync'.format(email, proofing_user)) return { '_status': 'error', 'message': 'user-out-of-sync' } current_app.logger.info('Email address {!r} made primary ' 'for user {}'.format(email, proofing_user)) current_app.stats.count(name='email_set_primary', value=1) emails = { 'emails': proofing_user.mail_addresses.to_list_of_dicts(), 'message': 'emails.primary-success' } return EmailListPayload().dump(emails).data
def convert_and_remove_norEduPersonNIN(self): self.user_data.update({'norEduPersonNIN': '123456781235'}) del self.user_data['nins'] for context in self.plugin_contexts: proofing_user = ProofingUser(data=self.user_data) context.private_db.save(proofing_user) actual_update = attribute_fetcher(context, proofing_user.user_id) expected_update = { '$set': { 'nins': [{'number': '123456781235', 'verified': True, 'primary': True}], }, '$unset': { 'norEduPersonNIN': None } } self.assertDictEqual( actual_update, expected_update )
def post_remove(user, number): """ view to remove one of the phone numbers of the logged in user. Returns a listing of all phones for the logged in user. """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to remove phone number {!r} ' 'from user {}'.format(number, proofing_user)) try: proofing_user.phone_numbers.remove(number) except PrimaryElementViolation: current_app.logger.info('Removing primary phone number') current_app.logger.debug('Phone number: {}.'.format(number)) verified = proofing_user.phone_numbers.verified.to_list() new_index = 1 if verified[0].number == number else 0 proofing_user.phone_numbers.primary = verified[new_index].number proofing_user.phone_numbers.remove(number) except UserDBValueError: current_app.logger.info('Tried to remove a non existing phone number') current_app.logger.debug('Phone number: {}.'.format(number)) return {'_status': 'error', 'message': 'phones.unknown_phone'} try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Couldnt remove phone number {!r} for user' ' {}, data out of sync'.format( number, proofing_user)) return {'_status': 'error', 'message': 'user-out-of-sync'} current_app.logger.info('Phone number {!r} removed ' 'for user {}'.format(number, proofing_user)) current_app.stats.count(name='mobile_remove_success', value=1) phones = { 'phones': proofing_user.phone_numbers.to_list_of_dicts(), 'message': 'phones.removal-success' } return PhoneListPayload().dump(phones).data
def test_fillup_attributes(self): self.user_data = { 'givenName': 'Testaren', 'surname': 'Testsson', 'preferredLanguage': 'sv', 'eduPersonPrincipalName': 'test-test', 'displayName': 'John', 'mailAliases': [{ 'email': '*****@*****.**', 'verified': True, 'primary': True }], 'mobile': [{ 'verified': True, 'mobile': '+46700011336', 'primary': True }], 'passwords': [{ 'id': bson.ObjectId('112345678901234567890123'), 'salt': '$NDNv1H1$9c810d852430b62a9a7c6159d5d64c41c3831846f81b6799b54e1e8922f11545$32$32$', }], 'nins': [{'number': '123456781235', 'verified': True, 'primary': True}], } for context in self.plugin_contexts: proofing_user = ProofingUser(data=self.user_data) context.private_db.save(proofing_user) self.assertDictEqual( attribute_fetcher(context, proofing_user.user_id), { '$set': { 'mailAliases': [{ 'email': '*****@*****.**', 'verified': True, 'primary': True }], }, } )
def test_existing_user(self): for context in self.plugin_contexts: proofing_user = ProofingUser(data=self.user_data) context.private_db.save(proofing_user) self.assertDictEqual( attribute_fetcher(context, proofing_user.user_id), { '$set': { 'orcid': { 'oidc_authz': { 'token_type': 'bearer', 'refresh_token': 'a_refresh_token', 'access_token': 'an_access_token', 'id_token': { 'nonce': 'a_nonce', 'sub': 'sub_id', 'iss': 'https://issuer.example.org', 'created_by': 'orcid', 'exp': 1526890816, 'auth_time': 1526890214, 'iat': 1526890216, 'aud': [ 'APP-YIAD0N1L4B3Z3W9Q' ] }, 'expires_in': 631138518, 'created_by': 'orcid' }, 'given_name': 'Testaren', 'family_name': 'Testsson', 'name': None, 'verified': True, 'id': 'orcid_unique_id', 'created_by': 'orcid' } }, } )
def post_primary(user, email): proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to save email address {!r} as primary ' 'for user {}'.format(email, proofing_user)) try: mail = proofing_user.mail_addresses.find(email) except IndexError: current_app.logger.debug('Couldnt save email {!r} as primary for user' ' {}, data out of sync'.format( email, proofing_user)) return {'_status': 'error', 'message': 'user-out-of-sync'} if not mail.is_verified: current_app.logger.debug('Couldnt save email {!r} as primary for user' ' {}, email unconfirmed'.format( email, proofing_user)) return { '_status': 'error', 'message': 'emails.unconfirmed_address_not_primary' } proofing_user.mail_addresses.primary = mail.email try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Couldnt save email {!r} as primary for user' ' {}, data out of sync'.format( email, proofing_user)) return {'_status': 'error', 'message': 'user-out-of-sync'} current_app.logger.info('Email address {!r} made primary ' 'for user {}'.format(email, proofing_user)) current_app.stats.count(name='email_set_primary', value=1) emails = { 'emails': proofing_user.mail_addresses.to_list_of_dicts(), 'message': 'emails.primary-success' } return EmailListPayload().dump(emails).data
def post_remove(user, email): proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to remove email address {!r} ' 'from user {}'.format(email, proofing_user)) emails = proofing_user.mail_addresses.to_list() if len(emails) == 1: msg = "Cannot remove unique address: {!r}".format(email) current_app.logger.debug(msg) return {'_status': 'error', 'message': 'emails.cannot_remove_unique'} try: proofing_user.mail_addresses.remove(email) except PrimaryElementViolation: new_index = 1 if emails[0].email == email else 0 proofing_user.mail_addresses.primary = emails[new_index].email proofing_user.mail_addresses.remove(email) try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Couldnt remove email {!r} for user' ' {}, data out of sync'.format( email, proofing_user)) return {'_status': 'error', 'message': 'user-out-of-sync'} except PrimaryElementViolation: return {'_status': 'error', 'message': 'emails.cannot_remove_primary'} current_app.logger.info('Email address {!r} removed ' 'for user {}'.format(email, proofing_user)) current_app.stats.count(name='email_remove_success', value=1) emails = { 'emails': proofing_user.mail_addresses.to_list_of_dicts(), 'message': 'emails.removal-success' } return EmailListPayload().dump(emails).data
def test_remove_orcid(self, mock_request_user_sync): mock_request_user_sync.side_effect = self.request_user_sync user = self.app.central_userdb.get_user_by_eppn(self.test_user_eppn) proofing_user = ProofingUser.from_user(user, self.app.private_userdb) proofing_user.orcid = self.orcid_element self.request_user_sync(proofing_user) with self.session_cookie(self.browser, self.test_user_eppn) as browser: response = browser.get('/') self.assertEqual(response.status_code, 200) response = json.loads(response.data) self.assertEqual(response['type'], 'GET_ORCID_SUCCESS') csrf_token = response['payload']['csrf_token'] with self.session_cookie(self.browser, self.test_user_eppn) as browser: response = browser.post('/remove', data={'csrf_token': csrf_token}) self.assertEqual(response.status_code, 200) response = json.loads(response.data) self.assertEqual(response['type'], 'POST_ORCID_REMOVE_SUCCESS') user = self.app.central_userdb.get_user_by_eppn(self.test_user_eppn) self.assertEqual(user.orcid, None)
def post_phone(user, number, verified, primary): """ view to add a new phone to the user data of the currently logged in user. Returns a listing of all phones for the logged in user. """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to save unconfirmed phone number {!r} ' 'for user {}'.format(number, proofing_user)) new_phone = PhoneNumber(number=number, application='phone', verified=False, primary=False) proofing_user.phone_numbers.add(new_phone) try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Couldnt save phone number {!r} for user {}, ' 'data out of sync'.format( number, proofing_user)) return {'_status': 'error', 'message': 'user-out-of-sync'} current_app.logger.info('Saved unconfirmed phone number {!r} ' 'for user {}'.format(number, proofing_user)) current_app.stats.count(name='mobile_save_unconfirmed_mobile', value=1) send_verification_code(proofing_user, number) current_app.stats.count(name='mobile_send_verification_code', value=1) phones = { 'phones': proofing_user.phone_numbers.to_list_of_dicts(), 'message': 'phones.save-success' } return PhoneListPayload().dump(phones).data
def post_phone(user, number, verified, primary): """ view to add a new phone to the user data of the currently logged in user. Returns a listing of all phones for the logged in user. """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to save unconfirmed phone number {!r} ' 'for user {}'.format(number, proofing_user)) new_phone = PhoneNumber(number=number, application='phone', verified=False, primary=False) proofing_user.phone_numbers.add(new_phone) try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Couldnt save phone number {!r} for user {}, ' 'data out of sync'.format(number, proofing_user)) return { '_status': 'error', 'message': 'user-out-of-sync' } current_app.logger.info('Saved unconfirmed phone number {!r} ' 'for user {}'.format(number, proofing_user)) current_app.stats.count(name='mobile_save_unconfirmed_mobile', value=1) send_verification_code(proofing_user, number) current_app.stats.count(name='mobile_send_verification_code', value=1) phones = { 'phones': proofing_user.phone_numbers.to_list_of_dicts(), 'message': 'phones.save-success' } return PhoneListPayload().dump(phones).data
def post_email(user, email, verified, primary): proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug('Trying to save unconfirmed email {!r} ' 'for user {}'.format(email, proofing_user)) new_mail = MailAddress(email=email, application='email', verified=False, primary=False) try: proofing_user.mail_addresses.add(new_mail) except DuplicateElementViolation: return { '_status': 'error', 'message': 'emails.duplicated' } try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Couldnt save email {} for user {}, ' 'data out of sync'.format(email, proofing_user)) return { '_status': 'error', 'message': 'user-out-of-sync' } current_app.logger.info('Saved unconfirmed email {!r} ' 'for user {}'.format(email, proofing_user)) current_app.stats.count(name='email_save_unconfirmed_email', value=1) send_verification_code(email, proofing_user) current_app.stats.count(name='email_send_verification_code', value=1) emails = { 'emails': proofing_user.mail_addresses.to_list_of_dicts(), 'message': 'emails.save-success' } return EmailListPayload().dump(emails).data
def post_primary(user, number): """ view to mark one of the (verified) phone numbers of the logged in user as the primary phone number. Returns a listing of all phones for the logged in user. """ proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug( 'Trying to save phone number {} as primary'.format(number)) phone_element = proofing_user.phone_numbers.find(number) if not phone_element: current_app.logger.debug( 'Could not save phone number {} as primary, data out of sync'. format(number)) return error_response(message=CommonMsg.out_of_sync) if not phone_element.is_verified: current_app.logger.debug( 'Could not save phone number {} as primary, phone number unconfirmed' .format(number)) return error_response(message=PhoneMsg.unconfirmed_primary) proofing_user.phone_numbers.primary = phone_element.number try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug( 'Could not save phone number {} as primary, data out of sync'. format(number)) return error_response(message=CommonMsg.out_of_sync) current_app.logger.info('Phone number {} made primary'.format(number)) current_app.stats.count(name='mobile_set_primary', value=1) phones = {'phones': proofing_user.phone_numbers.to_list_of_dicts()} return success_response(payload=phones, message=PhoneMsg.primary_success)
def post_primary(user, email): proofing_user = ProofingUser.from_user(user, current_app.private_userdb) current_app.logger.debug( 'Trying to save email address {!r} as primary for user {}'.format( email, proofing_user)) try: mail = proofing_user.mail_addresses.find(email) except IndexError: current_app.logger.debug( 'Couldnt save email {!r} as primary for user {}, data out of sync'. format(email, proofing_user)) return error_response(message=CommonMsg.out_of_sync) if not mail.is_verified: current_app.logger.debug( 'Couldnt save email {!r} as primary for user {}, email unconfirmed' .format(email, proofing_user)) return error_response(message=EmailMsg.unconfirmed_not_primary) proofing_user.mail_addresses.primary = mail.email try: save_and_sync_user(proofing_user) except UserOutOfSync: current_app.logger.debug('Couldnt save email {!r} as primary for user' ' {}, data out of sync'.format( email, proofing_user)) return error_response(message=CommonMsg.out_of_sync) current_app.logger.info('Email address {!r} made primary ' 'for user {}'.format(email, proofing_user)) current_app.stats.count(name='email_set_primary', value=1) emails = {'emails': proofing_user.mail_addresses.to_list_of_dicts()} email_list = EmailListPayload().dump(emails) return success_response(payload=email_list, message=EmailMsg.success_primary)
def authorization_response(user): # Redirect url for user feedback redirect_url = current_app.config.orcid_verify_redirect_url current_app.stats.count(name='authn_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_description') ) ) return redirect_with_msg(redirect_url, OrcidMsg.authz_error) user_oidc_state = authn_resp['state'] proofing_state = current_app.proofing_statedb.get_state_by_oidc_state(user_oidc_state, raise_on_missing=False) if not proofing_state: current_app.logger.error('The \'state\' parameter ({!s}) does not match a user state.'.format(user_oidc_state)) return redirect_with_msg(redirect_url, OrcidMsg.no_state) # do token request args = { 'code': authn_resp['code'], 'redirect_uri': url_for('orcid.authorization_response', _external=True), } current_app.logger.debug('Trying to do token request: {!s}'.format(args)) 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') return redirect_with_msg(redirect_url, OrcidMsg.unknown_nonce) current_app.logger.info('ORCID authorized for user') # do userinfo request current_app.logger.debug('Trying to do userinfo request:') 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 redirect_with_msg(redirect_url, OrcidMsg.sub_mismatch) # Save orcid and oidc data to user current_app.logger.info('Saving ORCID data for user') proofing_user = ProofingUser.from_user(user, current_app.private_userdb) oidc_id_token = OidcIdToken.from_dict( dict( iss=id_token['iss'], sub=id_token['sub'], aud=id_token['aud'], exp=id_token['exp'], iat=id_token['iat'], nonce=id_token['nonce'], auth_time=id_token['auth_time'], created_by='orcid', ) ) oidc_authz = OidcAuthorization.from_dict( dict( access_token=token_resp['access_token'], token_type=token_resp['token_type'], id_token=oidc_id_token, expires_in=token_resp['expires_in'], refresh_token=token_resp['refresh_token'], created_by='orcid', ) ) orcid_element = Orcid.from_dict( dict( id=userinfo['id'], name=userinfo['name'], given_name=userinfo['given_name'], family_name=userinfo['family_name'], verified=True, oidc_authz=oidc_authz, created_by='orcid', ) ) orcid_proofing = OrcidProofing( proofing_user, created_by='orcid', orcid=orcid_element.id, issuer=orcid_element.oidc_authz.id_token.iss, audience=orcid_element.oidc_authz.id_token.aud, proofing_method='oidc', proofing_version='2018v1', ) if current_app.proofing_log.save(orcid_proofing): current_app.logger.info('ORCID proofing data saved to log') proofing_user.orcid = orcid_element save_and_sync_user(proofing_user) current_app.logger.info('ORCID proofing data saved to user') message_args = dict(msg=OrcidMsg.authz_success, error=False) else: current_app.logger.info('ORCID proofing data NOT saved, failed to save proofing log') message_args = dict(msg=CommonMsg.temp_problem) # Clean up current_app.logger.info('Removing proofing state') current_app.proofing_statedb.remove_state(proofing_state) return redirect_with_msg(redirect_url, **message_args)
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(user): # Redirect url for user feedback url = urlappend(current_app.config['DASHBOARD_URL'], 'accountlinking') scheme, netloc, path, query_string, fragment = urlsplit(url) current_app.stats.count(name='authn_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_description'))) new_query_string = urlencode({'msg': ':ERROR:orc.authorization_fail'}) url = urlunsplit((scheme, netloc, path, new_query_string, fragment)) return redirect(url) user_oidc_state = authn_resp['state'] proofing_state = current_app.proofing_statedb.get_state_by_oidc_state(user_oidc_state, raise_on_missing=False) if not proofing_state: current_app.logger.error('The \'state\' parameter ({!s}) does not match a user state.'.format(user_oidc_state)) new_query_string = urlencode({'msg': ':ERROR:orc.unknown_state'}) url = urlunsplit((scheme, netloc, path, new_query_string, fragment)) return redirect(url) # do token request args = { 'code': authn_resp['code'], 'redirect_uri': url_for('orcid.authorization_response', _external=True), } current_app.logger.debug('Trying to do token request: {!s}'.format(args)) 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') new_query_string = urlencode({'msg': ':ERROR:orc.unknown_nonce'}) url = urlunsplit((scheme, netloc, path, new_query_string, fragment)) return redirect(url) current_app.logger.info('ORCID authorized for user') # do userinfo request current_app.logger.debug('Trying to do userinfo request:') 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)) new_query_string = urlencode({'msg': ':ERROR:orc.sub_mismatch'}) url = urlunsplit((scheme, netloc, path, new_query_string, fragment)) return redirect(url) # Save orcid and oidc data to user current_app.logger.info('Saving ORCID data for user') proofing_user = ProofingUser.from_user(user, current_app.private_userdb) oidc_id_token = OidcIdToken(iss=id_token['iss'], sub=id_token['sub'], aud=id_token['aud'], exp=id_token['exp'], iat=id_token['iat'], nonce=id_token['nonce'], auth_time=id_token['auth_time'], application='orcid') oidc_authz = OidcAuthorization(access_token=token_resp['access_token'], token_type=token_resp['token_type'], id_token=oidc_id_token, expires_in=token_resp['expires_in'], refresh_token=token_resp['refresh_token'], application='orcid') orcid_element = Orcid(id=userinfo['id'], name=userinfo['name'], given_name=userinfo['given_name'], family_name=userinfo['family_name'], verified=True, oidc_authz=oidc_authz, application='orcid') orcid_proofing = OrcidProofing(proofing_user, created_by='orcid', orcid=orcid_element.id, issuer=orcid_element.oidc_authz.id_token.iss, audience=orcid_element.oidc_authz.id_token.aud, proofing_method='oidc', proofing_version='2018v1') if current_app.proofing_log.save(orcid_proofing): current_app.logger.info('ORCID proofing data saved to log') proofing_user.orcid = orcid_element save_and_sync_user(proofing_user) current_app.logger.info('ORCID proofing data saved to user') new_query_string = urlencode({'msg': 'orc.authorization_success'}) else: current_app.logger.info('ORCID proofing data NOT saved, failed to save proofing log') new_query_string = urlencode({'msg': ':ERROR:Temporary technical problems'}) # Clean up current_app.logger.info('Removing proofing state') current_app.proofing_statedb.remove_state(proofing_state) url = urlunsplit((scheme, netloc, path, new_query_string, fragment)) return redirect(url)
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)