Esempio n. 1
0
def reset_email(user, kwargs):
    logout_internal()
    resetreq = PasswordResetRequest.query.filter_by(user=user, reset_code=kwargs["secret"]).first()
    if not resetreq:
        return render_message(title="Invalid reset link", message=Markup("The reset link you clicked on is invalid."))
    if resetreq.created_at < datetime.utcnow() - timedelta(days=1):
        # Reset code has expired (> 24 hours). Delete it
        db.session.delete(resetreq)
        db.session.commit()
        return render_message(title="Expired reset link", message=Markup("The reset link you clicked on has expired."))

    # Reset code is valid. Now ask user to choose a new password
    form = PasswordResetForm()
    if form.validate_on_submit():
        user.password = form.password.data
        db.session.delete(resetreq)
        db.session.commit()
        return render_message(
            title="Password reset complete",
            message=Markup(
                'Your password has been reset. You may now <a href="%s">login</a> with your new password.'
                % escape(url_for("login"))
            ),
        )
    return render_form(
        form=form,
        title="Reset password",
        formid="reset",
        submit="Reset password",
        message=Markup("Hello, <strong>%s</strong>. You may now choose a new password." % user.fullname),
        ajax=True,
    )
Esempio n. 2
0
def reset_email(user, kwargs):
    resetreq = PasswordResetRequest.query.filter_by(user=user, reset_code=kwargs['secret']).first()
    if not resetreq:
        return render_message(title=_("Invalid reset link"),
            message=_(u"The reset link you clicked on is invalid"))
    if resetreq.created_at < utcnow() - timedelta(days=1):
        # Reset code has expired (> 24 hours). Delete it
        db.session.delete(resetreq)
        db.session.commit()
        return render_message(title=_("Expired reset link"),
            message=_(u"The reset link you clicked on has expired"))

    # Logout *after* validating the reset request to prevent DoS attacks on the user
    logout_internal()
    db.session.commit()
    # Reset code is valid. Now ask user to choose a new password
    form = PasswordResetForm()
    form.edit_user = user
    if form.validate_on_submit():
        user.password = form.password.data
        db.session.delete(resetreq)
        db.session.commit()
        return render_message(title=_("Password reset complete"), message=Markup(
            _(u"Your password has been reset. You may now <a href=\"{loginurl}\">login</a> with your new password.").format(
                loginurl=escape(url_for('.login')))))
    return render_form(form=form, title=_("Reset password"), formid='reset', submit=_("Reset password"),
        message=Markup(_(u"Hello, <strong>{fullname}</strong>. You may now choose a new password.").format(
            fullname=escape(user.fullname))),
        ajax=False)
Esempio n. 3
0
def reset_email(user, kwargs):
    resetreq = PasswordResetRequest.query.filter_by(user=user, reset_code=kwargs['secret']).first()
    if not resetreq:
        return render_message(title=_("Invalid reset link"),
            message=_(u"The reset link you clicked on is invalid"))
    if resetreq.created_at < datetime.utcnow() - timedelta(days=1):
        # Reset code has expired (> 24 hours). Delete it
        db.session.delete(resetreq)
        db.session.commit()
        return render_message(title=_("Expired reset link"),
            message=_(u"The reset link you clicked on has expired"))

    # Logout *after* validating the reset request to prevent DoS attacks on the user
    logout_internal()
    db.session.commit()
    # Reset code is valid. Now ask user to choose a new password
    form = PasswordResetForm()
    form.edit_user = user
    if form.validate_on_submit():
        user.password = form.password.data
        db.session.delete(resetreq)
        db.session.commit()
        return render_message(title=_("Password reset complete"), message=Markup(
            _(u"Your password has been reset. You may now <a href=\"{loginurl}\">login</a> with your new password.").format(
                loginurl=escape(url_for('.login')))))
    return render_form(form=form, title=_("Reset password"), formid='reset', submit=_("Reset password"),
        message=Markup(_(u"Hello, <strong>{fullname}</strong>. You may now choose a new password.").format(
            fullname=escape(user.fullname))),
        ajax=False)
Esempio n. 4
0
def confirm_email(md5sum, secret):
    emailclaim = UserEmailClaim.query.filter_by(
        md5sum=md5sum, verification_code=secret).first()
    if emailclaim is not None:
        if 'verify' in emailclaim.permissions(g.user):
            useremail = emailclaim.user.add_email(
                emailclaim.email, primary=emailclaim.user.email is None)
            db.session.delete(emailclaim)
            for claim in UserEmailClaim.query.filter_by(
                    email=useremail.email).all():
                db.session.delete(claim)
            db.session.commit()
            return render_message(
                title="Email address verified",
                message=Markup(
                    "Hello %s! Your email address <code>%s</code> has now been verified."
                    % (escape(emailclaim.user.fullname), escape(
                        useremail.email))))
        else:
            return render_message(
                title="That was not for you",
                message=
                u"You’ve opened an email verification link that was meant for another user. "
                u"If you are managing multiple accounts, please login with the correct account "
                u"and open the link again.",
                code=403)
    else:
        return render_message(
            title="Expired confirmation link",
            message=
            "The confirmation link you clicked on is either invalid or has expired.",
            code=404)
Esempio n. 5
0
def profile_new():
    # Step 1: Get a list of organizations this user owns
    existing = Profile.query.filter(
        Profile.userid.in_(g.user.organizations_owned_ids())).all()
    existing_ids = [e.userid for e in existing]
    # Step 2: Prune list to organizations without a profile
    new_profiles = []
    for org in g.user.organizations_owned():
        if org['userid'] not in existing_ids:
            new_profiles.append((org['userid'], org['title']))
    if not new_profiles:
        return render_message(
            title=_(u"No organizations found"),
            message=Markup(
                _(u"You do not have any organizations that do not already have a Talkfunnel. "
                  u'Would you like to <a href="{link}">create a new organization</a>?'
                  ).format(link=lastuser.endpoint_url('/organizations/new'))))
    eligible_profiles = []
    for orgid, title in new_profiles:
        if Team.query.filter_by(orgid=orgid).first() is not None:
            eligible_profiles.append((orgid, title))
    if not eligible_profiles:
        return render_message(
            title=_(u"No organizations available"),
            message=
            _(u"To create a Talkfunnel for an organization, you must be the owner of the organization."
              ))

    # Step 3: Ask user to select organization
    form = NewProfileForm()
    form.profile.choices = eligible_profiles
    if request.method == 'GET':
        form.profile.data = new_profiles[0][0]
    if form.validate_on_submit():
        # Step 4: Make a profile
        org = [
            org for org in g.user.organizations_owned()
            if org['userid'] == form.profile.data
        ][0]
        profile = Profile(name=org['name'],
                          title=org['title'],
                          userid=org['userid'])
        db.session.add(profile)
        db.session.commit()
        flash(
            _(u"Created a profile for {profile}").format(
                profile=profile.title), "success")
        return render_redirect(profile.url_for('edit'), code=303)
    return render_form(
        form=form,
        title=_(u"Create a Talkfunnel for your organization..."),
        message=
        _(u"Talkfunnel is a free service while in beta. Sign up now to help us test the service."
          ),
        submit="Next",
        formid="profile_new",
        cancel_url=url_for('index'),
        ajax=False)
Esempio n. 6
0
def workspace_new():
    # Step 1: Get a list of organizations this user owns
    existing = Workspace.query.filter(
        Workspace.userid.in_(g.user.organizations_owned_ids())).all()
    existing_ids = [e.userid for e in existing]
    # Step 2: Prune list to organizations without a workspace
    new_workspaces = []
    for org in g.user.organizations_owned():
        if org['userid'] not in existing_ids:
            new_workspaces.append((org['userid'], org['title']))
    if not new_workspaces:
        return render_message(
            title=u"No organizations found",
            message=Markup(
                u"You do not have any organizations that do not already have a workspace. "
                u'Would you like to <a href="%s">create a new organization</a>?'
                % lastuser.endpoint_url('/organizations/new')))
    eligible_workspaces = []
    for orgid, title in new_workspaces:
        if Team.query.filter_by(orgid=orgid).first() is not None:
            eligible_workspaces.append((orgid, title))
    if not eligible_workspaces:
        return render_message(
            title=u"No organizations available",
            message=Markup(
                u"To create a workspace for an organization, you must first allow this app to "
                u"access the list of teams in your organization. "
                u'<a href="%s">Do that here</a>.' %
                lastuser.endpoint_url('/apps/' + lastuser.client_id)))

    # Step 3: Ask user to select organization
    form = NewWorkspaceForm()
    form.workspace.choices = eligible_workspaces
    if request.method == 'GET':
        form.workspace.data = new_workspaces[0][0]
    if form.validate_on_submit():
        # Step 4: Make a workspace
        org = [
            org for org in g.user.organizations_owned()
            if org['userid'] == form.workspace.data
        ][0]
        workspace = Workspace(name=org['name'],
                              title=org['title'],
                              userid=org['userid'],
                              currency=form.currency.data,
                              timezone=app.config.get('TIMEZONE', ''))
        db.session.add(workspace)
        db.session.commit()
        flash(u"Created a workspace for %s" % workspace.title, "success")
        return render_redirect(url_for('workspace_edit',
                                       workspace=workspace.name),
                               code=303)
    return render_form(form=form,
                       title="Create a workspace for your organization...",
                       submit="Next",
                       formid="workspace_new",
                       cancel_url=url_for('index'),
                       ajax=False)
Esempio n. 7
0
def profile_new():
    # Step 1: Get a list of organizations this user owns
    existing = Profile.query.filter(
        Profile.userid.in_(g.user.organizations_owned_ids())).all()
    existing_ids = [e.userid for e in existing]
    # Step 2: Prune list to organizations without a profile
    new_profiles = []
    for org in g.user.organizations_owned():
        if org['userid'] not in existing_ids:
            new_profiles.append((org['userid'], org['title']))
    if not new_profiles:
        return render_message(
            title=_(u"No organizations found"),
            message=Markup(
                _(u"You do not have any organizations that do not already have a Talkfunnel. "
                  u'Would you like to <a href="{link}">create a new organization</a>?'
                  ).format(link=lastuser.endpoint_url('/organizations/new'))))
    eligible_profiles = []
    for orgid, title in new_profiles:
        if Team.query.filter_by(orgid=orgid).first() is not None:
            eligible_profiles.append((orgid, title))
    if not eligible_profiles:
        return render_message(
            title=_(u"No organizations available"),
            message=
            _(u"To create a Talkfunnel for an organization, you must be the owner of the organization."
              ))

    # Step 3: Ask user to select organization
    form = NewProfileForm()
    form.profile.choices = eligible_profiles
    if request.method == 'GET':
        form.profile.data = new_profiles[0][0]
    if form.validate_on_submit():
        # Step 4: Make a profile
        org = [
            org for org in g.user.organizations_owned()
            if org['userid'] == form.profile.data
        ][0]
        profile = Profile(
            name=org['name'], title=org['title'], userid=org['userid'])
        db.session.add(profile)
        db.session.commit()
        flash(
            _(u"Created a profile for {profile}").format(
                profile=profile.title), "success")
        return render_redirect(profile.url_for('edit'), code=303)
    return render_form(
        form=form,
        title=_(u"Create a Talkfunnel for your organization..."),
        message=
        _(u"Talkfunnel is a free service while in beta. Sign up now to help us test the service."
          ),
        submit="Next",
        formid="profile_new",
        cancel_url=url_for('index'),
        ajax=False)
Esempio n. 8
0
def confirm_email(md5sum, secret):
    emailclaim = UserEmailClaim.query.filter_by(md5sum=md5sum, verification_code=secret).first()
    if emailclaim is not None:
        if "verify" in emailclaim.permissions(g.user):
            existing = UserEmail.query.filter(UserEmail.email.in_([emailclaim.email, emailclaim.email.lower()])).first()
            if existing is not None:
                claimed_email = emailclaim.email
                claimed_user = emailclaim.user
                db.session.delete(emailclaim)
                db.session.commit()
                if claimed_user != g.user:
                    return render_message(
                        title="Email address already claimed",
                        message=Markup(
                            "The email address <code>%s</code> has already been verified by another user."
                            % escape(claimed_email)
                        ),
                    )
                else:
                    return render_message(
                        title="Email address already verified",
                        message=Markup(
                            "Hello %s! Your email address <code>%s</code> has already been verified."
                            % (escape(claimed_user.fullname), escape(claimed_email))
                        ),
                    )

            useremail = emailclaim.user.add_email(emailclaim.email.lower(), primary=emailclaim.user.email is None)
            db.session.delete(emailclaim)
            for claim in UserEmailClaim.query.filter(
                UserEmailClaim.email.in_([useremail.email, useremail.email.lower()])
            ).all():
                db.session.delete(claim)
            db.session.commit()
            user_data_changed.send(g.user, changes=["email"])
            return render_message(
                title="Email address verified",
                message=Markup(
                    "Hello %s! Your email address <code>%s</code> has now been verified."
                    % (escape(emailclaim.user.fullname), escape(useremail.email))
                ),
            )
        else:
            return render_message(
                title="That was not for you",
                message=u"You’ve opened an email verification link that was meant for another user. "
                u"If you are managing multiple accounts, please login with the correct account "
                u"and open the link again.",
                code=403,
            )
    else:
        return render_message(
            title="Expired confirmation link",
            message="The confirmation link you clicked on is either invalid or has expired.",
            code=404,
        )
Esempio n. 9
0
def reset():
    # User wants to reset password
    # Ask for username or email, verify it, and send a reset code
    form = PasswordResetRequestForm()
    if getbool(request.args.get('expired')):
        message = _(u"Your password has expired. Please enter your username "
            "or email address to request a reset code and set a new password")
    else:
        message = None

    if request.method == 'GET':
        form.username.data = request.args.get('username')

    if form.validate_on_submit():
        username = form.username.data
        user = form.user
        if '@' in username and not username.startswith('@'):
            # They provided an email address. Send reset email to that address
            email = username
        else:
            # Send to their existing address
            # User.email is a UserEmail object
            email = unicode(user.email)
        if not email and user.emailclaims:
            email = user.emailclaims[0].email
        if not email:
            # They don't have an email address. Maybe they logged in via Twitter
            # and set a local username and password, but no email. Could happen.
            if len(user.externalids) > 0:
                extid = user.externalids[0]
                return render_message(title=_("Cannot reset password"), message=Markup(_(u"""
                    We do not have an email address for your account. However, your account
                    is linked to <strong>{service}</strong> with the id <strong>{username}</strong>.
                    You can use that to login.
                    """).format(service=login_registry[extid.service].title, username=extid.username or extid.userid)))
            else:
                return render_message(title=_("Cannot reset password"), message=Markup(_(
                    u"""
                    We do not have an email address for your account and therefore cannot
                    email you a reset link. Please contact
                    <a href="mailto:{email}">{email}</a> for assistance.
                    """).format(email=escape(current_app.config['SITE_SUPPORT_EMAIL']))))
        resetreq = PasswordResetRequest(user=user)
        db.session.add(resetreq)
        send_password_reset_link(email=email, user=user, secret=resetreq.reset_code)
        db.session.commit()
        return render_message(title=_("Reset password"), message=_(u"""
            We sent a link to reset your password to your email address: {masked_email}.
            Please check your email. If it doesn’t arrive in a few minutes,
            it may have landed in your spam or junk folder.
            The reset link is valid for 24 hours.
            """.format(masked_email=mask_email(email))))

    return render_form(form=form, title=_("Reset password"), message=message, submit=_("Send reset code"), ajax=False)
Esempio n. 10
0
def reset():
    # User wants to reset password
    # Ask for username or email, verify it, and send a reset code
    form = PasswordResetRequestForm()
    if getbool(request.args.get('expired')):
        message = _(u"Your password has expired. Please enter your username "
            "or email address to request a reset code and set a new password")
    else:
        message = None

    if request.method == 'GET':
        form.username.data = request.args.get('username')

    if form.validate_on_submit():
        username = form.username.data
        user = form.user
        if '@' in username and not username.startswith('@'):
            # They provided an email address. Send reset email to that address
            email = username
        else:
            # Send to their existing address
            # User.email is a UserEmail object
            email = unicode(user.email)
        if not email and user.emailclaims:
            email = user.emailclaims[0].email
        if not email:
            # They don't have an email address. Maybe they logged in via Twitter
            # and set a local username and password, but no email. Could happen.
            if len(user.externalids) > 0:
                extid = user.externalids[0]
                return render_message(title=_("Cannot reset password"), message=Markup(_(u"""
                    We do not have an email address for your account. However, your account
                    is linked to <strong>{service}</strong> with the id <strong>{username}</strong>.
                    You can use that to login.
                    """).format(service=login_registry[extid.service].title, username=extid.username or extid.userid)))
            else:
                return render_message(title=_("Cannot reset password"), message=Markup(_(
                    u"""
                    We do not have an email address for your account and therefore cannot
                    email you a reset link. Please contact
                    <a href="mailto:{email}">{email}</a> for assistance.
                    """).format(email=escape(current_app.config['SITE_SUPPORT_EMAIL']))))
        resetreq = PasswordResetRequest(user=user)
        db.session.add(resetreq)
        send_password_reset_link(email=email, user=user, secret=resetreq.reset_code)
        db.session.commit()
        return render_message(title=_("Reset password"), message=_(u"""
            We sent you an email with a link to reset your password.
            Please check your email. If it doesn’t arrive in a few minutes,
            it may have landed in your spam or junk folder.
            The reset link is valid for 24 hours.
            """))

    return render_form(form=form, title=_("Reset password"), message=message, submit=_("Send reset code"), ajax=False)
Esempio n. 11
0
def board_new():
    # Step 1: Get a list of organizations this user owns
    existing = Board.query.filter(Board.userid.in_(g.user.organizations_owned_ids())).all()
    existing_ids = [e.userid for e in existing]
    # Step 2: Prune list to organizations without a board
    new_boards = []
    for org in g.user.organizations_owned():
        if org['userid'] not in existing_ids:
            new_boards.append((org['userid'], org['title']))
    if not new_boards:
        return render_message(
            title=u"No organizations found",
            message=Markup(u"You do not have any organizations that do not already have a board. "
                u'Would you like to <a href="%s">create a new organization</a>?' %
                    lastuser.endpoint_url('/organizations/new')))
    # Step 3: Ask user to select organization
    form = NewBoardForm()
    form.board.choices = new_boards
    if request.method == 'GET':
        form.board.data = new_boards[0][0]
    if form.validate_on_submit():
        # Step 4: Make a board
        org = [org for org in g.user.organizations_owned() if org['userid'] == form.board.data][0]
        board = Board(name=org['name'], title=org['title'], userid=org['userid'])
        db.session.add(board)
        db.session.commit()
        flash(u"Created a board for %s" % board.title, 'success')
        return render_redirect(url_for('board_edit', board=board.name), code=303)
    return render_form(form=form, title="Create a board for your organization...", submit="Next",
        formid="board_new", cancel_url=url_for('index'), ajax=False)
Esempio n. 12
0
def video_remove(channel, playlist, video):
    """
    Remove video from playlist
    """
    if playlist not in video.playlists:
        # This video isn't in this playlist
        abort(404)

    # If this is the primary playlist for this video, refuse to remove it.
    if playlist == video.playlist:
        return render_message(
            title="Cannot remove",
            message=Markup(
                "Videos cannot be removed from their primary playlist. "
                '<a href="%s">Return to video</a>.' % video.url_for()))

    connection = PlaylistVideo.query.filter_by(
        playlist_id=playlist.id, video_id=video.id).first_or_404()

    return render_delete_sqla(
        connection,
        db,
        title="Confirm remove",
        message=u"Remove video '%s' from playlist '%s'?" %
        (video.title, playlist.title),
        success=u"You have removed video '%s' from playlist '%s'." %
        (video.title, playlist.title),
        next=playlist.url_for())
Esempio n. 13
0
def video_remove(channel, playlist, video, kwargs):
    """
    Remove video from playlist
    """
    if playlist not in video.playlists:
        # This video isn't in this playlist
        abort(404)

    if channel.userid not in g.user.user_organization_ids():
        # User doesn't own this playlist
        abort(403)

    if kwargs['video'] != video.url_name:
        # Video's URL has changed. Redirect user to prevent old/invalid names
        # showing in the URL
        return redirect(url_for('video_remove', channel=channel.name, playlist=playlist.name, video=video.url_name))

    # If this is the primary playlist for this video, refuse to remove it.
    if playlist == video.playlist:
        return render_message(title="Cannot remove",
            message=Markup("Videos cannot be removed from their primary playlist. "
                '<a href="%s">Return to video</a>.' % url_for('video_view',
                    channel=channel.name, playlist=playlist.name, video=video.url_name)))

    connection = PlaylistVideo.query.filter_by(playlist_id=playlist.id, video_id=video.id).first_or_404()

    return render_delete_sqla(connection, db, title="Confirm remove",
        message=u"Remove video '%s' from playlist '%s'?" % (video.title, playlist.title),
        success=u"You have removed video '%s' from playlist '%s'." % (video.title, playlist.title),
        next=url_for('playlist_view', channel=channel.name, playlist=playlist.name))
Esempio n. 14
0
def workspace_new():
    # Step 1: Get a list of organizations this user owns
    existing = Workspace.query.filter(Workspace.userid.in_(g.user.organizations_owned_ids())).all()
    existing_ids = [e.userid for e in existing]
    # Step 2: Prune list to organizations without a workspace
    new_workspaces = []
    for org in g.user.organizations_owned():
        if org['userid'] not in existing_ids:
            new_workspaces.append((org['userid'], org['title']))
    if not new_workspaces:
        return render_message(
            title=u"No organizations remaining",
            message=u"You do not have any organizations that do not yet have a workspace.")

    # Step 3: Ask user to select organization
    form = NewWorkspaceForm()
    form.workspace.choices = new_workspaces
    if form.validate_on_submit():
        # Step 4: Make a workspace
        org = [org for org in g.user.organizations_owned() if org['userid'] == form.workspace.data][0]
        workspace = Workspace(name=org['name'], title=org['title'], userid=org['userid'],
            currency=form.currency.data, fullname=form.fullname.data, address=form.address.data,
            cin=form.cin.data,pan=form.pan.data,tin=form.tin.data,tan=form.tan.data)
        db.session.add(workspace)
        db.session.commit()
        flash("Created new workspace for %s" % workspace.title, "success")
        return render_redirect(url_for('workspace_view', workspace=workspace.name), code=303)
    return render_form(form=form, title="Create a new organization workspace", submit="Create",
        formid="workspace_new", cancel_url=url_for('index'), ajax=False)
Esempio n. 15
0
def confirm_email(md5sum, secret):
    emailclaim = UserEmailClaim.query.filter_by(md5sum=md5sum, verification_code=secret).first()
    if emailclaim is not None:
        if 'verify' in emailclaim.permissions(current_auth.user):
            existing = UserEmail.query.filter(UserEmail.email.in_([emailclaim.email, emailclaim.email.lower()])).first()
            if existing is not None:
                claimed_email = emailclaim.email
                claimed_user = emailclaim.user
                db.session.delete(emailclaim)
                db.session.commit()
                if claimed_user != current_auth.user:
                    return render_message(title=_("Email address already claimed"),
                        message=Markup(
                            _(u"The email address <code>{email}</code> has already been verified by another user").format(
                                email=escape(claimed_email))))
                else:
                    return render_message(title=_("Email address already verified"),
                        message=Markup(_(u"Hello <strong>{fullname}</strong>! "
                            u"Your email address <code>{email}</code> has already been verified").format(
                                fullname=escape(claimed_user.fullname), email=escape(claimed_email))))

            useremail = emailclaim.user.add_email(emailclaim.email,
                primary=emailclaim.user.email is None,
                type=emailclaim.type,
                private=emailclaim.private)
            db.session.delete(emailclaim)
            for claim in UserEmailClaim.query.filter(
                    UserEmailClaim.email.in_([useremail.email, useremail.email.lower()])).all():
                db.session.delete(claim)
            db.session.commit()
            user_data_changed.send(current_auth.user, changes=['email'])
            return render_message(title=_("Email address verified"),
                message=Markup(_(u"Hello <strong>{fullname}</strong>! "
                    u"Your email address <code>{email}</code> has now been verified").format(
                        fullname=escape(emailclaim.user.fullname), email=escape(useremail.email))))
        else:
            return render_message(
                title=_("This was not for you"),
                message=_(u"You’ve opened an email verification link that was meant for another user. "
                        u"If you are managing multiple accounts, please login with the correct account "
                        u"and open the link again"),
                code=403)
    else:
        return render_message(
            title=_("Expired confirmation link"),
            message=_(u"The confirmation link you clicked on is either invalid or has expired"),
            code=404)
Esempio n. 16
0
def reset():
    # User wants to reset password
    # Ask for username or email, verify it, and send a reset code
    form = PasswordResetRequestForm()
    if form.validate_on_submit():
        username = form.username.data
        user = form.user
        if "@" in username and not username.startswith("@"):
            # They provided an email address. Send reset email to that address
            email = username
        else:
            # Send to their existing address
            # User.email is a UserEmail object
            email = unicode(user.email)
        if not email:
            # They don't have an email address. Maybe they logged in via Twitter
            # and set a local username and password, but no email. Could happen.
            return render_message(
                title="Reset password",
                message=Markup(
                    """
            We do not have an email address for your account and therefore cannot
            email you a reset link. Please contact
            <a href="mailto:%s">%s</a> for assistance.
            """
                    % (escape(app.config["SITE_SUPPORT_EMAIL"]), escape(app.config["SITE_SUPPORT_EMAIL"]))
                ),
            )
        resetreq = PasswordResetRequest(user=user)
        db.session.add(resetreq)
        send_password_reset_link(email=email, user=user, secret=resetreq.reset_code)
        db.session.commit()
        return render_message(
            title="Reset password",
            message=Markup(
                u"""
            You were sent an email at <code>%s</code> with a link to reset your password.
            Please check your email. If it doesn’t arrive in a few minutes,
            it may have landed in your spam or junk folder.
            The reset link is valid for 24 hours.
            """
                % escape(email)
            ),
        )

    return render_form(form=form, title="Reset password", submit="Send reset code", ajax=True)
Esempio n. 17
0
def lastuser_error(error, error_description=None, error_uri=None):
    if error == 'access_denied':
        flash(_("You denied the request to login"), category='error')
        return redirect(get_next_url())
    return render_message(title=_("Error: {error}").format(error=error),
                          message=Markup(
                              "<p>{desc}</p><p>URI: {uri}</p>".format(
                                  desc=escape(error_description or ''),
                                  uri=escape(error_uri or _('NA')))))
Esempio n. 18
0
def workspace_delete(workspace):
    # Only allow workspaces to be deleted if they have no expense reports
    if workspace.reports:
        return render_message(
            title=u"Cannot delete this workspace",
            message=u"This workspace cannot be deleted because it contains expense reports.")
    return render_delete_sqla(workspace, db, title=u"Confirm delete",
        message=u"Delete workspace '%s'?" % workspace.title,
        success=u"You have deleted workspace '%s'." % workspace.title,
        next=url_for('index'))
Esempio n. 19
0
def workspace_new():
    # Step 1: Get a list of organizations this user owns
    existing = Workspace.query.filter(Workspace.userid.in_(g.user.organizations_owned_ids())).all()
    existing_ids = [e.userid for e in existing]
    # Step 2: Prune list to organizations without a workspace
    new_workspaces = []
    for org in g.user.organizations_owned():
        if org['userid'] not in existing_ids:
            new_workspaces.append((org['userid'], org['title']))
    if not new_workspaces:
        return render_message(
            title=u"No organizations found",
            message=Markup(u"You do not have any organizations that do not already have a workspace. "
                u'Would you like to <a href="%s">create a new organization</a>?' %
                    lastuser.endpoint_url('/organizations/new')))
    eligible_workspaces = []
    for orgid, title in new_workspaces:
        if Team.query.filter_by(orgid=orgid).first() is not None:
            eligible_workspaces.append((orgid, title))
    if not eligible_workspaces:
        return render_message(
            title=u"No organizations available",
            message=Markup(u"To create a workspace for an organization, you must first allow this app to "
                u"access the list of teams in your organization. "
                u'<a href="%s">Do that here</a>.' % lastuser.endpoint_url('/apps/' + lastuser.client_id)))

    # Step 3: Ask user to select organization
    form = NewWorkspaceForm()
    form.workspace.choices = eligible_workspaces
    if request.method == 'GET':
        form.workspace.data = new_workspaces[0][0]
    if form.validate_on_submit():
        # Step 4: Make a workspace
        org = [org for org in g.user.organizations_owned() if org['userid'] == form.workspace.data][0]
        workspace = Workspace(name=org['name'], title=org['title'], userid=org['userid'],
            currency=form.currency.data, timezone=app.config.get('TIMEZONE', ''))
        db.session.add(workspace)
        db.session.commit()
        flash(u"Created a workspace for %s" % workspace.title, "success")
        return render_redirect(url_for('workspace_edit', workspace=workspace.name), code=303)
    return render_form(form=form, title="Create a workspace for your organization...", submit="Next",
        formid="workspace_new", cancel_url=url_for('index'), ajax=False)
Esempio n. 20
0
def reset():
    # User wants to reset password
    # Ask for username or email, verify it, and send a reset code
    form = PasswordResetRequestForm()
    if form.validate_on_submit():
        username = form.username.data
        user = form.user
        if '@' in username and not username.startswith('@'):
            # They provided an email address. Send reset email to that address
            email = username
        else:
            # Send to their existing address
            # User.email is a UserEmail object
            email = unicode(user.email)
        if not email:
            # They don't have an email address. Maybe they logged in via Twitter
            # and set a local username and password, but no email. Could happen.
            return render_message(title="Reset password",
                                  message=Markup("""
            We do not have an email address for your account and therefore cannot
            email you a reset link. Please contact
            <a href="mailto:%s">%s</a> for assistance.
            """ % (escape(app.config['SITE_SUPPORT_EMAIL']),
                   escape(app.config['SITE_SUPPORT_EMAIL']))))
        resetreq = PasswordResetRequest(user=user)
        db.session.add(resetreq)
        send_password_reset_link(email=email,
                                 user=user,
                                 secret=resetreq.reset_code)
        db.session.commit()
        return render_message(title="Reset password",
                              message=Markup(u"""
            You were sent an email at <code>%s</code> with a link to reset your password.
            Please check your email. If it doesn’t arrive in a few minutes,
            it may have landed in your spam or junk folder.
            The reset link is valid for 24 hours.
            """ % escape(email)))

    return render_form(form=form,
                       title="Reset password",
                       submit="Send reset code",
                       ajax=True)
Esempio n. 21
0
def lastuser_error(error, error_description=None, error_uri=None):
    if error == 'access_denied':
        flash("You denied the request to login", category='error')
        return redirect(get_next_url())
    return render_message(
        title="Error: {0}".format(error),
        message=Markup(
            "<p>{desc}</p><p>URI: {uri}</p>".format(
                desc=escape(error_description or ''), uri=escape(error_uri or _('NA')))
            )
        )
Esempio n. 22
0
def lastuser_error(error, error_description=None, error_uri=None):
    if error == "access_denied":
        flash(_(u"You denied the request to login"), category="error")
        return redirect(get_next_url())
    return render_message(
        title=_(u"Error: {error}").format(error=error),
        message=Markup(
            u"<p>{desc}</p><p>URI: {uri}</p>".format(
                desc=escape(error_description or u""), uri=escape(error_uri or _(u"NA"))
            )
        ),
    )
Esempio n. 23
0
def reset_email(user, kwargs):
    logout_internal()
    resetreq = PasswordResetRequest.query.filter_by(
        user=user, reset_code=kwargs['secret']).first()
    if not resetreq:
        return render_message(
            title="Invalid reset link",
            message=Markup("The reset link you clicked on is invalid."))
    if resetreq.created_at < datetime.utcnow() - timedelta(days=1):
        # Reset code has expired (> 24 hours). Delete it
        db.session.delete(resetreq)
        db.session.commit()
        return render_message(
            title="Expired reset link",
            message=Markup("The reset link you clicked on has expired."))

    # Reset code is valid. Now ask user to choose a new password
    form = PasswordResetForm()
    if form.validate_on_submit():
        user.password = form.password.data
        db.session.delete(resetreq)
        db.session.commit()
        return render_message(
            title="Password reset complete",
            message=Markup(
                'Your password has been reset. You may now <a href="%s">login</a> with your new password.'
                % escape(url_for('login'))))
    return render_form(
        form=form,
        title="Reset password",
        formid='reset',
        submit="Reset password",
        message=Markup(
            'Hello, <strong>%s</strong>. You may now choose a new password.' %
            user.fullname),
        ajax=True)
Esempio n. 24
0
def confirm_email(md5sum, secret):
    emailclaim = UserEmailClaim.query.filter_by(md5sum=md5sum, verification_code=secret).first()
    if emailclaim is not None:
        if 'verify' in emailclaim.permissions(g.user):
            useremail = emailclaim.user.add_email(emailclaim.email, primary=emailclaim.user.email is None)
            db.session.delete(emailclaim)
            for claim in UserEmailClaim.query.filter_by(email=useremail.email).all():
                db.session.delete(claim)
            db.session.commit()
            return render_message(title="Email address verified",
                message=Markup("Hello %s! Your email address <code>%s</code> has now been verified." % (
                    escape(emailclaim.user.fullname), escape(useremail.email))))
        else:
            return render_message(
                title="That was not for you",
                message=u"You’ve opened an email verification link that was meant for another user. "
                    u"If you are managing multiple accounts, please login with the correct account "
                    u"and open the link again.",
                code=403)
    else:
        return render_message(
            title="Expired confirmation link",
            message="The confirmation link you clicked on is either invalid or has expired.",
            code=404)
Esempio n. 25
0
def workspace_delete(workspace):
    # Only allow workspaces to be deleted if they have no expense reports
    if workspace.reports:
        return render_message(
            title=u"Cannot delete this workspace",
            message=
            u"This workspace cannot be deleted because it contains expense reports."
        )
    return render_delete_sqla(
        workspace,
        db,
        title=u"Confirm delete",
        message=u"Delete workspace '%s'?" % workspace.title,
        success=u"You have deleted workspace '%s'." % workspace.title,
        next=url_for('index'))
Esempio n. 26
0
def space_form_test(profile):
    fields = [{
        'name': 'test',
        'label': 'Test Field',
        'validators': ['Required'],
    }, {
        'name': 'phone',
        'type': 'AnnotatedTextField',
        'prefix': '+91',
    }]
    form = FormGenerator().generate(fields)()
    if form.validate_on_submit():
        class Target(object):
            pass
        target = Target()
        form.populate_obj(target)
        return render_message("Form submit", "Form content: " + repr(target.__dict__))
    return render_form(form=form, title=_("Test form"), submit=_("Test submit"), cancel_url=profile.url_for())
Esempio n. 27
0
def board_new():
    # Step 1: Get a list of organizations this user owns
    existing = Board.query.filter(
        Board.userid.in_(g.user.organizations_owned_ids())).all()
    existing_ids = [e.userid for e in existing]
    # Step 2: Prune list to organizations without a board
    new_boards = []
    for org in g.user.organizations_owned():
        if org['userid'] not in existing_ids:
            new_boards.append((org['userid'], org['title']))
    if not new_boards:
        return render_message(
            title=u"No organizations found",
            message=Markup(
                u"You do not have any organizations that do not already have a board. "
                u'Would you like to <a href="%s">create a new organization</a>?'
                % lastuser.endpoint_url('/organizations/new')))
    # Step 3: Ask user to select organization
    form = NewBoardForm()
    form.board.choices = new_boards
    if request.method == 'GET':
        form.board.data = new_boards[0][0]
    if form.validate_on_submit():
        # Step 4: Make a board
        org = [
            org for org in g.user.organizations_owned()
            if org['userid'] == form.board.data
        ][0]
        board = Board(name=org['name'],
                      title=org['title'],
                      userid=org['userid'])
        db.session.add(board)
        db.session.commit()
        flash(u"Created a board for %s" % board.title, 'success')
        return render_redirect(url_for('board_edit', board=board.name),
                               code=303)
    return render_form(form=form,
                       title="Create a board for your organization...",
                       submit="Next",
                       formid="board_new",
                       cancel_url=url_for('index'),
                       ajax=False)
Esempio n. 28
0
def video_remove(channel, playlist, video):
    """
    Remove video from playlist
    """
    if playlist not in video.playlists:
        # This video isn't in this playlist
        abort(404)

    # If this is the primary playlist for this video, refuse to remove it.
    if playlist == video.playlist:
        return render_message(title="Cannot remove",
            message=Markup("Videos cannot be removed from their primary playlist. "
                '<a href="%s">Return to video</a>.' % video.url_for()))

    connection = PlaylistVideo.query.filter_by(playlist_id=playlist.id, video_id=video.id).first_or_404()

    return render_delete_sqla(connection, db, title="Confirm remove",
        message=u"Remove video '%s' from playlist '%s'?" % (video.title, playlist.title),
        success=u"You have removed video '%s' from playlist '%s'." % (video.title, playlist.title),
        next=playlist.url_for())
Esempio n. 29
0
def funnelapp_login(cookietest=False):
    # 1. Create a login nonce (single use, unlike CSRF)
    session['login_nonce'] = str(uuid.uuid4())
    if not cookietest:
        # Reconstruct current URL with ?cookietest=1 or &cookietest=1 appended
        if request.query_string:
            return redirect(request.url + '&cookietest=1')
        return redirect(request.url + '?cookietest=1')

    if 'login_nonce' not in session:
        # No support for cookies. Abort login
        return render_message(
            title=_("Cookies required"),
            message=_("Please enable cookies in your browser."),
        )
    # 2. Nonce has been set. Create a request code
    request_code = talkfunnel_serializer().dumps(
        {'nonce': session['login_nonce']})
    # 3. Redirect user
    return redirect(app_url_for(app, 'login_talkfunnel', code=request_code))
Esempio n. 30
0
def search():
    return render_message(title="No search",
                          message=u"Search hasn’t been implemented yet.")
Esempio n. 31
0
def confirm_email(email_hash, secret):
    kwargs = blake2b_b58(email_hash)
    emailclaim = UserEmailClaim.get_by(verification_code=secret, **kwargs)
    if emailclaim is not None:
        emailclaim.email_address.mark_active()
        if 'verify' in emailclaim.permissions(current_auth.user):
            existing = UserEmail.get(email=emailclaim.email)
            if existing is not None:
                claimed_email = emailclaim.email
                claimed_user = emailclaim.user
                db.session.delete(emailclaim)
                db.session.commit()
                if claimed_user != current_auth.user:
                    return render_message(
                        title=_("Email address already claimed"),
                        message=Markup(
                            _("The email address <code>{email}</code> has already"
                              " been verified by another user").format(
                                  email=escape(claimed_email))),
                    )
                else:
                    return render_message(
                        title=_("Email address already verified"),
                        message=Markup(
                            _("Hello <strong>{fullname}</strong>! Your email address"
                              " <code>{email}</code> has already been verified"
                              ).format(
                                  fullname=escape(claimed_user.fullname),
                                  email=escape(claimed_email),
                              )),
                    )

            useremail = emailclaim.user.add_email(
                emailclaim.email,
                primary=emailclaim.user.email is None,
                type=emailclaim.type,
                private=emailclaim.private,
            )
            for emailclaim in UserEmailClaim.all(useremail.email):
                db.session.delete(emailclaim)
            db.session.commit()
            user_data_changed.send(current_auth.user, changes=['email'])
            return render_message(
                title=_("Email address verified"),
                message=Markup(
                    _("Hello <strong>{fullname}</strong>! "
                      "Your email address <code>{email}</code> has now been verified"
                      ).format(
                          fullname=escape(useremail.user.fullname),
                          email=escape(useremail.email),
                      )),
            )
        else:
            return render_message(
                title=_("This was not for you"),
                message=
                _("You’ve opened an email verification link that was meant for"
                  " another user. If you are managing multiple accounts, please login"
                  " with the correct account and open the link again."),
                code=403,
            )
    else:
        return render_message(
            title=_("Expired confirmation link"),
            message=
            _("The confirmation link you clicked on is either invalid or has expired."
              ),
            code=404,
        )
Esempio n. 32
0
def reset():
    # User wants to reset password
    # Ask for username or email, verify it, and send a reset code
    form = PasswordResetRequestForm()
    if getbool(request.args.get('expired')):
        message = _(
            "Your password has expired. Please enter your username or email address to"
            " request a reset code and set a new password."
        )
    else:
        message = None

    if request.method == 'GET':
        form.username.data = abort_null(request.args.get('username'))

    if form.validate_on_submit():
        username = form.username.data
        user = form.user
        if '@' in username and not username.startswith('@'):
            # They provided an email address. Send reset email to that address
            email = username
        else:
            # Send to their existing address
            # User.email is a UserEmail object
            email = str(user.email)
        if not email and user.emailclaims:
            email = user.emailclaims[0].email
        if not email:
            # They don't have an email address. Maybe they logged in via Twitter
            # and set a local username and password, but no email. Could happen.
            if len(user.externalids) > 0:
                extid = user.externalids[0]
                return render_message(
                    title=_("Cannot reset password"),
                    message=Markup(
                        _(
                            "Your account does not have an email address. However,"
                            " it is linked to <strong>{service}</strong> with the id"
                            " <strong>{username}</strong>. You can use that to login."
                        ).format(
                            service=login_registry[extid.service].title,
                            username=extid.username or extid.userid,
                        )
                    ),
                )

            return render_message(
                title=_("Cannot reset password"),
                message=Markup(
                    _(
                        'Your account does not have an email address. Please'
                        ' contact <a href="mailto:{email}">{email}</a> for'
                        ' assistance.'
                    ).format(email=escape(current_app.config['SITE_SUPPORT_EMAIL']))
                ),
            )

        # Allow only two reset attempts per hour to discourage abuse
        validate_rate_limit('email_reset', user.uuid_b58, 2, 3600)
        send_password_reset_link(
            email=email,
            user=user,
            token=token_serializer().dumps(
                {'buid': user.buid, 'pw_set_at': str_pw_set_at(user)}
            ),
        )
        return render_message(
            title=_("Email sent"),
            message=_(
                "You have been sent an email with a link to reset your password, to"
                " your address {masked_email}. If it doesn’t arrive in a few minutes,"
                " it may have landed in your spam or junk folder. The reset link is"
                " valid for 24 hours."
            ).format(masked_email=mask_email(email)),
        )
    return render_form(
        form=form,
        title=_("Reset password"),
        message=message,
        submit=_("Send reset link"),
        ajax=False,
        template='account_formlayout.html.jinja2',
    )
Esempio n. 33
0
def reset_email_do():

    # Validate the token
    # 1. Do we have a token? User may have accidentally landed here
    if 'temp_token' not in session:
        if request.method == 'GET':
            # No token. GET request. Either user landed here by accident, or browser
            # reloaded this page from history. Send back to to the reset request page
            return redirect(url_for('reset'), code=303)

        # Reset token was expired from session, likely because they didn't submit
        # the form in time. We no longer know what user this is for. Inform the user
        return render_message(
            title=_("Please try again"),
            message=_("This page timed out. Please open the reset link again."),
        )

    # 2. There's a token in the session. Is it valid?
    try:
        # Allow 24 hours (86k seconds) validity for the reset token
        token = token_serializer().loads(session['temp_token'], max_age=86400)
    except itsdangerous.exc.SignatureExpired:
        # Link has expired (timeout).
        session.pop('temp_token', None)
        session.pop('temp_token_at', None)
        flash(
            _(
                "This password reset link has expired."
                " If you still need to reset your password, you may request a new link"
            ),
            'error',
        )
        return redirect(url_for('reset'), code=303)
    except itsdangerous.exc.BadData:
        # Link is invalid
        session.pop('temp_token', None)
        session.pop('temp_token_at', None)
        flash(
            _(
                "This password reset link is invalid."
                " If you still need to reset your password, you may request a new link"
            ),
            'error',
        )
        return redirect(url_for('reset'), code=303)

    # 3. We have a token and it's not expired. Is there a user?
    user = User.get(buid=token['buid'])
    if not user:
        # If the user has disappeared, it's likely because of account deletion.
        session.pop('temp_token', None)
        session.pop('temp_token_at', None)
        return render_message(
            title=_("Unknown user"),
            message=_("There is no account matching this password reset request"),
        )
    # 4. We have a user. Has the token been used already? Check pw_set_at
    if token['pw_set_at'] != str_pw_set_at(user):
        # Token has been used to set a password, as the timestamp has changed. Ask user
        # if they want to reset their password again
        session.pop('temp_token', None)
        session.pop('temp_token_at', None)
        flash(
            _(
                "This password reset link has been used."
                " If you need to reset your password again, you may request a new link"
            ),
            'error',
        )
        return redirect(url_for('reset'), code=303)

    # All good? Proceed with request
    # Logout *after* validating the reset request to prevent DoS attacks on the user
    logout_internal()
    db.session.commit()
    # Reset code is valid. Now ask user to choose a new password
    form = PasswordResetForm()
    form.edit_user = user
    if form.validate_on_submit():
        current_app.logger.info("Password strength %f", form.password_strength)
        user.password = form.password.data
        session.pop('temp_token', None)
        session.pop('temp_token_at', None)
        # Invalidate all of the user's active sessions
        counter = None
        for counter, user_session in enumerate(user.active_user_sessions.all()):
            user_session.revoke()
        db.session.commit()
        dispatch_notification(AccountPasswordNotification(document=user))
        return render_message(
            title=_("Password reset complete"),
            message=_(
                "Your password has been changed. You may now login with your new"
                " password."
            )
            if counter is None
            else ngettext(
                "Your password has been changed. As a precaution, you have been logged"
                " out of one other device. You may now login with your new password.",
                "Your password has been changed. As a precaution, you have been logged"
                " out of %(num)d other devices. You may now login with your new"
                " password.",
                counter + 1,
            ),
        )
    # Form with id 'form-password-change' will have password strength meter on UI
    return render_form(
        form=form,
        title=_("Reset password"),
        formid='password-change',
        submit=_("Reset password"),
        message=Markup(
            _(
                "Hello, <strong>{fullname}</strong>. You may now choose a new password."
            ).format(fullname=escape(user.fullname))
        ),
        ajax=False,
        template='account_formlayout.html.jinja2',
    )
    def unsubscribe(self, token, token_type, cookietest=False):
        # This route strips the token from the URL before rendering the page, to avoid
        # leaking the token to web analytics software.

        # Step 1: Sanity check: someone loaded this URL without a token at all.
        # Send them away
        if not token and (
            (request.method == 'GET' and 'temp_token' not in session) or
            (request.method == 'POST' and 'token' not in request.form)):
            return redirect(url_for('notification_preferences'))

        # Step 2: We have a URL token, but no `cookietest=1` in the URL. Copy token into
        # session and reload the page with the flag set
        if token and not cookietest:
            session['temp_token'] = token
            session['temp_token_type'] = token_type
            # Use naive datetime as the session can't handle tz-aware datetimes
            session['temp_token_at'] = datetime.utcnow()
            # These values are removed from the session in 10 minutes by
            # :func:`funnel.views.login_session.clear_expired_temp_token` if the user
            # abandons this page.

            # Reconstruct current URL with ?cookietest=1 or &cookietest=1 appended
            # and reload the page
            if request.query_string:
                return redirect(request.url + '&cookietest=1')
            return redirect(request.url + '?cookietest=1')

        # Step 3a: We have a URL token and cookietest is now set, but token is missing
        # from session. That typically means the browser is refusing to set cookies
        # on 30x redirects that originated off-site (such as a webmail app). Do a
        # meta-refresh redirect instead. It is less secure because browser extensions
        # may be able to read the URL during the brief period the page is rendered,
        # but so far there has been no indication of cookies not being set.
        if token and 'temp_token' not in session:
            session['temp_token'] = token
            session['temp_token_type'] = token_type
            session['temp_token_at'] = datetime.utcnow()
            return metarefresh_redirect(
                url_for('notification_unsubscribe_do') +
                ('?' + request.query_string.decode()) if request.
                query_string else '')

        # Step 3b: We have a URL token and cookietest is now set, and token is also in
        # session. Great! No browser cookie problem, so redirect again to remove the
        # token from the URL. This will hide it from web analytics software such as
        # Google Analytics and Matomo.
        if token and 'temp_token' in session:
            # Browser is okay with cookies. Do a 302 redirect
            # Strip out `cookietest=1` from the redirected URL
            return redirect(
                (url_for('notification_unsubscribe_do') +
                 ('?' + request.query_string.decode()) if request.query_string
                 else '').replace('?cookietest=1&',
                                  '?')  # If cookietest is somehow at start
                .replace('?cookietest=1', '')  # Or if it's solo
                .replace('&cookietest=1',
                         '')  # And/or if it's in the middle or end
            )

        # Step 4. We have a token and it's been stripped from the URL. Process it based
        # on the token type.
        if not token_type:
            token_type = session.get(
                'temp_token_type') or request.form['token_type']

        # --- Signed tokens (email)
        if token_type == 'signed':  # nosec
            try:
                # Token will be in session in the GET request, and in request.form
                # in the POST request because we'll move it over during the GET request.
                payload = token_serializer().loads(
                    session.get('temp_token') or request.form['token'],
                    max_age=365 * 24 * 60 * 60,  # Validity 1 year (365 days)
                )
            except itsdangerous.exc.SignatureExpired:
                # Link has expired. It's been over a year!
                discard_temp_token()
                flash(unsubscribe_link_expired, 'error')
                return redirect(url_for('notification_preferences'), code=303)
            except itsdangerous.exc.BadData:
                discard_temp_token()
                flash(unsubscribe_link_invalid, 'error')
                return redirect(url_for('notification_preferences'), code=303)

        # --- Cached tokens (SMS)
        elif token_type == 'cached':  # nosec

            # Enforce a rate limit per IP on cached tokens, to slow down enumeration.
            # Some ISPs use carrier-grade NAT and will have a single IP for a very
            # large number of users, so we have generous limits. 100 unsubscribes per
            # 10 minutes (600s) per IP address.
            validate_rate_limit('sms_unsubscribe', request.remote_addr, 100,
                                600)

            payload = retrieve_cached_token(
                session.get('temp_token') or request.form['token'])
            if not payload:
                # No payload, meaning invalid token
                discard_temp_token()
                flash(unsubscribe_link_invalid, 'error')
                return redirect(url_for('notification_preferences'), code=303)
            if payload['timestamp'] < datetime.utcnow() - timedelta(days=7):
                # Link older than a week. Expire it
                discard_temp_token()
                flash(unsubscribe_link_expired, 'error')
                return redirect(url_for('notification_preferences'), code=303)

        else:
            # This is not supposed to happen
            abort(400)

        # Step 5. Validate whether the token matches the current user, if any
        # Do not allow links to be used across accounts.
        if current_auth.user and current_auth.user.buid != payload['buid']:
            return render_message(
                title=_("Unauthorized unsubscribe link"),
                message=
                _("This unsubscribe link is for someone else’s account. Please logout"
                  " or use an incognito/private browsing session to use this link."
                  ),
            )

        # Step 6. Load the user. The contents of `payload` are defined in
        # :meth:`NotificationView.unsubscribe_token` above
        user = User.get(buid=payload['buid'])
        if payload['transport'] == 'email' and 'hash' in payload:
            email_address = EmailAddress.get(email_hash=payload['hash'])
            email_address.mark_active()
            db.session.commit()
        # TODO: Add active status for phone numbers and check here

        # Step 7. Ask the user to confirm unsubscribe. Do not unsubscribe on a GET
        # request as it may be triggered by link previews (for transports other than
        # email, or when an email is copy-pasted into a messenger app).
        form = UnsubscribeForm(
            edit_user=user,
            transport=payload['transport'],
            notification_type=payload['notification_type'],
        )
        # Move the token from session to form. The session is swept every 10 minutes,
        # so if the user opens an unsubscribe link, wanders away and comes back later,
        # it'll be gone from session. It's safe for longer in the form, and doesn't
        # bear the leakage risk of being in the URL where analytics software can log it.
        if 'temp_token' in session:
            form.token.data = session['temp_token']
            form.token_type.data = session['temp_token_type']
            discard_temp_token()
        if form.validate_on_submit():
            form.save_to_user()
            db.session.commit()
            return render_message(
                title=_("Preferences saved"),
                message=_("Your notification preferences have been updated"),
            )
        return render_form(
            form=form,
            title=_("Notification preferences"),
            formid='unsubscribe-preferences',
            submit=_("Save preferences"),
            ajax=False,
            template='account_formlayout.html.jinja2',
        )
Esempio n. 35
0
def search():
    return render_message(title="No search", message=u"Search hasn’t been implemented yet.")