def handle_freja_eid_userinfo(user, proofing_state, userinfo): """ :param user: Central userdb user :param proofing_state: Proofing state for user :param userinfo: userinfo from OP :type user: eduid_userdb.user.User :type proofing_state: eduid_userdb.proofing.OidcProofingState :type userinfo: dict :return: None """ current_app.logger.info('Verifying NIN from Freja eID for user {}'.format(user)) number = userinfo['results']['freja_eid']['ssn'] opaque = userinfo['results']['freja_eid']['opaque'] transaction_id = userinfo['results']['freja_eid']['ref'] if not number_match_proofing(user, proofing_state, number): current_app.logger.warning('Proofing state number did not match number in userinfo.' 'Using number from userinfo.') proofing_state.nin.number = number current_app.logger.info('Getting address for user {}'.format(user)) # Lookup official address via Navet address = current_app.msg_relay.get_postal_address(proofing_state.nin.number, timeout=15) proofing_log_entry = SeLegProofingFrejaEid(user, created_by=proofing_state.nin.created_by, nin=proofing_state.nin.number, transaction_id=transaction_id, opaque_data=opaque, user_postal_address=address, proofing_version='2017v1') verify_nin_for_user(user, proofing_state, proofing_log_entry) current_app.stats.count(name='freja.authn_response_handled')
def handle_seleg_userinfo(user, proofing_state, userinfo): """ :param user: Central userdb user :param proofing_state: Proofing state for user :param userinfo: userinfo from OP :type user: eduid_userdb.user.User :type proofing_state: eduid_userdb.proofing.OidcProofingState :type userinfo: dict :return: None """ current_app.logger.info('Verifying NIN from seleg for user {}'.format(user)) number = userinfo['identity'] metadata = userinfo.get('metadata', {}) if metadata.get('score', 0) == 100: if not number_match_proofing(user, proofing_state, number): current_app.logger.warning('Proofing state number did not match number in userinfo.' 'Using number from userinfo.') proofing_state.nin.number = number current_app.logger.info('Getting address for user {}'.format(user)) # Lookup official address via Navet address = current_app.msg_relay.get_postal_address(proofing_state.nin.number, timeout=15) # Transaction id is the same data as used for the QR code transaction_id = metadata['opaque'] proofing_log_entry = SeLegProofing(user, created_by=proofing_state.nin.created_by, nin=proofing_state.nin.number, vetting_by='se-leg', transaction_id=transaction_id, user_postal_address=address, proofing_version='2017v1') verify_nin_for_user(user, proofing_state, proofing_log_entry) current_app.stats.count(name='seleg.authn_response_handled') else: current_app.logger.info('se-leg proofing did not result in a verified account due to low score') current_app.stats.count(name='seleg.authn_response_with_low_score') send_new_verification_method_mail(user)
def proofing(user, nin): current_app.logger.info('Trying to verify nin via mobile number for user {}.'.format(user)) current_app.logger.debug('NIN: {!s}.'.format(nin)) # Add nin as not verified to the user proofing_state = create_proofing_state(user, nin) add_nin_to_user(user, proofing_state) # Get list of verified mobile numbers verified_mobiles = [item.number for item in user.phone_numbers.to_list() if item.is_verified] if not verified_mobiles: return {'_status': 'error', 'message': 'no_phone'} try: success, proofing_log_entry = match_mobile_to_user(user, nin, verified_mobiles) except LookupMobileTaskFailed: current_app.stats.count('validate_nin_by_mobile_error') return {'_status': 'error', 'message': 'error_lookup_mobile_task'} except MsgTaskFailed: current_app.stats.count('navet_error') return {'_status': 'error', 'message': 'error_navet_task'} if success: try: # Verify nin for user verify_nin_for_user(user, proofing_state, proofing_log_entry) return {'success': True, 'message': 'letter.verification_success'} except AmTaskFailed as e: current_app.logger.error('Verifying nin for user {} failed'.format(user)) current_app.logger.error('{}'.format(e)) return {'_status': 'error', 'message': 'Temporary technical problems'} return {'_status': 'error', 'message': 'nins.no-mobile-match'}
def nin_verify_action(session_info, user): """ Use a Sweden Connect federation IdP assertion to verify a users identity. :param session_info: the SAML session info :param user: Central db user :type session_info: dict :type user: eduid_userdb.User :return: redirect response :rtype: Response """ redirect_url = urlappend(current_app.config['DASHBOARD_URL'], 'nins') if not is_required_loa(session_info, 'loa3'): return redirect_with_msg(redirect_url, ':ERROR:eidas.authn_context_mismatch') if not is_valid_reauthn(session_info): return redirect_with_msg(redirect_url, ':ERROR:eidas.reauthn_expired') proofing_user = ProofingUser.from_user(user, current_app.private_userdb) asserted_nin = get_saml_attribute(session_info, 'personalIdentityNumber')[0] if proofing_user.nins.verified.count != 0: current_app.logger.error('User already has a verified NIN') current_app.logger.debug('Primary NIN: {}. Asserted NIN: {}'.format(proofing_user.nins.primary.number, asserted_nin)) return redirect_with_msg(redirect_url, ':ERROR:eidas.nin_already_verified') # Create a proofing log issuer = session_info['issuer'] authn_context = get_authn_ctx(session_info) try: user_address = current_app.msg_relay.get_postal_address(asserted_nin) except MsgTaskFailed as e: current_app.logger.error('Navet lookup failed: {}'.format(e)) current_app.stats.count('navet_error') return redirect_with_msg(redirect_url, ':ERROR:error_navet_task') proofing_log_entry = SwedenConnectProofing(user=proofing_user, created_by='eduid-eidas', nin=asserted_nin, issuer=issuer, authn_context_class=authn_context, user_postal_address=user_address, proofing_version='2018v1') # Verify NIN for user try: nin_element = NinProofingElement(number=asserted_nin, application='eduid-eidas', verified=False) proofing_state = NinProofingState({'eduPersonPrincipalName': user.eppn, 'nin': nin_element.to_dict()}) verify_nin_for_user(user, proofing_state, proofing_log_entry) except AmTaskFailed as e: current_app.logger.error('Verifying NIN for user failed') current_app.logger.error('{}'.format(e)) return redirect_with_msg(redirect_url, ':ERROR:Temporary technical problems') current_app.stats.count(name='nin_verified') return redirect_with_msg(redirect_url, 'eidas.nin_verify_success')
def handle_seleg_userinfo(user, proofing_state, userinfo): """ :param user: Central userdb user :param proofing_state: Proofing state for user :param userinfo: userinfo from OP :type user: eduid_userdb.user.User :type proofing_state: eduid_userdb.proofing.OidcProofingState :type userinfo: dict :return: None """ current_app.logger.info( 'Verifying NIN from seleg for user {}'.format(user)) number = userinfo['identity'] metadata = userinfo.get('metadata', {}) if metadata.get('score', 0) == 100: if not number_match_proofing(user, proofing_state, number): current_app.logger.warning( 'Proofing state number did not match number in userinfo.' 'Using number from userinfo.') proofing_state.nin.number = number current_app.logger.info('Getting address for user {}'.format(user)) # Lookup official address via Navet address = current_app.msg_relay.get_postal_address( proofing_state.nin.number, timeout=15) # Transaction id is the same data as used for the QR code transaction_id = metadata['opaque'] proofing_log_entry = SeLegProofing( user, created_by=proofing_state.nin.created_by, nin=proofing_state.nin.number, vetting_by='se-leg', transaction_id=transaction_id, user_postal_address=address, proofing_version='2017v1') verify_nin_for_user(user, proofing_state, proofing_log_entry) current_app.stats.count(name='seleg.authn_response_handled') else: current_app.logger.info( 'se-leg proofing did not result in a verified account due to low score' ) current_app.stats.count(name='seleg.authn_response_with_low_score') send_new_verification_method_mail(user)
def nin_verify_BACKDOOR(user: User) -> WerkzeugResponse: """ Mock using a Sweden Connect federation IdP assertion to verify a users identity when the request carries a magic cookie. :param user: Central db user :return: redirect response """ redirect_url = current_app.config.nin_verify_redirect_url proofing_user = ProofingUser.from_user(user, current_app.private_userdb) asserted_nin = request.cookies.get('nin') if proofing_user.nins.verified.count != 0: current_app.logger.error('User already has a verified NIN') current_app.logger.debug( 'Primary NIN: {}. Asserted NIN: {}'.format(proofing_user.nins.primary.number, asserted_nin) ) return redirect_with_msg(redirect_url, ':ERROR:eidas.nin_already_verified') # Create a proofing log issuer = 'https://idp.example.com/simplesaml/saml2/idp/metadata.php' authn_context = 'http://id.elegnamnden.se/loa/1.0/loa3' user_address = { 'Name': {'GivenNameMarking': '20', 'GivenName': 'Magic Cookie', 'Surname': 'Testsson'}, 'OfficialAddress': {'Address2': 'MAGIC COOKIE', 'PostalCode': '12345', 'City': 'LANDET'}, } proofing_log_entry = SwedenConnectProofing( user=proofing_user, created_by='eduid-eidas', nin=asserted_nin, issuer=issuer, authn_context_class=authn_context, user_postal_address=user_address, proofing_version='2018v1', ) # Verify NIN for user try: nin_element = NinProofingElement.from_dict(dict(number=asserted_nin, created_by='eduid-eidas', verified=False)) proofing_state = NinProofingState(id=None, modified_ts=None, eppn=user.eppn, nin=nin_element) if not verify_nin_for_user(user, proofing_state, proofing_log_entry): current_app.logger.error(f'Failed verifying NIN for user {user}') return redirect_with_msg(redirect_url, ':ERROR:Temporary technical problems') except AmTaskFailed: current_app.logger.exception('Verifying NIN for user failed') return redirect_with_msg(redirect_url, ':ERROR:Temporary technical problems') current_app.stats.count(name='nin_verified') return redirect_with_msg(redirect_url, 'eidas.nin_verify_success')
def verify_code(user, code): current_app.logger.info('Verifying code for user {}'.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': 'letter.no_state_found'} # Check if provided code matches the one in the letter if not code == proofing_state.nin.verification_code: current_app.logger.error('Verification code for user {} does not match'.format(user)) # TODO: Throttling to discourage an adversary to try brute force return {'_status': 'error', 'message': 'letter.wrong-code'} try: official_address = get_address(user, proofing_state) except MsgTaskFailed as e: current_app.logger.error('Navet lookup failed for user {}: {}'.format(user, e)) current_app.stats.count('navet_error') return {'_status': 'error', 'message': 'error_navet_task'} proofing_log_entry = LetterProofing(user, created_by='eduid_letter_proofing', nin=proofing_state.nin.number, letter_sent_to=proofing_state.proofing_letter.address, transaction_id=proofing_state.proofing_letter.transaction_id, user_postal_address=official_address, proofing_version='2016v1') try: # Verify nin for user verify_nin_for_user(user, proofing_state, proofing_log_entry) current_app.logger.info('Verified code for user {}'.format(user)) # Remove proofing state current_app.proofing_statedb.remove_state(proofing_state) return {'success': True, 'message': 'letter.verification_success', 'nins': user.nins.to_list_of_dicts()} except AmTaskFailed as e: current_app.logger.error('Verifying nin for user {} failed'.format(user)) current_app.logger.error('{}'.format(e)) return {'_status': 'error', 'message': 'Temporary technical problems'}
def handle_freja_eid_userinfo(user, proofing_state, userinfo): """ :param user: Central userdb user :param proofing_state: Proofing state for user :param userinfo: userinfo from OP :type user: eduid_userdb.user.User :type proofing_state: eduid_userdb.proofing.OidcProofingState :type userinfo: dict :return: None """ current_app.logger.info( 'Verifying NIN from Freja eID for user {}'.format(user)) number = userinfo['results']['freja_eid']['ssn'] opaque = userinfo['results']['freja_eid']['opaque'] transaction_id = userinfo['results']['freja_eid']['ref'] if not number_match_proofing(user, proofing_state, number): current_app.logger.warning( 'Proofing state number did not match number in userinfo.' 'Using number from userinfo.') proofing_state.nin.number = number current_app.logger.info('Getting address for user {}'.format(user)) # Lookup official address via Navet address = current_app.msg_relay.get_postal_address( proofing_state.nin.number, timeout=15) proofing_log_entry = SeLegProofingFrejaEid( user, created_by=proofing_state.nin.created_by, nin=proofing_state.nin.number, transaction_id=transaction_id, opaque_data=opaque, user_postal_address=address, proofing_version='2017v1') verify_nin_for_user(user, proofing_state, proofing_log_entry) current_app.stats.count(name='freja.authn_response_handled')
def proofing(user, nin): current_app.logger.info( 'Trying to verify nin via mobile number for user {}.'.format(user)) current_app.logger.debug('NIN: {!s}.'.format(nin)) # Add nin as not verified to the user proofing_state = create_proofing_state(user, nin) add_nin_to_user(user, proofing_state) # Get list of verified mobile numbers verified_mobiles = [ item.number for item in user.phone_numbers.to_list() if item.is_verified ] if not verified_mobiles: return error_response(message=MobileMsg.no_phone) try: success, proofing_log_entry = match_mobile_to_user( user, nin, verified_mobiles) except LookupMobileTaskFailed: current_app.stats.count('validate_nin_by_mobile_error') return error_response(message=MobileMsg.lookup_error) except MsgTaskFailed: current_app.stats.count('navet_error') return error_response(message=CommonMsg.navet_error) if success: try: # Verify nin for user if not verify_nin_for_user(user, proofing_state, proofing_log_entry): return error_response(message=CommonMsg.temp_problem) return { 'success': True, 'message': str(MobileMsg.verify_success.value) } except AmTaskFailed: current_app.logger.exception( f'Verifying nin for user {user} failed') return error_response(message=CommonMsg.temp_problem) return error_response(message=MobileMsg.no_match)
def nin_verify_action(session_info: Mapping[str, Any], user: User) -> WerkzeugResponse: """ Use a Sweden Connect federation IdP assertion to verify a users identity. :param session_info: the SAML session info :param user: Central db user :return: redirect response """ redirect_url = current_app.config.nin_verify_redirect_url if not is_required_loa(session_info, 'loa3'): return redirect_with_msg(redirect_url, EidasMsg.authn_context_mismatch) if not is_valid_reauthn(session_info): return redirect_with_msg(redirect_url, EidasMsg.reauthn_expired) proofing_user = ProofingUser.from_user(user, current_app.private_userdb) asserted_nin = get_saml_attribute(session_info, 'personalIdentityNumber')[0] if proofing_user.nins.verified.count != 0: current_app.logger.error('User already has a verified NIN') current_app.logger.debug( 'Primary NIN: {}. Asserted NIN: {}'.format(proofing_user.nins.primary.number, asserted_nin) ) return redirect_with_msg(redirect_url, EidasMsg.nin_already_verified) # Create a proofing log issuer = session_info['issuer'] authn_context = get_authn_ctx(session_info) try: user_address = current_app.msg_relay.get_postal_address(asserted_nin) except MsgTaskFailed as e: current_app.logger.error('Navet lookup failed: {}'.format(e)) current_app.stats.count('navet_error') return redirect_with_msg(redirect_url, CommonMsg.navet_error) proofing_log_entry = SwedenConnectProofing( user=proofing_user, created_by='eduid-eidas', nin=asserted_nin, issuer=issuer, authn_context_class=authn_context, user_postal_address=user_address, proofing_version='2018v1', ) # Verify NIN for user try: nin_element = NinProofingElement.from_dict(dict(number=asserted_nin, created_by='eduid-eidas', verified=False)) proofing_state = NinProofingState(id=None, modified_ts=None, eppn=user.eppn, nin=nin_element) if not verify_nin_for_user(user, proofing_state, proofing_log_entry): current_app.logger.error(f'Failed verifying NIN for user {user}') return redirect_with_msg(redirect_url, CommonMsg.temp_problem) except AmTaskFailed: current_app.logger.exception('Verifying NIN for user failed') return redirect_with_msg(redirect_url, CommonMsg.temp_problem) current_app.stats.count(name='nin_verified') return redirect_with_msg(redirect_url, EidasMsg.nin_verify_success, error=False)