Ejemplo n.º 1
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)
Ejemplo n.º 2
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)
Ejemplo n.º 3
0
def get_groups(user: User) -> FluxData:
    scim_user = get_scim_user_by_eppn(user.eppn)
    if not scim_user:
        current_app.logger.info('User does not exist in scimapi_userdb')
        # As the user does not exist return empty group lists
        return success_response(payload={})
    payload = get_all_group_data(scim_user)
    return success_response(payload=payload)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
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)
Ejemplo n.º 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)
Ejemplo n.º 8
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)
Ejemplo n.º 9
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)
Ejemplo n.º 10
0
def resend_email_verification(email):
    """
    The user has not yet verified the email address.
    Send a verification message to the address so it can be verified.
    """
    current_app.logger.debug("Resend email confirmation to {!s}".format(email))
    send_verification_mail(email)
    current_app.stats.count(name='resend_code')
    return success_response(message=SignupMsg.resent_success)
Ejemplo n.º 11
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),
    }, )
Ejemplo n.º 12
0
def get_suggested(user) -> FluxData:
    """
    View to get a suggested password for the logged user.
    """
    current_app.logger.debug(f'Sending new generated password for {user}')
    password = generate_suggested_password()

    session.reset_password.generated_password_hash = hash_password(password)

    return success_response(payload={'suggested_password': password},
                            message=None)
Ejemplo n.º 13
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)
Ejemplo n.º 14
0
def get_all_data(user: User) -> FluxData:
    payload = {}
    scim_user = get_scim_user_by_eppn(user.eppn)
    if scim_user:
        # The user can only have group data if there is a scim user
        payload.update(get_all_group_data(scim_user))
    # Update payload with incoming and outgoing invites
    payload.update({
        'incoming': get_incoming_invites(user),
        'outgoing': get_outgoing_invites(user)
    })
    return success_response(payload=payload)
Ejemplo n.º 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)
Ejemplo n.º 16
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,
     )
Ejemplo n.º 17
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)
Ejemplo n.º 18
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)
Ejemplo n.º 19
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)
Ejemplo n.º 20
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)
Ejemplo n.º 21
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)
Ejemplo n.º 22
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)
Ejemplo n.º 23
0
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)
Ejemplo n.º 24
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)
Ejemplo n.º 25
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)
Ejemplo n.º 26
0
def set_new_pw(code: str, password: str) -> FluxData:
    """
    View that receives an emailed reset password code and a password, and sets
    the password as credential for the user, with no extra security.

    Preconditions required for the call to succeed:
    * A PasswordResetEmailState object in the password_reset_state_db
      keyed by the received code.
    * 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;
    * Unverify any verified phone number or NIN the user previously had.

    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;
    * Communication problems with the VCCS backend;
    * Synchronization problems with the central user db.
    """
    if not password or not code:
        return error_response(message=ResetPwMsg.missing_data)

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

    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)
Ejemplo n.º 27
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)
Ejemplo n.º 28
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)
Ejemplo n.º 29
0
def set_new_pw_extra_security_token(
    code: str,
    password: str,
    tokenResponse: Optional[str] = None,
    authenticatorData: Optional[str] = None,
    clientDataJSON: Optional[str] = None,
    credentialId: Optional[str] = None,
    signature: Optional[str] = None,
) -> FluxData:
    """
    View that receives an emailed reset password code, hw token data,
    and a password, and sets the password as credential for the user, with
    extra security.

    Preconditions required for the call to succeed:
    * A PasswordResetEmailAndTokenState object in the password_reset_state_db
      keyed by the received code.
    * 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.
    """
    data = _load_data(code, password)
    if data.error:
        return error_response(message=data.error)

    # Process POSTed data
    success = False
    if tokenResponse:
        # CTAP1/U2F
        token_response = request.get_json().get('tokenResponse', '')
        current_app.logger.debug(f'U2F token response: {token_response}')

        _challenge = session.get(SESSION_PREFIX + '.u2f.challenge')
        if not isinstance(_challenge, bytes):
            raise TypeError(
                f'U2F challenge in session is not bytes {repr(_challenge)}')
        current_app.logger.debug(f'Challenge: {_challenge!r}')

        result = fido_tokens.verify_u2f(data.user, _challenge, token_response)

        if result is not None:
            success = result['success']

    elif not success and authenticatorData:
        # CTAP2/Webauthn
        try:
            result = fido_tokens.verify_webauthn(
                data.user,
                dict(
                    credentialId=credentialId,
                    clientDataJSON=clientDataJSON,
                    authenticatorData=authenticatorData,
                    signature=signature,
                ),
                SESSION_PREFIX,
            )
        except fido_tokens.VerificationProblem:
            pass
        else:
            success = result['success']

    else:
        current_app.logger.error(
            f'Neither U2F nor Webauthn data in request to authn {data.user}')

    if not success:
        return error_response(message=ResetPwMsg.fido_token_fail)

    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)
Ejemplo n.º 30
0
def choose_extra_security_phone(code: str, phone_index: int) -> FluxData:
    """
    View called when the user chooses extra security (she can do that when she
    has some verified phone number). It receives an emailed reset password code
    and an index for one of the verified phone numbers, and returns info on the
    result of the attempted operation.

    Preconditions required for the call to succeed:
    * A PasswordResetEmailState object in the password_reset_state_db
      keyed by the received code.
    * A flag in said state object indicating that the emailed code has already
      been verified.
    * The user referenced in the state has at least phone_index (number) of
      verified phone numbers.

    As side effects, this operation will:
    * Copy the data in the PasswordResetEmailState to a new
      PasswordResetEmailAndPhoneState;
    * Create a new random hash as identifier code for the new state;
    * Store this code in the new state;
    * Send an SMS message with the code to the phone number corresponding to
      the received phone_index;

    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;
    * Problems sending the SMS message
    """
    try:
        state = get_pwreset_state(code)
    except BadCode as e:
        return error_response(message=e.msg)

    if isinstance(state, ResetPasswordEmailAndPhoneState):
        now = int(time.time())
        if not isinstance(state.modified_ts, datetime):
            raise TypeError(
                f'Modified timestamp in state is not a datetime ({repr(state.modified_ts)})'
            )
        if int(state.modified_ts.timestamp()
               ) > now - current_app.config.throttle_sms_seconds:
            current_app.logger.info(
                f'Throttling reset password SMSs for: {state.eppn}')
            return error_response(message=ResetPwMsg.send_sms_throttled)

    current_app.logger.info(
        f'Password reset: choose_extra_security for user with eppn {state.eppn}'
    )

    # Check that the email code has been validated
    if not state.email_code.is_verified:
        current_app.logger.info(
            f'User with eppn {state.eppn} has not verified their email address'
        )
        return error_response(message=ResetPwMsg.email_not_validated)

    phone_number = state.extra_security['phone_numbers'][phone_index]
    current_app.logger.info(
        f'Trying to send password reset sms to user with eppn {state.eppn}')
    try:
        send_verify_phone_code(state, phone_number["number"])
    except MsgTaskFailed as e:
        current_app.logger.error(f'Sending sms failed: {e}')
        return error_response(message=ResetPwMsg.send_sms_failure)

    current_app.stats.count(name='reset_password_extra_security_phone')
    return success_response(message=ResetPwMsg.send_sms_success)