コード例 #1
0
def decline_invite(user: User, group_identifier: UUID, email_address: str,
                   role: GroupRole) -> FluxData:
    # Check that the current user has verified the invited email address
    user_email_address = user.mail_addresses.find(email_address)
    if not user_email_address or not user_email_address.is_verified:
        current_app.logger.error(
            f'User has not verified email address: {email_address}')
        return error_response(
            message=GroupManagementMsg.mail_address_not_verified)

    invite_state = current_app.invite_state_db.get_state(
        group_scim_id=str(group_identifier),
        email_address=email_address,
        role=role,
        raise_on_missing=False)
    if not invite_state:
        current_app.logger.error('Invite does not exist')
        return error_response(message=GroupManagementMsg.invite_not_found)

    # Remove group invite
    try:
        current_app.invite_state_db.remove_state(invite_state)
    except EduIDDBError:
        return error_response(message=CommonMsg.temp_problem)

    current_app.stats.count(name=f'invite_declined_{invite_state.role.value}')
    return incoming_invites()
コード例 #2
0
ファイル: security.py プロジェクト: lamlion/eduid-webapp
def add_nin(user, nin):
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    current_app.logger.info('Removing NIN from user')
    current_app.logger.debug('NIN: {}'.format(nin))

    nin_obj = security_user.nins.find(nin)
    if nin_obj:
        current_app.logger.info('NIN already added.')
        return error_response(message=SecurityMsg.already_exists)

    try:
        nin_element = NinProofingElement.from_dict(
            dict(number=nin, created_by='security', verified=False))
        proofing_state = NinProofingState.from_dict({
            'eduPersonPrincipalName':
            security_user.eppn,
            'nin':
            nin_element.to_dict()
        })
        add_nin_to_user(user, proofing_state, user_class=SecurityUser)
        return success_response(
            payload=dict(nins=security_user.nins.to_list_of_dicts()),
            message=SecurityMsg.add_success)
    except AmTaskFailed as e:
        current_app.logger.error('Adding nin to user failed')
        current_app.logger.debug(f'NIN: {nin}')
        current_app.logger.error('{}'.format(e))
        return error_response(message=CommonMsg.temp_problem)
コード例 #3
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)
コード例 #4
0
def freja_proofing(user, nin):
    proofing_state = current_app.proofing_statedb.get_state_by_eppn(
        user.eppn, raise_on_missing=False)
    if not proofing_state:
        current_app.logger.debug(
            'No proofing state found for user {!s}. Initializing new proofing flow.'
            .format(user))
        proofing_state = helpers.create_proofing_state(user, nin)

        # Initiate authn request
        try:
            redirect_url = url_for('oidc_proofing.authorization_response',
                                   _external=True)
            claims_request = ClaimsRequest(userinfo=Claims(results=None))
            success = helpers.do_authn_request(proofing_state, claims_request,
                                               redirect_url)
            if not success:
                current_app.stats.count(name='freja.authn_request_op_error')
                return error_response(message=CommonMsg.temp_problem)
        except requests.exceptions.ConnectionError as e:
            current_app.logger.error(
                'No connection to authorization endpoint: {!s}'.format(e))
            return error_response(message=OIDCMsg.no_conn)

        # If authentication request went well save user state
        current_app.stats.count(name='freja.authn_request_success')
        current_app.proofing_statedb.save(proofing_state)
        current_app.logger.debug(
            'Proofing state {!s} for user {!s} saved'.format(
                proofing_state.state, user))
    # Add the nin used to initiate the proofing state to the user
    # NOOP if the user already have the nin
    add_nin_to_user(user, proofing_state)

    return get_freja_state()
コード例 #5
0
def resend_code(user, number):
    """
    view to resend a new verification code for one of the (unverified)
    phone numbers of the logged in user.

    Returns a listing of  all phones for the logged in user.
    """
    current_app.logger.debug(
        'Trying to send new verification code for phone number '
        ' {!r} for user {}'.format(number, user))

    if not user.phone_numbers.find(number):
        current_app.logger.warning(
            'Unknown phone in resend_code_action, user {}'.format(user))
        return error_response(message=CommonMsg.out_of_sync)

    sent = send_verification_code(user, number)
    if not sent:
        return error_response(message=PhoneMsg.still_valid_code)

    current_app.logger.debug('New verification code sent to '
                             'phone number {!r} for user {}'.format(
                                 number, user))
    current_app.stats.count(name='mobile_resend_code', value=1)

    phones = {'phones': user.phone_numbers.to_list_of_dicts()}
    return success_response(payload=phones, message=PhoneMsg.resend_success)
コード例 #6
0
def create_invite(user: User, group_identifier: UUID, email_address: str,
                  role: GroupRole) -> FluxData:
    scim_user = get_scim_user_by_eppn(user.eppn)
    if not scim_user:
        current_app.logger.error('User does not exist in scimapi_userdb')
        return error_response(message=GroupManagementMsg.user_does_not_exist)

    if not is_owner(scim_user, group_identifier):
        current_app.logger.error(
            f'User is not owner of group with scim_id: {group_identifier}')
        return error_response(message=GroupManagementMsg.user_not_owner)

    invite_state = GroupInviteState(group_scim_id=str(group_identifier),
                                    email_address=email_address,
                                    role=role,
                                    inviter_eppn=user.eppn)
    try:
        current_app.invite_state_db.save(invite_state)
    except DuplicateKeyError:
        current_app.logger.info(
            f'Invite for email address {invite_state.email_address} to group {invite_state.group_scim_id} '
            f'as role {invite_state.role.value} already exists.')
    # Always send an e-mail even it the invite already existed
    try:
        send_invite_email(invite_state)
    except MailTaskFailed:
        return error_response(message=CommonMsg.temp_problem)
    current_app.stats.count(name='invite_created')
    return outgoing_invites()
コード例 #7
0
ファイル: webauthn.py プロジェクト: lamlion/eduid-webapp
def registration_begin(user, authenticator):
    user_webauthn_tokens = user.credentials.filter(FidoCredential)
    if user_webauthn_tokens.count >= current_app.config.webauthn_max_allowed_tokens:
        current_app.logger.error(
            'User tried to register more than {} tokens.'.format(current_app.config.webauthn_max_allowed_tokens)
        )
        return error_response(message=SecurityMsg.max_webauthn)

    creds = make_credentials(user_webauthn_tokens.to_list())
    server = get_webauthn_server(current_app.config.fido2_rp_id)
    if user.given_name is None or user.surname is None or user.display_name is None:
        return error_response(message=SecurityMsg.no_pdata)

    registration_data, state = server.register_begin(
        {
            'id': str(user.eppn).encode('ascii'),
            'name': "{} {}".format(user.given_name, user.surname),
            'displayName': user.display_name,
        },
        credentials=creds,
        user_verification=USER_VERIFICATION.DISCOURAGED,
        authenticator_attachment=authenticator,
    )
    session['_webauthn_state_'] = state

    current_app.logger.info('User {} has started registration of a webauthn token'.format(user))
    current_app.logger.debug('Webauthn Registration data: {}.'.format(registration_data))
    current_app.stats.count(name='webauthn_register_begin')

    encoded_data = base64.urlsafe_b64encode(cbor.encode(registration_data)).decode('ascii')
    encoded_data = encoded_data.rstrip('=')
    return {'csrf_token': session.new_csrf_token(), 'registration_data': encoded_data}
コード例 #8
0
def set_new_pw_extra_security_phone(code: str, password: str,
                                    phone_code: str) -> FluxData:
    """
    View that receives an emailed reset password code, an SMS'ed reset password
    code, and a password, and sets the password as credential for the user, with
    extra security.

    Preconditions required for the call to succeed:
    * A PasswordResetEmailAndPhoneState object in the password_reset_state_db
      keyed by the received codes.
    * A flag in said state object indicating that the emailed code has already
      been verified.

    As side effects, this view will:
    * Compare the received password with the hash in the session to mark
      it accordingly (as suggested or as custom);
    * Revoke all password credentials the user had;

    This operation may fail due to:
    * The codes do not correspond to a valid state in the db;
    * Any of the codes have expired;
    * No valid user corresponds to the eppn stored in the state;
    * Communication problems with the VCCS backend;
    * Synchronization problems with the central user db.
    """
    if not password:
        return error_response(message=ResetPwMsg.chpass_no_data)

    data = _load_data(code, password)
    if data.error:
        return error_response(message=data.error)

    if not isinstance(data.state, ResetPasswordEmailAndPhoneState):
        raise TypeError(
            f'State is not ResetPasswordEmailAndPhoneState ({type(data.state)})'
        )

    if phone_code == data.state.phone_code.code:
        if not verify_phone_number(data.state):
            current_app.logger.info(
                f'Could not verify phone code for {data.state.eppn}')
            return error_response(message=ResetPwMsg.phone_invalid)

        current_app.logger.info(f'Phone code verified for {data.state.eppn}')
        current_app.stats.count(
            name='reset_password_extra_security_phone_success')
    else:
        current_app.logger.info(
            f'Could not verify phone code for {data.state.eppn}')
        return error_response(message=ResetPwMsg.unknown_phone_code)

    current_app.logger.info(f'Resetting password for user {data.user}')
    reset_user_password(data.user, data.state, password)
    current_app.logger.info(
        f'Password reset done, removing state for {data.user}')
    current_app.password_reset_state_db.remove_state(data.state)
    return success_response(message=ResetPwMsg.pw_resetted)
コード例 #9
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}'
    )
コード例 #10
0
def trycaptcha(email, recaptcha_response, tou_accepted):
    """
    Kantara requires a check for humanness even at level AL1.
    """
    if not tou_accepted:
        return error_response(message=SignupMsg.no_tou)

    config = current_app.config
    recaptcha_verified = False

    # add a backdoor to bypass recaptcha checks for humanness,
    # to be used in testing environments for automated integration tests.
    if check_magic_cookie(config):
        current_app.logger.info(
            'Using BACKDOOR to verify reCaptcha during signup!')
        recaptcha_verified = True

    # common path with no backdoor
    if not recaptcha_verified:
        remote_ip = request.remote_addr
        recaptcha_public_key = config.recaptcha_public_key

        if recaptcha_public_key:
            recaptcha_private_key = config.recaptcha_private_key
            recaptcha_verified = verify_recaptcha(recaptcha_private_key,
                                                  recaptcha_response,
                                                  remote_ip)
        else:
            recaptcha_verified = False
            current_app.logger.info('Missing configuration for reCaptcha!')

    if recaptcha_verified:
        next = check_email_status(email)
        if next == 'new':
            # Workaround for failed earlier sync of user to userdb: Remove any signup_user with this e-mail address.
            remove_users_with_mail_address(email)
            send_verification_mail(email)
            return success_response(payload=dict(next=next),
                                    message=SignupMsg.reg_new)

        elif next == 'resend-code':
            return {'next': next}

        elif next == 'address-used':
            current_app.stats.count(name='address_used_error')
            return error_response(payload=dict(next=next),
                                  message=SignupMsg.email_used)

    return error_response(message=SignupMsg.no_recaptcha)
コード例 #11
0
def _do_action():
    action_type = session.get('current_plugin')
    if not action_type:
        abort(403)

    plugin_obj = current_app.plugins[action_type]()
    old_format = 'user_oid' in session['current_action']
    action = Action(data=session['current_action'], old_format=old_format)
    try:
        data = plugin_obj.perform_step(action)
    except plugin_obj.ActionError as exc:
        return _aborted(action, exc)
    except plugin_obj.ValidationError as exc:
        errors = exc.args[0]
        current_app.logger.info(
            'Validation error {} for step {} of action {}'.format(
                errors, session['current_step'], action))
        session['current_step'] -= 1
        return error_response(payload={'errors': errors},
                              message=CommonMsg.form_errors)

    eppn = session.get('user_eppn')
    if session['total_steps'] == session['current_step']:
        current_app.logger.info(
            'Finished pre-login action {} for eppn {}'.format(
                action.action_type, eppn))
        return success_response(payload=dict(data=data),
                                message=ActionsMsg.action_completed)

    current_app.logger.info(
        'Performed step {} for action {} for eppn {}'.format(
            action.action_type, session['current_step'], eppn))
    session['current_step'] += 1
    return success_response(payload={'data': data}, message=None)
コード例 #12
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)
コード例 #13
0
def init_reset_pw(email: str) -> FluxData:
    """
    View that receives an email address to initiate a reset password process.
    It returns a message informing of the result of the operation.

    Preconditions required for the call to succeed:
    * There is a valid user corresponding to the received email address.

    As side effects, this view will:
    * Create a PasswordResetEmailState in the password_reset_state_db
      (holding the email address, the eppn of the user associated to the
      email address in the central userdb, and a freshly generated random hash
      as an identifier code for the created state);
    * Email the generated code to the received email address.

    The operation can fail due to:
    * The email address does not correspond to any valid user in the central db;
    * There is some problem sending the email.
    """
    current_app.logger.info(f'Trying to send password reset email to {email}')
    try:
        send_password_reset_mail(email)
    except BadCode as error:
        current_app.logger.error(
            f'Sending password reset e-mail for {email} failed: {error}')
        return error_response(message=error.msg)

    return success_response(message=ResetPwMsg.send_pw_success)
コード例 #14
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)
コード例 #15
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)}
コード例 #16
0
def verify(user, code, number):
    """
    view to mark one of the (unverified) phone numbers of the logged in user
    as verified.

    Returns a listing of  all phones for the logged in user.
    """
    proofing_user = ProofingUser.from_user(user, current_app.private_userdb)
    current_app.logger.debug(
        'Trying to save phone number {} as verified'.format(number))

    db = current_app.proofing_statedb
    try:
        state = db.get_state_by_eppn_and_mobile(proofing_user.eppn, number)
        timeout = current_app.config.phone_verification_timeout
        if state.is_expired(timeout):
            current_app.logger.info(
                "Proofing state is expired. Removing the state.")
            current_app.logger.debug("Proofing state: {!r}".format(state))
            current_app.proofing_statedb.remove_state(state)
            return error_response(message=PhoneMsg.code_invalid)
    except DocumentDoesNotExist:
        current_app.logger.info(
            "Could not find proofing state for number {}".format(number))
        return error_response(message=PhoneMsg.unknown_phone)

    if code == state.verification.verification_code:
        try:
            verify_phone_number(state, proofing_user)
            current_app.logger.info('Phone number successfully verified')
            current_app.logger.debug('Phone number: {}'.format(number))
            phones = {
                'phones': proofing_user.phone_numbers.to_list_of_dicts(),
            }
            return success_response(payload=phones,
                                    message=PhoneMsg.verify_success)
        except UserOutOfSync:
            current_app.logger.info(
                'Could not confirm phone number, data out of sync')
            current_app.logger.debug('Phone number: {}'.format(number))
            return error_response(message=CommonMsg.out_of_sync)
    current_app.logger.info("Invalid verification code")
    current_app.logger.debug("Proofing state: {!r}".format(state))
    return error_response(message=PhoneMsg.code_invalid)
コード例 #17
0
def config_reset_pw(code: str) -> FluxData:
    """
    View that receives an emailed reset password code and returns the
    configuration needed for the reset password form.

    Preconditions required for the call to succeed:
    * A PasswordResetEmailState object in the password_reset_state_db
      keyed by the received code.

    The configuration returned (in case of success) will include:
    * The received code;
    * A newly generated suggested password;
    * In case the user corresponding to the email address has verified phone
      numbers, these will be sent (masked) to allow the user to use extra
      security. (If the user does not use extra security, any verified NIN or
      phone number will be unverified upon resetting the password).

    As side effects, this view will:
    * Create a MailAddressProofing element in the proofing_log;
    * Set the email_code.is_verified flag in the PasswordResetEmailState
      object;
    * Set a hash of the generated password in the session.

    This operation may fail due to:
    * The code does not correspond to a valid state in the db;
    * The code has expired;
    * No valid user corresponds to the eppn stored in the state.
    """
    current_app.logger.info(f'Configuring password reset form for {code}')
    try:
        state = get_pwreset_state(code)
    except BadCode as e:
        return error_response(message=e.msg)

    verify_email_address(state)

    new_password = generate_suggested_password()
    session.reset_password.generated_password_hash = hash_password(
        new_password)

    user = current_app.central_userdb.get_user_by_eppn(state.eppn)
    alternatives = get_extra_security_alternatives(user, SESSION_PREFIX)
    state.extra_security = alternatives
    current_app.password_reset_state_db.save(state)

    return success_response(payload={
        'csrf_token': session.get_csrf_token(),
        'suggested_password': new_password,
        'email_code': state.email_code.code,
        'email_address': state.email_address,
        'extra_security': mask_alternatives(alternatives),
        'password_entropy': current_app.config.password_entropy,
        'password_length': current_app.config.password_length,
        'password_service_url': current_app.config.password_service_url,
        'zxcvbn_terms': get_zxcvbn_terms(state.eppn),
    }, )
コード例 #18
0
def verify_link(code: str) -> FluxData:
    try:
        user = verify_email_code(code)
    except CodeDoesNotExist:
        return error_response(payload=dict(status='unknown-code'),
                              message=SignupMsg.unknown_code)

    except AlreadyVerifiedException:
        return error_response(payload=dict(status='already-verified'),
                              message=SignupMsg.already_verified)

    except ProofingLogFailure:
        return error_response(message=CommonMsg.temp_problem)

    except EduIDUserDBError:
        return error_response(payload=dict(status='unknown-code'),
                              message=SignupMsg.unknown_code)

    return complete_registration(user)
コード例 #19
0
def resend_code(user, email):
    current_app.logger.debug('Trying to send new verification code for email '
                             'address {} for user {}'.format(email, user))

    if not user.mail_addresses.find(email):
        current_app.logger.debug('Unknown email {!r} in resend_code_action,'
                                 ' user {}'.format(email, user))
        return error_response(message=CommonMsg.out_of_sync)

    sent = send_verification_code(email, user)
    if not sent:
        return error_response(message=EmailMsg.still_valid_code)

    current_app.logger.debug('New verification code sent to '
                             'address {} for user {}'.format(email, user))
    current_app.stats.count(name='email_resend_code', value=1)

    emails = {'emails': user.mail_addresses.to_list_of_dicts()}
    return success_response(payload=emails, message=EmailMsg.code_sent)
コード例 #20
0
def verify(user, code, email):
    """
    """
    proofing_user = ProofingUser.from_user(user, current_app.private_userdb)
    current_app.logger.debug(
        'Trying to save email address {} as verified'.format(email))

    db = current_app.proofing_statedb
    try:
        state = db.get_state_by_eppn_and_email(proofing_user.eppn, email)
        timeout = current_app.config.email_verification_timeout
        if state.is_expired(timeout):
            current_app.logger.info(
                "Verification code is expired. Removing the state")
            current_app.logger.debug("Proofing state: {}".format(state))
            current_app.proofing_statedb.remove_state(state)
            return error_response(message=EmailMsg.invalid_code)
    except DocumentDoesNotExist:
        current_app.logger.info(
            'Could not find proofing state for email {}'.format(email))
        return error_response(message=EmailMsg.unknown_email)

    if code == state.verification.verification_code:
        try:
            verify_mail_address(state, proofing_user)
            current_app.logger.info('Email successfully verified')
            current_app.logger.debug('Email address: {}'.format(email))
            emails = {
                'emails': proofing_user.mail_addresses.to_list_of_dicts(),
            }
            email_list = EmailListPayload().dump(emails)
            return success_response(payload=email_list,
                                    message=EmailMsg.verify_success)
        except UserOutOfSync:
            current_app.logger.info(
                'Could not confirm email, data out of sync')
            current_app.logger.debug('Mail address: {}'.format(email))
            return error_response(message=CommonMsg.out_of_sync)
    current_app.logger.info("Invalid verification code")
    current_app.logger.debug("Email address: {}".format(
        state.verification.email))
    return error_response(message=EmailMsg.invalid_code)
コード例 #21
0
def _aborted(action, exc):
    eppn = session.get('user_eppn')
    current_app.logger.info(u'Aborted pre-login action {} for eppn {}, '
                            u'reason: {}'.format(action.action_type, eppn,
                                                 exc.args[0]))
    if exc.remove_action:
        aid = action.action_id
        msg = 'Removing faulty action with id '
        current_app.logger.info(msg + str(aid))
        current_app.actions_db.remove_action_by_id(aid)
    return error_response(message=exc.args[0])
コード例 #22
0
ファイル: security.py プロジェクト: lamlion/eduid-webapp
def remove_nin(user, nin):
    security_user = SecurityUser.from_user(user, current_app.private_userdb)
    current_app.logger.info('Removing NIN from user')
    current_app.logger.debug('NIN: {}'.format(nin))

    nin_obj = security_user.nins.find(nin)
    if nin_obj and nin_obj.is_verified:
        current_app.logger.info('NIN verified. Will not remove it.')
        return error_response(message=SecurityMsg.rm_verified)

    try:
        remove_nin_from_user(security_user, nin)
        return success_response(
            payload=dict(nins=security_user.nins.to_list_of_dicts()),
            message=SecurityMsg.rm_success)
    except AmTaskFailed as e:
        current_app.logger.error('Removing nin from user failed')
        current_app.logger.debug(f'NIN: {nin}')
        current_app.logger.error('{}'.format(e))
        return error_response(message=CommonMsg.temp_problem)
コード例 #23
0
 def to_response(self):
     """ Create a response with information about the users current proofing state (or an error)."""
     if self.error:
         return error_response(message=self.message)
     return success_response(
         {
             'letter_sent': self.sent,
             'letter_expires': self.expires,
             'letter_expired': self.is_expired,
         },
         message=self.message,
     )
コード例 #24
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)
コード例 #25
0
def sign(user):
    user_u2f_tokens = user.credentials.filter(U2F)
    if not user_u2f_tokens.count:
        current_app.logger.error('Found no U2F token for user.')
        return error_response(message=SecurityMsg.no_u2f)

    registered_keys = credentials_to_registered_keys(user_u2f_tokens)
    challenge = begin_authentication(current_app.config.u2f_app_id,
                                     registered_keys)
    session['_u2f_challenge_'] = challenge.json
    current_app.stats.count(name='u2f_sign')
    return U2FSignResponseSchema().load(challenge.data_for_client)
コード例 #26
0
def accept_invite(user: User, group_identifier: UUID, email_address: str,
                  role: GroupRole) -> FluxData:
    # Check that the current user has verified the invited email address
    user_email_address = user.mail_addresses.find(email_address)
    if not user_email_address or not user_email_address.is_verified:
        current_app.logger.error(
            f'User has not verified email address: {email_address}')
        return error_response(
            message=GroupManagementMsg.mail_address_not_verified)

    invite_state = current_app.invite_state_db.get_state(
        group_scim_id=str(group_identifier),
        email_address=email_address,
        role=role,
        raise_on_missing=False)
    if not invite_state:
        current_app.logger.error(
            f'Invite for group {group_identifier} does not exist')
        return error_response(message=GroupManagementMsg.invite_not_found)

    # Invite exists and current user is the one invited
    scim_user = get_or_create_scim_user_by_eppn(user.eppn)

    group = current_app.scimapi_groupdb.get_group_by_scim_id(
        invite_state.group_scim_id)
    if not group:
        current_app.logger.error(
            f'Group with scim_id {invite_state.group_scim_id} not found')
        return error_response(message=GroupManagementMsg.group_not_found)

    # Try to add user to group
    try:
        accept_group_invitation(scim_user, group, invite_state)
    except EduIDDBError:
        return error_response(message=CommonMsg.temp_problem)

    current_app.invite_state_db.remove_state(invite_state)
    current_app.stats.count(name=f'invite_accepted_{invite_state.role.value}')
    return incoming_invites()
コード例 #27
0
def get_config():
    try:
        action_type = session['current_plugin']
    except KeyError:
        abort(403)
    plugin_obj = current_app.plugins[action_type]()
    action = Action(data=session['current_action'])
    try:
        config = plugin_obj.get_config_for_bundle(action)
        config['csrf_token'] = session.new_csrf_token()
        return config
    except plugin_obj.ActionError as exc:
        return error_response(message=exc.args[0])
コード例 #28
0
ファイル: group.py プロジェクト: lamlion/eduid-webapp
def delete_group(user: User, group_identifier: UUID) -> FluxData:
    scim_user = get_scim_user_by_eppn(user.eppn)
    if not scim_user:
        current_app.logger.error('User does not exist in scimapi_userdb')
        return error_response(message=GroupManagementMsg.user_does_not_exist)

    if not is_owner(scim_user, group_identifier):
        current_app.logger.error(
            f'User is not owner of group with scim_id: {group_identifier}')
        return error_response(message=GroupManagementMsg.user_not_owner)

    group = current_app.scimapi_groupdb.get_group_by_scim_id(
        scim_id=str(group_identifier))
    if group and current_app.scimapi_groupdb.remove_group(group):
        # Remove outstanding invitations to the group
        for state in current_app.invite_state_db.get_states_by_group_scim_id(
                str(group_identifier), raise_on_missing=False):
            current_app.invite_state_db.remove_state(state)
        current_app.logger.info(
            f'Deleted ScimApiGroup with scim_id: {group.scim_id}')
        current_app.stats.count(name='group_deleted')
    return get_groups()
コード例 #29
0
def enroll(user):
    user_u2f_tokens = user.credentials.filter(U2F)
    if user_u2f_tokens.count >= current_app.config.u2f_max_allowed_tokens:
        current_app.logger.error(
            'User tried to register more than {} tokens.'.format(
                current_app.config.u2f_max_allowed_tokens))
        return error_response(message=SecurityMsg.max_tokens)
    registered_keys = credentials_to_registered_keys(user_u2f_tokens)
    enrollment = begin_registration(current_app.config.u2f_app_id,
                                    registered_keys)
    session['_u2f_enroll_'] = enrollment.json
    current_app.stats.count(name='u2f_token_enroll')
    return U2FEnrollResponseSchema().load(enrollment.data_for_client)
コード例 #30
0
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 error_response(message=PhoneMsg.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 error_response(message=CommonMsg.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()}
    return success_response(payload=phones, message=PhoneMsg.removal_success)