Esempio n. 1
0
def verify_email():
    """Target for email verification links.

    The token ensures that this view is only accessible from a verification email.
    """
    token = request.args.get('token')
    try:
        login_request, challenge, brand, params = decode_token(
            token, 'account.verify_email')

        email = params.get('email')
        try:
            user_id = validate_email_verification(email)
            update_user_verified(user_id, True)
            flash("Your email address has been verified.")

            complete_token = encode_token('account.verify_email_complete',
                                          challenge,
                                          brand,
                                          user_id=user_id)
            redirect_to = url_for('.verify_email_complete',
                                  token=complete_token)

        except x.ODPIdentityError as e:
            # any validation error => reject login
            redirect_to = hydra_admin.reject_login_request(
                challenge, e.error_code, e.error_description)

        return redirect(redirect_to)

    except x.HydraAdminError as e:
        return hydra_error_page(e)
Esempio n. 2
0
def login():
    """
    Implements the login provider component of the Hydra login workflow.
    Hydra redirects to this endpoint based on the ``URLS_LOGIN`` environment
    variable configured on the Hydra server.
    """
    try:
        challenge = request.args.get('login_challenge')
        login_request = hydra_admin.get_login_request(challenge)
        mode = LoginMode.from_login_request(login_request)
        brand = Brand.from_login_request(login_request).value

        if mode == LoginMode.LOGIN:
            target_endpoint = 'login.login'
        elif mode == LoginMode.SIGNUP:
            target_endpoint = 'signup.signup'
        else:
            raise ValueError

        token = encode_token('login', challenge, brand)
        redirect_to = url_for(target_endpoint, token=token)
        return redirect(redirect_to)

    except x.HydraAdminError as e:
        return hydra_error_page(e)
Esempio n. 3
0
def signup():
    """User signup view.

    The token ensures that we can only access this view in the context
    of the Hydra login workflow.
    """
    token = request.args.get('token')
    try:
        # the token scope 'login' here is correct - it enables us to easily
        # switch between login and signup using the same token
        login_request, challenge, brand, params = decode_token(token, 'login')

        form = SignupForm()
        try:
            if request.method == 'GET':
                # if the user is already authenticated with Hydra, their user id is
                # associated with the login challenge; we cannot then associate a new
                # user id with the same login challenge
                authenticated = login_request['skip']
                if authenticated:
                    raise x.ODPSignupAuthenticatedUser

            else:  # POST
                if form.validate():
                    email = form.email.data
                    password = form.password.data
                    name = form.name.data
                    try:
                        create_user_account(email, password, name)

                        # the signup (and login) is completed via email verification
                        send_verification_email(email, name, challenge, brand)
                        verify_token = encode_token('signup.verify', challenge, brand, email=email, name=name)
                        redirect_to = url_for('.verify', token=verify_token)

                        return redirect(redirect_to)

                    except x.ODPEmailInUse:
                        form.email.errors.append("The email address is already associated with a user account.")

                    except x.ODPPasswordComplexityError:
                        form.password.errors.append("The password does not meet the minimum complexity requirements.")
                        flash(password_complexity_description(), category='info')

            return render_template('signup.html', form=form, token=token, brand=brand, enable_google=config.GOOGLE.ENABLE)

        except x.ODPIdentityError as e:
            # any other validation error (e.g. user already authenticated) => reject login
            redirect_to = hydra_admin.reject_login_request(challenge, e.error_code, e.error_description)
            return redirect(redirect_to)

    except x.HydraAdminError as e:
        return hydra_error_page(e)
Esempio n. 4
0
def reset_password():
    """Target for password reset links.

    The token ensures that this view is only accessible from a password reset email.
    """
    token = request.args.get('token')
    try:
        login_request, challenge, brand, params = decode_token(
            token, 'account.reset_password')

        form = ResetPasswordForm()
        email = params.get('email')

        if request.method == 'POST':
            if form.validate():
                password = form.password.data
                redirect_to = None
                try:
                    user_id = validate_password_reset(email, password)
                    update_user_password(user_id, password)
                    flash("Your password has been changed.")

                    complete_token = encode_token(
                        'account.reset_password_complete',
                        challenge,
                        brand,
                        user_id=user_id)
                    redirect_to = url_for('.reset_password_complete',
                                          token=complete_token)

                except x.ODPPasswordComplexityError:
                    form.password.errors.append(
                        "The password does not meet the minimum complexity requirements."
                    )
                    flash(password_complexity_description(), category='info')

                except x.ODPIdentityError as e:
                    # any other validation error => reject login
                    redirect_to = hydra_admin.reject_login_request(
                        challenge, e.error_code, e.error_description)

                if redirect_to:
                    return redirect(redirect_to)

        return render_template('reset_password.html',
                               form=form,
                               token=token,
                               brand=brand)

    except x.HydraAdminError as e:
        return hydra_error_page(e)
Esempio n. 5
0
def send_password_reset_email(email, name, challenge, brand):
    """Send a password reset email.

    :param email: the email address
    :param name: the user's full name
    :param challenge: the Hydra login challenge
    :param brand: branding identifier
    """
    try:
        token = encode_token('account.reset_password',
                             challenge,
                             brand,
                             email=email)
        context = {
            'url': url_for('account.reset_password',
                           token=token,
                           _external=True),
            'name': name or '',
            'brand': brand,
        }
        msg = Message(
            subject=render_template('email/reset_password_subject.txt',
                                    **context),
            body=render_template('email/reset_password.txt', **context),
            html=render_template('email/reset_password.html', **context),
            recipients=[email],
            sender=("SAEON", "*****@*****.**"),
            reply_to=("SAEON", "*****@*****.**"),
        )
        mail.send(msg)
        flash("A password reset link has been sent to your email address.")
    except Exception as e:
        current_app.logger.error(
            "Error sending password reset email to {}: {}".format(email, e))
        flash("There was a problem sending the password reset email.",
              category='error')
Esempio n. 6
0
def login():
    """User login view.

    The token ensures that we can only access this view in the context
    of the Hydra login workflow.
    """
    token = request.args.get('token')
    try:
        login_request, challenge, brand, params = decode_token(token, 'login')

        user_id = None
        error = None
        form = LoginForm()

        if request.method == 'GET':
            authenticated = login_request['skip']  # indicates whether the user is already authenticated with Hydra

            # if already authenticated, we'll wind up with either a user_id or an error
            if authenticated:
                user_id = login_request['subject']
                try:
                    validate_auto_login(user_id)
                except x.ODPIdentityError as e:
                    # any validation error => reject login
                    user_id = None
                    error = e

            # if not authenticated, we'll display the login form

        else:  # POST
            if form.validate():
                email = form.email.data
                password = form.password.data
                try:
                    user_id = validate_user_login(email, password)

                except x.ODPUserNotFound:
                    form.email.errors.append("The email address is not associated with any user account.")

                except x.ODPNoPassword:
                    form.email.errors.append("Please click the 'Log in via Google' button.")

                except x.ODPIncorrectPassword:
                    form.email.errors.append("The email address and password do not match.")

                except x.ODPEmailNotVerified:
                    # the login is completed via email verification
                    name = get_user_profile_by_email(email)['name']
                    send_verification_email(email, name, challenge, brand)
                    verify_token = encode_token('login.verify', challenge, brand, email=email, name=name)
                    return redirect(url_for('.verify', token=verify_token))

                except x.ODPIdentityError as e:
                    # any other validation error (e.g. account locked/disabled) => reject login
                    error = e

        if user_id:
            redirect_to = hydra_admin.accept_login_request(challenge, user_id)
        elif error:
            redirect_to = hydra_admin.reject_login_request(challenge, error.error_code, error.error_description)
        else:
            return render_template('login.html', form=form, token=token, brand=brand, enable_google=config.GOOGLE.ENABLE)

        return redirect(redirect_to)

    except x.HydraAdminError as e:
        return hydra_error_page(e)