コード例 #1
0
ファイル: views.py プロジェクト: lamlion/eduid-webapp
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()
コード例 #2
0
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)
コード例 #3
0
ファイル: verifications.py プロジェクト: SUNET/eduid-webapp
def verify_mail_address(state, proofing_user):
    """
    :param proofing_user: ProofingUser
    :param state: E-mail proofing state

    :type proofing_user: eduid_userdb.proofing.ProofingUser
    :type state: EmailProofingState

    :return: None

    """
    new_email = MailAddress(email=state.verification.email, application='email',
                            verified=True, primary=False)

    has_primary = proofing_user.mail_addresses.primary
    if has_primary is None:
        new_email.is_primary = True
    try:
        proofing_user.mail_addresses.add(new_email)
    except DuplicateElementViolation:
        proofing_user.mail_addresses.find(state.verification.email).is_verified = True
        if has_primary is None:
            proofing_user.mail_addresses.find(state.verification.email).is_primary = True

    mail_address_proofing = MailAddressProofing(proofing_user, created_by='email', mail_address=new_email.email,
                                                reference=state.reference, proofing_version='2013v1')
    if current_app.proofing_log.save(mail_address_proofing):
        save_and_sync_user(proofing_user)
        current_app.logger.info('Email address {!r} confirmed '
                                'for user {}'.format(state.verification.email, proofing_user))
        current_app.stats.count(name='email_verify_success', value=1)
        current_app.proofing_statedb.remove_state(state)
        current_app.logger.debug('Removed proofing state: {} '.format(state))
コード例 #4
0
ファイル: webauthn.py プロジェクト: SUNET/eduid-webapp
def registration_complete(user, credential_id, attestation_object, client_data, description):
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    server = get_webauthn_server(current_app.config['FIDO2_RP_ID'])
    att_obj = AttestationObject(urlsafe_b64decode(attestation_object))
    cdata_obj = ClientData(urlsafe_b64decode(client_data))
    state = session['_webauthn_state_']
    auth_data = server.register_complete(state, cdata_obj, att_obj)

    cred_data = auth_data.credential_data
    current_app.logger.debug('Proccessed Webauthn credential data: {}.'.format(cred_data))

    credential = Webauthn(
        keyhandle = credential_id,
        credential_data = base64.urlsafe_b64encode(cred_data).decode('ascii'),
        app_id = current_app.config['FIDO2_RP_ID'],
        attest_obj = base64.b64encode(attestation_object.encode('utf-8')).decode('ascii'),
        description = description,
        application = 'security'
        )

    security_user.credentials.add(credential)
    save_and_sync_user(security_user)
    current_app.stats.count(name='webauthn_register_complete')
    current_app.logger.info('User {} has completed registration of a webauthn token'.format(security_user))
    return {
        'message': 'security.webauthn_register_success',
        'credentials': compile_credential_list(security_user)
    }
コード例 #5
0
ファイル: webauthn.py プロジェクト: lamlion/eduid-webapp
def registration_complete(user, credential_id, attestation_object, client_data, description):
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    server = get_webauthn_server(current_app.config.fido2_rp_id)
    att_obj = AttestationObject(urlsafe_b64decode(attestation_object))
    cdata_obj = ClientData(urlsafe_b64decode(client_data))
    state = session['_webauthn_state_']
    auth_data = server.register_complete(state, cdata_obj, att_obj)

    cred_data = auth_data.credential_data
    current_app.logger.debug('Proccessed Webauthn credential data: {}.'.format(cred_data))

    credential = Webauthn.from_dict(
        dict(
            keyhandle=credential_id,
            credential_data=base64.urlsafe_b64encode(cred_data).decode('ascii'),
            app_id=current_app.config.fido2_rp_id,
            attest_obj=base64.b64encode(attestation_object.encode('utf-8')).decode('ascii'),
            description=description,
            created_by='security',
        )
    )

    security_user.credentials.add(credential)
    save_and_sync_user(security_user)
    current_app.stats.count(name='webauthn_register_complete')
    current_app.logger.info('User {} has completed registration of a webauthn token'.format(security_user))
    credentials = compile_credential_list(security_user)
    return success_response(payload=dict(credentials=credentials), message=SecurityMsg.webauthn_success)
コード例 #6
0
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
コード例 #7
0
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)
コード例 #8
0
ファイル: u2f.py プロジェクト: SUNET/eduid-webapp
def bind(user, version, registration_data, client_data, description=''):
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    enrollment_data = session.pop('_u2f_enroll_', None)
    if not enrollment_data:
        current_app.logger.error('Found no U2F enrollment data in session.')
        return {'_error': True, 'message': 'security.u2f.missing_enrollment_data'}
    data = {
        'version': version,
        'registrationData': registration_data,
        'clientData': client_data
    }
    device, der_cert = complete_registration(enrollment_data, data, current_app.config['U2F_FACETS'])

    cert = x509.load_der_x509_certificate(der_cert, default_backend())
    pem_cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
    if not isinstance(pem_cert, six.string_types):
        pem_cert = pem_cert.decode('utf-8')

    u2f_token = U2F(version=device['version'], keyhandle=device['keyHandle'], app_id=device['appId'],
                    public_key=device['publicKey'], attest_cert=pem_cert, description=description,
                    application='eduid_security', created_ts=True)
    security_user.credentials.add(u2f_token)
    save_and_sync_user(security_user)
    current_app.stats.count(name='u2f_token_bind')
    return {
        'message': 'security.u2f_register_success',
        'credentials': compile_credential_list(security_user)
    }
コード例 #9
0
ファイル: views.py プロジェクト: SUNET/eduid-webapp
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()
コード例 #10
0
ファイル: verifications.py プロジェクト: SUNET/eduid-webapp
def verify_phone_number(state, proofing_user):
    """
    :param proofing_user: ProofingUser
    :param state: Phone proofing state

    :type proofing_user: eduid_userdb.proofing.ProofingUser
    :type state: PhoneProofingState

    :return: None

    """
    number = state.verification.number
    new_phone = PhoneNumber(number=number, application='eduid_phone', verified=True, primary=False)

    has_primary = proofing_user.phone_numbers.primary
    if has_primary is None:
        new_phone.is_primary = True
    try:
        proofing_user.phone_numbers.add(new_phone)
    except DuplicateElementViolation:
        proofing_user.phone_numbers.find(number).is_verified = True
        if has_primary is None:
            proofing_user.phone_numbers.find(number).is_primary = True

    phone_number_proofing = PhoneNumberProofing(proofing_user, created_by='phone',
                                                phone_number=state.verification.number, reference=state.reference,
                                                proofing_version='2013v1')
    if current_app.proofing_log.save(phone_number_proofing):
        save_and_sync_user(proofing_user)
        current_app.logger.info('Mobile {} confirmed '
                                'for user {}'.format(number, proofing_user))
        current_app.stats.count(name='mobile_verify_success', value=1)
        current_app.proofing_statedb.remove_state(state)
        current_app.logger.debug('Removed proofing state: {} '.format(state))
コード例 #11
0
def complete_registration(signup_user):
    """
    After a successful registration:
    * record acceptance of TOU
    * generate a password,
    * add it to the user record,
    * update the attribute manager db with the new account,
    * create authn token and nonce for the dashboard,
    * return information to be sent to the user.

    :param signup_user: SignupUser instance
    :type signup_user: SignupUser

    :return: registration status info
    :rtype: dict
    """
    current_app.logger.info("Completing registration for user "
                            "{}".format(signup_user))

    context = {}
    password_id = ObjectId()
    (password, salt) = generate_password(str(password_id), signup_user)

    credential = Password(credential_id=password_id,
                          salt=salt,
                          application='signup')
    signup_user.passwords.add(credential)
    # Record the acceptance of the terms of use
    record_tou(signup_user, 'signup')
    try:
        save_and_sync_user(signup_user)
    except UserOutOfSync:
        current_app.logger.error('Couldnt save user {}, '
                                 'data out of sync'.format(signup_user))
        return {'_status': 'error', 'message': 'user-out-of-sync'}

    secret = current_app.config.get('AUTH_SHARED_SECRET')
    timestamp = '{:x}'.format(int(time.time()))
    nonce = os.urandom(16).encode('hex')
    auth_token = generate_auth_token(secret, signup_user.eppn, nonce,
                                     timestamp)

    context.update({
        "status": 'verified',
        "password": password,
        "email": signup_user.mail_addresses.primary.email,
        "eppn": signup_user.eppn,
        "nonce": nonce,
        "timestamp": timestamp,
        "auth_token": auth_token,
        "dashboard_url": current_app.config.get('AUTH_TOKEN_URL')
    })

    current_app.logger.info(
        "Signup process for new user {} complete".format(signup_user))
    return context
コード例 #12
0
ファイル: security.py プロジェクト: lamlion/eduid-webapp
def account_terminated(user):
    """
    The account termination action,
    removes all credentials for the terminated account
    from the VCCS service,
    flags the account as terminated,
    sends an email to the address in the terminated account,
    and logs out the session.

    :type user: eduid_userdb.user.User
    """
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    authn_ts = session.get('reauthn-for-termination', None)
    if authn_ts is None:
        abort(400)

    now = datetime.utcnow()
    delta = now - datetime.fromtimestamp(authn_ts)

    if int(delta.total_seconds()) > 600:
        return error_response(message=SecurityMsg.stale_reauthn)

    del session['reauthn-for-termination']

    # revoke all user passwords
    revoke_all_credentials(current_app.config.vccs_url, security_user)
    # Skip removing old passwords from the user at this point as a password reset will do that anyway.
    # This fixes the problem with loading users for a password reset as users without passwords triggers
    # the UserHasNotCompletedSignup check in eduid-userdb.
    # TODO: Needs a decision on how to handle unusable user passwords
    # for p in security_user.credentials.filter(Password).to_list():
    #    security_user.passwords.remove(p.key)

    # flag account as terminated
    security_user.terminated = True
    try:
        save_and_sync_user(security_user)
    except UserOutOfSync:
        return error_response(message=CommonMsg.out_of_sync)

    current_app.stats.count(name='security_account_terminated', value=1)
    current_app.logger.info('Terminated user account')

    # email the user
    try:
        send_termination_mail(security_user)
    except MsgTaskFailed as e:
        current_app.logger.error(
            f'Failed to send account termination mail: {e}')
        current_app.logger.error(
            'Account will be terminated successfully anyway.')

    current_app.logger.debug(f'Logging out (terminated) user {user}')
    return redirect(
        f'{current_app.config.logout_endpoint}?next={current_app.config.termination_redirect_url}'
    )
コード例 #13
0
ファイル: u2f.py プロジェクト: SUNET/eduid-webapp
def remove(user, credential_key):
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    token_to_remove = security_user.credentials.filter(U2F).find(credential_key)
    if token_to_remove:
        security_user.credentials.remove(credential_key)
        save_and_sync_user(security_user)
        current_app.stats.count(name='u2f_token_remove')
    return {
        'message': 'security.u2f-token-removed',
        'credentials': compile_credential_list(security_user)
    }
コード例 #14
0
def complete_registration(signup_user) -> FluxData:
    """
    After a successful registration:
    * record acceptance of TOU
    * generate a password,
    * add it to the user record,
    * update the attribute manager db with the new account,
    * create authn token for the dashboard,
    * return information to be sent to the user.

    :param signup_user: SignupUser instance
    :type signup_user: SignupUser

    :return: registration status info
    """
    current_app.logger.info("Completing registration for user "
                            "{}".format(signup_user))

    context = {}
    password_id = ObjectId()
    (password, salt) = generate_password(str(password_id), signup_user)

    credential = Password.from_dict(
        dict(credential_id=password_id,
             salt=salt,
             is_generated=True,
             created_by='signup'))
    signup_user.passwords.add(credential)
    # Record the acceptance of the terms of use
    record_tou(signup_user, 'signup')
    try:
        save_and_sync_user(signup_user)
    except UserOutOfSync:
        current_app.logger.error('Couldnt save user {}, '
                                 'data out of sync'.format(signup_user))
        return error_response(message='user-out-of-sync')

    timestamp = datetime.datetime.fromtimestamp(int(time.time()))
    if session.common is not None:  # please mypy
        session.common.eppn = signup_user.eppn
    if session.signup is not None:  # please mypy
        session.signup.ts = timestamp
    context.update({
        "status": 'verified',
        "password": password,
        "email": signup_user.mail_addresses.primary.email,
        "dashboard_url": current_app.config.signup_authn_url,
    })

    current_app.stats.count(name='signup_complete')
    current_app.logger.info(
        "Signup process for new user {} complete".format(signup_user))
    return success_response(payload=context)
コード例 #15
0
def remove(user, credential_key):
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    token_to_remove = security_user.credentials.filter(U2F).find(
        credential_key)
    if token_to_remove:
        security_user.credentials.remove(credential_key)
        save_and_sync_user(security_user)
        current_app.stats.count(name='u2f_token_remove')

    credentials = compile_credential_list(security_user)
    return success_response(payload=dict(credentials=credentials),
                            message=SecurityMsg.rm_u2f_success)
コード例 #16
0
def account_terminated(user):
    """
    The account termination action,
    removes all credentials for the terminated account
    from the VCCS service,
    flags the account as terminated,
    sends an email to the address in the terminated account,
    and logs out the session.

    :type user: eduid_userdb.user.User
    """
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    authn_ts = session.get('reauthn-for-termination', None)
    if authn_ts is None:
        abort(400)

    now = datetime.utcnow()
    delta = now - datetime.fromtimestamp(authn_ts)

    if int(delta.total_seconds()) > 600:
        return error_message('security.stale_authn_info')

    del session['reauthn-for-termination']

    # revoke all user passwords
    revoke_all_credentials(current_app.config.get('VCCS_URL'), security_user)
    # Skip removing old passwords from the user at this point as a password reset will do that anyway.
    # This fixes the problem with loading users for a password reset as users without passwords triggers
    # the UserHasNotCompletedSignup check in eduid-userdb.
    # TODO: Needs a decision on how to handle unusable user passwords
    #for p in security_user.credentials.filter(Password).to_list():
    #    security_user.passwords.remove(p.key)

    # flag account as terminated
    security_user.terminated = True
    try:
        save_and_sync_user(security_user)
    except UserOutOfSync:
        return error_message('user-out-of-sync')

    current_app.stats.count(name='security_account_terminated', value=1)
    current_app.logger.info('Terminated user account')

    # email the user
    send_termination_mail(security_user)

    session.invalidate()
    current_app.logger.info('Invalidated session for user')

    site_url = current_app.config.get("EDUID_SITE_URL")
    current_app.logger.info('Redirection user to user {}'.format(site_url))
    # TODO: Add a account termination completed view to redirect to
    return redirect(site_url)
コード例 #17
0
ファイル: security.py プロジェクト: SUNET/eduid-webapp
def account_terminated(user):
    """
    The account termination action,
    removes all credentials for the terminated account
    from the VCCS service,
    flags the account as terminated,
    sends an email to the address in the terminated account,
    and logs out the session.

    :type user: eduid_userdb.user.User
    """
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    authn_ts = session.get('reauthn-for-termination', None)
    if authn_ts is None:
        abort(400)

    now = datetime.utcnow()
    delta = now - datetime.fromtimestamp(authn_ts)

    if int(delta.total_seconds()) > 600:
        return error_message('security.stale_authn_info')

    del session['reauthn-for-termination']

    # revoke all user passwords
    revoke_all_credentials(current_app.config.get('VCCS_URL'), security_user)
    # Skip removing old passwords from the user at this point as a password reset will do that anyway.
    # This fixes the problem with loading users for a password reset as users without passwords triggers
    # the UserHasNotCompletedSignup check in eduid-userdb.
    # TODO: Needs a decision on how to handle unusable user passwords
    #for p in security_user.credentials.filter(Password).to_list():
    #    security_user.passwords.remove(p.key)

    # flag account as terminated
    security_user.terminated = True
    try:
        save_and_sync_user(security_user)
    except UserOutOfSync:
        return error_message('user-out-of-sync')

    current_app.stats.count(name='security_account_terminated', value=1)
    current_app.logger.info('Terminated user account')

    # email the user
    send_termination_mail(security_user)

    session.invalidate()
    current_app.logger.info('Invalidated session for user')

    site_url = current_app.config.get("EDUID_SITE_URL")
    current_app.logger.info('Redirection user to user {}'.format(site_url))
    # TODO: Add a account termination completed view to redirect to
    return redirect(site_url)
コード例 #18
0
ファイル: views.py プロジェクト: SUNET/eduid-webapp
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
コード例 #19
0
ファイル: helpers.py プロジェクト: SUNET/eduid-webapp
def complete_registration(signup_user):
    """
    After a successful registration:
    * record acceptance of TOU
    * generate a password,
    * add it to the user record,
    * update the attribute manager db with the new account,
    * create authn token for the dashboard,
    * return information to be sent to the user.

    :param signup_user: SignupUser instance
    :type signup_user: SignupUser

    :return: registration status info
    :rtype: dict
    """
    current_app.logger.info("Completing registration for user "
                    "{}".format(signup_user))

    context = {}
    password_id = ObjectId()
    (password, salt) = generate_password(str(password_id), signup_user)

    credential = Password(credential_id=password_id, salt=salt, application='signup')
    signup_user.passwords.add(credential)
    # Record the acceptance of the terms of use
    record_tou(signup_user, 'signup')
    try:
        save_and_sync_user(signup_user)
    except UserOutOfSync:
        current_app.logger.error('Couldnt save user {}, '
                                 'data out of sync'.format(signup_user))
        return {
            '_status': 'error',
            'message': 'user-out-of-sync'
        }

    timestamp = datetime.datetime.fromtimestamp(int(time.time()))
    session.common.eppn = signup_user.eppn
    session.signup.ts = timestamp
    context.update({
        "status": 'verified',
        "password": password,
        "email": signup_user.mail_addresses.primary.email,
        "dashboard_url": current_app.config.get('AUTH_TOKEN_URL')
    })

    current_app.stats.count(name='signup_complete')
    current_app.logger.info("Signup process for new user {} complete".format(signup_user))
    return context
コード例 #20
0
ファイル: helpers.py プロジェクト: lamlion/eduid-webapp
def reset_user_password(state, password):
    """
    :param state: Password reset state
    :type state: PasswordResetState
    :param password: Plain text password
    :type password: six.string_types
    :return: None
    :rtype: None
    """
    vccs_url = current_app.config.vccs_url

    user = current_app.central_userdb.get_user_by_eppn(state.eppn,
                                                       raise_on_missing=False)
    security_user = SecurityUser.from_user(
        user, private_userdb=current_app.private_userdb)

    # If no extra security is all verified information (except email addresses) is set to not verified
    if not extra_security_used(state):
        current_app.logger.info('No extra security used by user {}'.format(
            state.eppn))
        # Phone numbers
        verified_phone_numbers = security_user.phone_numbers.verified.to_list()
        if verified_phone_numbers:
            current_app.logger.info(
                'Unverifying phone numbers for user {}'.format(state.eppn))
            security_user.phone_numbers.primary.is_primary = False
            for phone_number in verified_phone_numbers:
                phone_number.is_verified = False
                current_app.logger.debug('Phone number {} unverified'.format(
                    phone_number.number))
        # NINs
        verified_nins = security_user.nins.verified.to_list()
        if verified_nins:
            current_app.logger.info('Unverifying nins for user {}'.format(
                state.eppn))
            security_user.nins.primary.is_primary = False
            for nin in verified_nins:
                nin.is_verified = False
                current_app.logger.debug('NIN {} unverified'.format(
                    nin.number))

    security_user = reset_password(security_user,
                                   new_password=password,
                                   application='security',
                                   vccs_url=vccs_url)
    security_user.terminated = False
    save_and_sync_user(security_user)
    current_app.stats.count(name='security_password_reset', value=1)
    current_app.logger.info('Reset password successful for user {}'.format(
        security_user.eppn))
コード例 #21
0
def reset_user_password(user: User, state: ResetPasswordState, password: str):
    """
    :param user: the user
    :param state: Password reset state
    :param password: Plain text password
    """
    vccs_url = current_app.config.vccs_url

    reset_password_user = ResetPasswordUser.from_user(
        user, private_userdb=current_app.private_userdb)

    # If no extra security is used, all verified information (except email addresses) is set to not verified
    if not extra_security_used(state):
        current_app.logger.info(f'No extra security used by user {user}')
        # Phone numbers
        verified_phone_numbers = reset_password_user.phone_numbers.verified.to_list(
        )
        if verified_phone_numbers:
            current_app.logger.info(
                f'Unverifying phone numbers for user {user}')
            reset_password_user.phone_numbers.primary.is_primary = False
            for phone_number in verified_phone_numbers:
                phone_number.is_verified = False
                current_app.logger.debug(
                    f'Phone number {phone_number.number} unverified')
        # NINs
        verified_nins = reset_password_user.nins.verified.to_list()
        if verified_nins:
            current_app.logger.info(f'Unverifying nins for user {user}')
            reset_password_user.nins.primary.is_primary = False
            for nin in verified_nins:
                nin.is_verified = False
                current_app.logger.debug(f'NIN {nin.number} unverified')

    is_generated = state.generated_password if isinstance(
        state.generated_password, bool) else False

    reset_password_user = reset_password(
        reset_password_user,
        new_password=password,
        is_generated=is_generated,
        application='security',
        vccs_url=vccs_url,
    )
    reset_password_user.terminated = False
    save_and_sync_user(reset_password_user)
    current_app.stats.count(name='security_password_reset', value=1)
    current_app.logger.info(
        f'Reset password successful for user {reset_password_user}')
コード例 #22
0
ファイル: u2f.py プロジェクト: SUNET/eduid-webapp
def modify(user, credential_key, description):
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    token_to_modify = security_user.credentials.filter(U2F).find(credential_key)
    if not token_to_modify:
        current_app.logger.error('Did not find requested U2F token for user.')
        return {'_error': True, 'message': 'security.u2f.missing_token'}
    if len(description) > current_app.config['U2F_MAX_DESCRIPTION_LENGTH']:
        current_app.logger.error('User tried to set a U2F token description longer than {}.'.format(
            current_app.config['U2F_MAX_DESCRIPTION_LENGTH']))
        return {'_error': True, 'message': 'security.u2f.description_to_long'}
    token_to_modify.description = description
    save_and_sync_user(security_user)
    current_app.stats.count(name='u2f_token_modify')
    return {
        'credentials': compile_credential_list(security_user)
    }
コード例 #23
0
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)
コード例 #24
0
ファイル: views.py プロジェクト: johanlundberg/eduid-webapp
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
コード例 #25
0
ファイル: views.py プロジェクト: SUNET/eduid-webapp
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
コード例 #26
0
def modify(user, credential_key, description):
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    token_to_modify = security_user.credentials.filter(U2F).find(
        credential_key)
    if not token_to_modify:
        current_app.logger.error('Did not find requested U2F token for user.')
        return error_response(message=SecurityMsg.no_token)

    if len(description) > current_app.config.u2f_max_description_length:
        current_app.logger.error(
            'User tried to set a U2F token description longer than {}.'.format(
                current_app.config.u2f_max_description_length))
        return error_response(message=SecurityMsg.long_desc)

    token_to_modify.description = description
    save_and_sync_user(security_user)
    current_app.stats.count(name='u2f_token_modify')
    return {'credentials': compile_credential_list(security_user)}
コード例 #27
0
ファイル: verifications.py プロジェクト: lamlion/eduid-webapp
def verify_mail_address(state, proofing_user):
    """
    :param proofing_user: ProofingUser
    :param state: E-mail proofing state

    :type proofing_user: eduid_userdb.proofing.ProofingUser
    :type state: EmailProofingState

    :return: None

    """
    new_email = MailAddress.from_dict(
        dict(email=state.verification.email,
             created_by='email',
             verified=True,
             primary=False))

    has_primary = proofing_user.mail_addresses.primary
    if has_primary is None:
        new_email.is_primary = True
    try:
        proofing_user.mail_addresses.add(new_email)
    except DuplicateElementViolation:
        proofing_user.mail_addresses.find(
            state.verification.email).is_verified = True
        if has_primary is None:
            proofing_user.mail_addresses.find(
                state.verification.email).is_primary = True

    mail_address_proofing = MailAddressProofing(
        proofing_user,
        created_by='email',
        mail_address=new_email.email,
        reference=state.reference,
        proofing_version='2013v1',
    )
    if current_app.proofing_log.save(mail_address_proofing):
        save_and_sync_user(proofing_user)
        current_app.logger.info('Email address {!r} confirmed '
                                'for user {}'.format(state.verification.email,
                                                     proofing_user))
        current_app.stats.count(name='email_verify_success', value=1)
        current_app.proofing_statedb.remove_state(state)
        current_app.logger.debug('Removed proofing state: {} '.format(state))
コード例 #28
0
ファイル: views.py プロジェクト: SUNET/eduid-webapp
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
コード例 #29
0
ファイル: views.py プロジェクト: SUNET/eduid-webapp
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
コード例 #30
0
def verify_phone_number(state, proofing_user):
    """
    :param proofing_user: ProofingUser
    :param state: Phone proofing state

    :type proofing_user: eduid_userdb.proofing.ProofingUser
    :type state: PhoneProofingState

    :return: None

    """
    number = state.verification.number
    new_phone = PhoneNumber.from_dict(
        dict(number=number,
             created_by='eduid_phone',
             verified=True,
             primary=False))

    has_primary = proofing_user.phone_numbers.primary
    if has_primary is None:
        new_phone.is_primary = True
    try:
        proofing_user.phone_numbers.add(new_phone)
    except DuplicateElementViolation:
        proofing_user.phone_numbers.find(number).is_verified = True
        if has_primary is None:
            proofing_user.phone_numbers.find(number).is_primary = True

    phone_number_proofing = PhoneNumberProofing(
        proofing_user,
        created_by='phone',
        phone_number=state.verification.number,
        reference=state.reference,
        proofing_version='2013v1',
    )
    if current_app.proofing_log.save(phone_number_proofing):
        save_and_sync_user(proofing_user)
        current_app.logger.info('Mobile {} confirmed '
                                'for user {}'.format(number, proofing_user))
        current_app.stats.count(name='mobile_verify_success', value=1)
        current_app.proofing_statedb.remove_state(state)
        current_app.logger.debug('Removed proofing state: {} '.format(state))
コード例 #31
0
def post_user(user, given_name, surname, display_name, language):
    personal_data_user = PersonalDataUser.from_user(user,
                                                    current_app.private_userdb)
    current_app.logger.debug('Trying to save user {}'.format(user))

    personal_data_user.given_name = given_name
    personal_data_user.surname = surname
    personal_data_user.display_name = display_name
    personal_data_user.language = language
    try:
        save_and_sync_user(personal_data_user)
    except UserOutOfSync:
        return error_response(message=CommonMsg.out_of_sync)
    current_app.stats.count(name='personal_data_saved', value=1)
    current_app.logger.info(
        'Saved personal data for user {}'.format(personal_data_user))

    personal_data = personal_data_user.to_dict()
    return success_response(payload=personal_data,
                            message=PDataMsg.save_success)
コード例 #32
0
ファイル: webauthn.py プロジェクト: SUNET/eduid-webapp
def remove(user, credential_key):
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    tokens = security_user.credentials.filter(FidoCredential)
    if tokens.count <= 1:
        return {'_error': True, 'message': 'security.webauthn-noremove-last'}
    token_to_remove = security_user.credentials.find(credential_key)
    if token_to_remove:
        security_user.credentials.remove(credential_key)
        save_and_sync_user(security_user)
        current_app.stats.count(name='webauthn_token_remove')
        current_app.logger.info(f'User {security_user} has removed a security token: {credential_key}')
        message = 'security.webauthn-token-removed'
    else:
        current_app.logger.info(f'User {security_user} has tried to remove a'
                                f' missing security token: {credential_key}')
        message = 'security.webauthn-token-notfound'
    return {
        'message': message,
        'credentials': compile_credential_list(security_user)
    }
コード例 #33
0
def post_user(user, given_name, surname, display_name, language):
    personal_data_user = PersonalDataUser.from_user(user,
                                                    current_app.private_userdb)
    current_app.logger.debug('Trying to save user {}'.format(user))

    personal_data_user.given_name = given_name
    personal_data_user.surname = surname
    personal_data_user.display_name = display_name
    personal_data_user.language = language
    try:
        save_and_sync_user(personal_data_user)
    except UserOutOfSync:
        return {'_status': 'error', 'message': 'user-out-of-sync'}
    current_app.stats.count(name='personal_data_saved', value=1)
    current_app.logger.info(
        'Saved personal data for user {}'.format(personal_data_user))

    personal_data = personal_data_user.to_dict()
    personal_data['message'] = 'pd.save-success'
    return PersonalDataSchema().dump(personal_data).data
コード例 #34
0
def change_password(user, old_password, new_password):
    """
    View to change the password
    """
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    authn_ts = session.get('reauthn-for-chpass', None)
    if authn_ts is None:
        return error_message('chpass.no_reauthn')

    now = datetime.utcnow()
    delta = now - datetime.fromtimestamp(authn_ts)
    timeout = current_app.config.get('CHPASS_TIMEOUT', 600)
    if int(delta.total_seconds()) > timeout:
        return error_message('chpass.stale_reauthn')

    vccs_url = current_app.config.get('VCCS_URL')
    added = add_credentials(vccs_url, old_password, new_password, security_user, source='security')

    if not added:
        current_app.logger.debug('Problem verifying the old credentials for {}'.format(user))
        return error_message('chpass.unable-to-verify-old-password')

    security_user.terminated = False
    try:
        save_and_sync_user(security_user)
    except UserOutOfSync:
        return error_message('user-out-of-sync')

    del session['reauthn-for-chpass']

    current_app.stats.count(name='security_password_changed', value=1)
    current_app.logger.info('Changed password for user {}'.format(security_user.eppn))

    next_url = current_app.config.get('DASHBOARD_URL', '/profile')
    credentials = {
        'next_url': next_url,
        'credentials': compile_credential_list(security_user),
        'message': 'chpass.password-changed'
        }

    return CredentialList().dump(credentials).data
コード例 #35
0
ファイル: security.py プロジェクト: SUNET/eduid-webapp
def change_password(user, old_password, new_password):
    """
    View to change the password
    """
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    authn_ts = session.get('reauthn-for-chpass', None)
    if authn_ts is None:
        return error_message('chpass.no_reauthn')

    now = datetime.utcnow()
    delta = now - datetime.fromtimestamp(authn_ts)
    timeout = current_app.config.get('CHPASS_TIMEOUT', 600)
    if int(delta.total_seconds()) > timeout:
        return error_message('chpass.stale_reauthn')

    vccs_url = current_app.config.get('VCCS_URL')
    added = add_credentials(vccs_url, old_password, new_password, security_user, source='security')

    if not added:
        current_app.logger.debug('Problem verifying the old credentials for {}'.format(user))
        return error_message('chpass.unable-to-verify-old-password')

    security_user.terminated = False
    try:
        save_and_sync_user(security_user)
    except UserOutOfSync:
        return error_message('user-out-of-sync')

    del session['reauthn-for-chpass']

    current_app.stats.count(name='security_password_changed', value=1)
    current_app.logger.info('Changed password for user {}'.format(security_user.eppn))

    next_url = current_app.config.get('DASHBOARD_URL', '/profile')
    credentials = {
        'next_url': next_url,
        'credentials': compile_credential_list(security_user),
        'message': 'chpass.password-changed'
        }

    return CredentialList().dump(credentials).data
コード例 #36
0
ファイル: webauthn.py プロジェクト: lamlion/eduid-webapp
def remove(user, credential_key):
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    tokens = security_user.credentials.filter(FidoCredential)
    if tokens.count <= 1:
        return {'_error': True, 'message': SecurityMsg.no_last.value}

    token_to_remove = security_user.credentials.find(credential_key)
    if token_to_remove:
        security_user.credentials.remove(credential_key)
        save_and_sync_user(security_user)
        current_app.stats.count(name='webauthn_token_remove')
        current_app.logger.info(f'User {security_user} has removed a security token: {credential_key}')
        message = SecurityMsg.rm_webauthn
    else:
        current_app.logger.info(
            f'User {security_user} has tried to remove a' f' missing security token: {credential_key}'
        )
        message = SecurityMsg.no_webauthn

    credentials = compile_credential_list(security_user)
    return {'message': message, 'credentials': credentials}
コード例 #37
0
ファイル: views.py プロジェクト: SUNET/eduid-webapp
def post_user(user, given_name, surname, display_name, language):
    personal_data_user = PersonalDataUser.from_user(user, current_app.private_userdb)
    current_app.logger.debug('Trying to save user {}'.format(user))

    personal_data_user.given_name = given_name
    personal_data_user.surname = surname
    personal_data_user.display_name = display_name
    personal_data_user.language = language
    try:
        save_and_sync_user(personal_data_user)
    except UserOutOfSync:
        return {
            '_status': 'error',
            'message': 'user-out-of-sync'
        }
    current_app.stats.count(name='personal_data_saved', value=1)
    current_app.logger.info('Saved personal data for user {}'.format(personal_data_user))

    personal_data = personal_data_user.to_dict()
    personal_data['message'] = 'pd.save-success'
    return PersonalDataSchema().dump(personal_data).data
コード例 #38
0
ファイル: views.py プロジェクト: johanlundberg/eduid-webapp
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
コード例 #39
0
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
コード例 #40
0
ファイル: helpers.py プロジェクト: SUNET/eduid-webapp
def reset_user_password(state, password):
    """
    :param state: Password reset state
    :type state: PasswordResetState
    :param password: Plain text password
    :type password: six.string_types
    :return: None
    :rtype: None
    """
    vccs_url = current_app.config.get('VCCS_URL')

    user = current_app.central_userdb.get_user_by_eppn(state.eppn, raise_on_missing=False)
    security_user = SecurityUser.from_user(user, private_userdb=current_app.private_userdb)

    # If no extra security is all verified information (except email addresses) is set to not verified
    if not extra_security_used(state):
        current_app.logger.info('No extra security used by user {}'.format(state.eppn))
        # Phone numbers
        verified_phone_numbers = security_user.phone_numbers.verified.to_list()
        if verified_phone_numbers:
            current_app.logger.info('Unverifying phone numbers for user {}'.format(state.eppn))
            security_user.phone_numbers.primary.is_primary = False
            for phone_number in verified_phone_numbers:
                phone_number.is_verified = False
                current_app.logger.debug('Phone number {} unverified'.format(phone_number.number))
        # NINs
        verified_nins = security_user.nins.verified.to_list()
        if verified_nins:
            current_app.logger.info('Unverifying nins for user {}'.format(state.eppn))
            security_user.nins.primary.is_primary = False
            for nin in verified_nins:
                nin.is_verified = False
                current_app.logger.debug('NIN {} unverified'.format(nin.number))

    security_user = reset_password(security_user, new_password=password, application='security', vccs_url=vccs_url)
    security_user.terminated = False
    save_and_sync_user(security_user)
    current_app.stats.count(name='security_password_reset', value=1)
    current_app.logger.info('Reset password successful for user {}'.format(security_user.eppn))
コード例 #41
0
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
コード例 #42
0
ファイル: views.py プロジェクト: johanlundberg/eduid-webapp
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
コード例 #43
0
ファイル: views.py プロジェクト: SUNET/eduid-webapp
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
コード例 #44
0
ファイル: views.py プロジェクト: SUNET/eduid-webapp
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
コード例 #45
0
def bind(user, version, registration_data, client_data, description=''):
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    enrollment_data = session.pop('_u2f_enroll_', None)
    if not enrollment_data:
        current_app.logger.error('Found no U2F enrollment data in session.')
        return error_response(message=SecurityMsg.missing_data)

    data = {
        'version': version,
        'registrationData': registration_data,
        'clientData': client_data
    }
    device, der_cert = complete_registration(enrollment_data, data,
                                             current_app.config.u2f_facets)

    cert = x509.load_der_x509_certificate(der_cert, default_backend())
    pem_cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
    if not isinstance(pem_cert, six.string_types):
        pem_cert = pem_cert.decode('utf-8')

    u2f_token = U2F.from_dict(
        dict(
            version=device['version'],
            keyhandle=device['keyHandle'],
            app_id=device['appId'],
            public_key=device['publicKey'],
            attest_cert=pem_cert,
            description=description,
            created_by='eduid_security',
            created_ts=True,
        ))
    security_user.credentials.add(u2f_token)
    save_and_sync_user(security_user)
    current_app.stats.count(name='u2f_token_bind')
    credentials = compile_credential_list(security_user)
    return success_response(payload=dict(credentials=credentials),
                            message=SecurityMsg.u2f_registered)
コード例 #46
0
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)
コード例 #47
0
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)
コード例 #48
0
ファイル: views.py プロジェクト: lamlion/eduid-webapp
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)
コード例 #49
0
def token_verify_action(session_info: Mapping[str, Any], user: User) -> WerkzeugResponse:
    """
    Use a Sweden Connect federation IdP assertion to verify a users MFA token and, if necessary,
    the users identity.

    :param session_info: the SAML session info
    :param user: Central db user

    :return: redirect response
    """
    redirect_url = current_app.config.token_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)
    token_to_verify = proofing_user.credentials.filter(FidoCredential).find(
        session['verify_token_action_credential_id']
    )

    # Check (again) if token was used to authenticate this session
    if token_to_verify.key not in session['eduidIdPCredentialsUsed']:
        return redirect_with_msg(redirect_url, EidasMsg.token_not_in_creds)

    # Verify asserted NIN for user if there are no verified NIN
    if proofing_user.nins.verified.count == 0:
        nin_verify_action(session_info)
        user = current_app.central_userdb.get_user_by_eppn(user.eppn)
        proofing_user = ProofingUser.from_user(user, current_app.private_userdb)
        token_to_verify = proofing_user.credentials.filter(FidoCredential).find(
            session['verify_token_action_credential_id']
        )

    # Check that a verified NIN is equal to the asserted attribute personalIdentityNumber
    asserted_nin = get_saml_attribute(session_info, 'personalIdentityNumber')[0]
    user_nin = proofing_user.nins.verified.find(asserted_nin)
    if not user_nin:
        current_app.logger.error('Asserted NIN not matching user verified nins')
        current_app.logger.debug('Asserted NIN: {}'.format(asserted_nin))
        return redirect_with_msg(redirect_url, EidasMsg.nin_not_matching)

    # Create a proofing log
    issuer = session_info['issuer']
    current_app.logger.debug('Issuer: {}'.format(issuer))
    authn_context = get_authn_ctx(session_info)
    current_app.logger.debug('Authn context: {}'.format(authn_context))
    try:
        user_address = current_app.msg_relay.get_postal_address(user_nin.number)
    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 = MFATokenProofing(
        user=proofing_user,
        created_by='eduid-eidas',
        nin=user_nin.number,
        issuer=issuer,
        authn_context_class=authn_context,
        key_id=token_to_verify.key,
        user_postal_address=user_address,
        proofing_version='2018v1',
    )

    # Set token as verified
    token_to_verify.is_verified = True
    token_to_verify.proofing_method = 'SWAMID_AL2_MFA_HI'
    token_to_verify.proofing_version = '2018v1'

    # Save proofing log entry and save user
    if current_app.proofing_log.save(proofing_log_entry):
        current_app.logger.info('Recorded MFA token verification in the proofing log')
        try:
            save_and_sync_user(proofing_user)
        except AmTaskFailed as e:
            current_app.logger.error('Verifying token for user failed')
            current_app.logger.error('{}'.format(e))
            return redirect_with_msg(redirect_url, CommonMsg.temp_problem)
        current_app.stats.count(name='fido_token_verified')

    return redirect_with_msg(redirect_url, EidasMsg.verify_success, error=False)
コード例 #50
0
ファイル: acs_actions.py プロジェクト: SUNET/eduid-webapp
def token_verify_action(session_info, user):
    """
    Use a Sweden Connect federation IdP assertion to verify a users MFA token and, if necessary,
    the 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'], 'security')

    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)
    token_to_verify = proofing_user.credentials.filter(FidoCredential).find(
        session['verify_token_action_credential_id'])

    # Check (again) if token was used to authenticate this session
    if token_to_verify.key not in session['eduidIdPCredentialsUsed']:
        return redirect_with_msg(redirect_url, ':ERROR:eidas.token_not_in_credentials_used')

    # Verify asserted NIN for user if there are no verified NIN
    if proofing_user.nins.verified.count == 0:
        nin_verify_action(session_info)
        user = current_app.central_userdb.get_user_by_eppn(user.eppn)
        proofing_user = ProofingUser.from_user(user, current_app.private_userdb)
        token_to_verify = proofing_user.credentials.filter(FidoCredential).find(
            session['verify_token_action_credential_id'])

    # Check that a verified NIN is equal to the asserted attribute personalIdentityNumber
    asserted_nin = get_saml_attribute(session_info, 'personalIdentityNumber')[0]
    user_nin = proofing_user.nins.verified.find(asserted_nin)
    if not user_nin:
        current_app.logger.error('Asserted NIN not matching user verified nins')
        current_app.logger.debug('Asserted NIN: {}'.format(asserted_nin))
        return redirect_with_msg(redirect_url, ':ERROR:eidas.nin_not_matching')

    # Create a proofing log
    issuer = session_info['issuer']
    current_app.logger.debug('Issuer: {}'.format(issuer))
    authn_context = get_authn_ctx(session_info)
    current_app.logger.debug('Authn context: {}'.format(authn_context))
    try:
        user_address = current_app.msg_relay.get_postal_address(user_nin.number)
    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 = MFATokenProofing(user=proofing_user, created_by='eduid-eidas', nin=user_nin.number,
                                          issuer=issuer, authn_context_class=authn_context, key_id=token_to_verify.key,
                                          user_postal_address=user_address, proofing_version='2018v1')

    # Set token as verified
    token_to_verify.is_verified = True
    token_to_verify.proofing_method = 'SWAMID_AL2_MFA_HI'
    token_to_verify.proofing_version = '2018v1'

    # Save proofing log entry and save user
    if current_app.proofing_log.save(proofing_log_entry):
        current_app.logger.info('Recorded MFA token verification in the proofing log')
        try:
            save_and_sync_user(proofing_user)
        except AmTaskFailed as e:
            current_app.logger.error('Verifying token for user failed')
            current_app.logger.error('{}'.format(e))
            return redirect_with_msg(redirect_url, ':ERROR:Temporary technical problems')
        current_app.stats.count(name='fido_token_verified')

    return redirect_with_msg(redirect_url, 'eidas.token_verify_success')
コード例 #51
0
ファイル: views.py プロジェクト: SUNET/eduid-webapp
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)