Ejemplo n.º 1
0
def test_search(module_client, module_db, fake_auth_from_oc, fake_algolia_save,
                fake_algolia_search):
    client = module_client

    first_term = random_string()
    apikey = get_api_key(client)

    # Create resource and find it in the search results.
    resource = client.post("/api/v1/resources",
                           json=dict(
                               name=f"{first_term}",
                               category="Website",
                               url=f"{first_term}",
                               paid=False,
                           ),
                           headers={'x-apikey': apikey})
    result = client.get(f"/api/v1/search?q={first_term}")

    assert (resource.status_code == 200)
    assert (result.status_code == 200)
    assert (result.json['data'][0]['url'] == resource.json['data'].get('url'))

    # Update the resource and test that search results reflect changes
    updated_term = random_string()
    resource_id = resource.json['data'].get('id')
    resource = client.put(f"/api/v1/resources/{resource_id}",
                          json=dict(url=f"{updated_term}", ),
                          headers={'x-apikey': apikey})

    result = client.get(f"/api/v1/search?q={updated_term}")

    assert (resource.status_code == 200)
    assert (result.status_code == 200)
    assert (result.json['data'][0]['url'] == resource.json['data'].get('url'))
Ejemplo n.º 2
0
def test_algolia_exception_error(module_client, module_db, fake_auth_from_oc,
                                 fake_algolia_exception):
    client = module_client
    first_term = random_string()
    apikey = get_api_key(client)

    result = client.get(f"/api/v1/search?q=python")

    assert (result.status_code == 500)

    resource = client.post("/api/v1/resources",
                           json=dict(
                               name=f"{first_term}",
                               category="Website",
                               url=f"{first_term}",
                               paid=False,
                           ),
                           headers={'x-apikey': apikey})

    assert (resource.status_code == 200)

    updated_term = random_string()

    response = client.put(
        f"/api/v1/resources/{resource.json['data'].get('id')}",
        json=dict(name="New name",
                  languages=["New language"],
                  category="New Category",
                  url=f"https://{updated_term}.url",
                  paid=False,
                  notes="New notes"),
        headers={'x-apikey': apikey})

    assert (response.status_code == 200)
Ejemplo n.º 3
0
def test_algolia_unreachable_host_error(
    module_client,
    module_db,
    fake_auth_from_oc,
    fake_algolia_unreachable_host,
):
    # Reset the FLASK_ENV to run Algolia error handling code
    environ["FLASK_ENV"] = "not_development"
    client = module_client
    first_term = random_string()
    apikey = get_api_key(client)

    response = client.get("/api/v1/search?q=python")

    assert (response.status_code == 500)
    assert ("Algolia" in response.get_json().get("errors")[0].get(
        "algolia-failed").get("message"))

    response = client.post("/api/v1/resources",
                           json=[
                               dict(
                                   name=f"{first_term}",
                                   category="Website",
                                   url=f"{first_term}",
                                   paid=False,
                               )
                           ],
                           headers={'x-apikey': apikey})

    assert (response.status_code == 500)
    assert ("Algolia" in response.get_json().get("errors")[0].get(
        "algolia-failed").get("message"))

    updated_term = random_string()

    response = client.put("/api/v1/resources/1",
                          json=dict(name="New name",
                                    languages=["New language"],
                                    category="New Category",
                                    url=f"https://{updated_term}.url",
                                    paid=False,
                                    notes="New notes"),
                          headers={'x-apikey': apikey})

    assert (response.status_code == 500)
    assert ("Algolia" in response.get_json().get("errors")[0].get(
        "algolia-failed").get("message"))

    # Set it back to development for other tests
    environ["FLASK_ENV"] = "development"
Ejemplo n.º 4
0
def update_user_info():
    """
    Input
    - profile_picture (optional): base64 of the profile picture. Set to null to remove the profile picture
    - name (optional)

    """
    user = g.user
    data = request.get_json() or {}

    if "profile_picture" in data:
        if data["profile_picture"] is None:
            if user.profile_picture_id:
                file = user.profile_picture
                user.profile_picture_id = None
                Session.flush()
                if file:
                    File.delete(file.id)
                    s3.delete(file.path)
                    Session.flush()
        else:
            raw_data = base64.decodebytes(data["profile_picture"].encode())
            file_path = random_string(30)
            file = File.create(user_id=user.id, path=file_path)
            Session.flush()
            s3.upload_from_bytesio(file_path, BytesIO(raw_data))
            user.profile_picture_id = file.id
            Session.flush()

    if "name" in data:
        user.name = data["name"]

    Session.commit()

    return jsonify(user_to_dict(user))
Ejemplo n.º 5
0
def generate_key():
    print(random_string(
    	min_size=32,
    	max_size=128,
    	punctuation=True,
    	exposant=6
    ))
Ejemplo n.º 6
0
    def paddle_coupon():
        LOG.d(f"paddle coupon callback %s", request.form)

        if not paddle_utils.verify_incoming_request(dict(request.form)):
            LOG.e("request not coming from paddle. Request data:%s",
                  dict(request.form))
            return "KO", 400

        product_id = request.form.get("p_product_id")
        if product_id != PADDLE_COUPON_ID:
            LOG.e("product_id %s not match with %s", product_id,
                  PADDLE_COUPON_ID)
            return "KO", 400

        email = request.form.get("email")
        LOG.d("Paddle coupon request for %s", email)

        coupon = Coupon.create(
            code=random_string(30),
            comment="For 1-year coupon",
            expires_date=arrow.now().shift(years=1, days=-1),
            commit=True,
        )

        return (
            f"Your 1-year coupon is <b>{coupon.code}</b> <br> "
            f"It's valid until <b>{coupon.expires_date.date().isoformat()}</b>"
        )
Ejemplo n.º 7
0
def copy_student_name(apps, schema_editor):
    db_alias = schema_editor.connection.alias
    emit_post_migrate_signal(2, False, db_alias)

    Permission = apps.get_model('auth', 'Permission')
    Group = apps.get_model('auth', 'Group')

    global group_and_permission
    for group_name, permission_list in group_and_permission:
        new_group, group_create = Group.objects.get_or_create(name=group_name)
        new_group.save()

        for permission_name in permission_list:
            permission, _ = Permission.objects.get_or_create(
                    name=permission_name)
            permission.save()
            new_group.permissions.add(permission)

    Parent = apps.get_model('app', 'Parent')
    User = apps.get_model('auth', 'User')
    Profile = apps.get_model('app', 'Profile')
    Student = apps.get_model('app', 'Student')

    parents = Parent.objects.all()
    for parent in parents:
        username = random_string()[:30]
        salt = random_string()[:5]
        password = "******"
        user = User(username=username)
        user.email = ""
        user.password = make_password(password, salt)
        user.save()

        student_group = Group.objects.get(name="学生")
        user.groups.add(student_group)

        profile = Profile(user=user, phone="fake" + random_string()[:16])
        profile.save()

        student = Student(name=parent.old_student_name,
                          school_name=parent.old_student_name,
                          user=user)
        student.save()
        parent.students.add(student)
Ejemplo n.º 8
0
def generate_access_token() -> str:
    """generate an access-token that does not exist before"""
    access_token = random_string(40)

    if not OauthToken.get_by(access_token=access_token):
        return access_token

    # Rerun the function
    LOG.warning("access token already exists, generate a new one")
    return generate_access_token()
Ejemplo n.º 9
0
def create_file_from_url(user, url) -> File:
    file_path = random_string(30)
    file = File.create(path=file_path, user_id=user.id)

    s3.upload_from_url(url, file_path)

    db.session.flush()
    LOG.d("upload file %s to s3", file)

    return file
Ejemplo n.º 10
0
def false_validation(module_client, module_db, fake_auth_from_oc, fake_algolia_save, fake_validate_resource):
    # Given the validate_resource method fails to catch errors
    # When we commit the resource to the database
    # Then the api returns a 422 response

    client = module_client
    first_term = random_string()
    apikey = get_api_key(client)

    resource = client.post("/api/v1/resources",
                           json=dict(
                               name=f"{first_term}",
                               category="Website",
                               url=f"{first_term}",
                               paid=False,
                           ),
                           headers={'x-apikey': apikey}
                           )

    assert (resource.status_code == 200)

    id = resource.json['data'].get("id")

    resource = client.post("/api/v1/resources",
                           json=dict(
                               name=f"{first_term}",
                               category="Website",
                               url=f"{first_term}",
                               paid=False,
                           ),
                           headers={'x-apikey': apikey}
                           )

    assert (resource.status_code == 422)
    assert (resource.json['data'].get("errors") is not None)
    assert (resource.json['data'].get("errors")['-'.join(err_map.get(resource.status_code).split(' ')).lower()] ==
            msg_map[resource.status_code])

    resource = client.put(f"/api/v1/resources/{id}",
                          json=dict(
                              name="New name",
                              languages=["New language"],
                              category="New Category",
                              url=f"{first_term}",
                              paid=False,
                              notes="New notes"
                          ),
                          headers={'x-apikey': apikey}
                          )

    assert (resource.status_code == 422)
    assert (resource.json['data'].get("errors") is not None)
    assert (resource.json['data'].get("errors")['-'.join(err_map.get(resource.status_code).split(' ')).lower()] ==
            msg_map[resource.status_code])
Ejemplo n.º 11
0
    def create(cls, user_id, name):
        # generate unique code
        found = False
        while not found:
            code = random_string(60)

            if not cls.get_by(code=code):
                found = True

        a = cls(user_id=user_id, code=code, name=name)
        db.session.add(a)
        return a
Ejemplo n.º 12
0
def send_reset_password_email(user):
    """
    generate a new ResetPasswordCode and send it over email to user
    """
    # the activation code is valid for 1h
    reset_password_code = ResetPasswordCode.create(user_id=user.id,
                                                   code=random_string(60))
    db.session.commit()

    reset_password_link = f"{URL}/auth/reset_password?code={reset_password_code.code}"

    email_utils.send_reset_password_email(user.email, reset_password_link)
Ejemplo n.º 13
0
    def create_new(cls, name, user_id) -> "Client":
        # generate a client-id
        oauth_client_id = generate_oauth_client_id(name)
        oauth_client_secret = random_string(40)
        client = Client.create(
            name=name,
            oauth_client_id=oauth_client_id,
            oauth_client_secret=oauth_client_secret,
            user_id=user_id,
        )

        return client
Ejemplo n.º 14
0
def send_activation_email(user, next_url):
    # the activation code is valid for 1h
    activation = ActivationCode.create(user_id=user.id, code=random_string(30))
    db.session.commit()

    # Send user activation email
    activation_link = f"{URL}/auth/activate?code={activation.code}"
    if next_url:
        LOG.d("redirect user to %s after activation", next_url)
        activation_link = activation_link + "&next=" + encode_url(next_url)

    email_utils.send_activation_email(user.email, user.name, activation_link)
Ejemplo n.º 15
0
def generate_oauth_client_id(client_name) -> str:
    oauth_client_id = convert_to_id(client_name) + "-" + random_string()

    # check that the client does not exist yet
    if not Client.get_by(oauth_client_id=oauth_client_id):
        LOG.debug("generate oauth_client_id %s", oauth_client_id)
        return oauth_client_id

    # Rerun the function
    LOG.warning("client_id %s already exists, generate a new client_id",
                oauth_client_id)
    return generate_oauth_client_id(client_name)
Ejemplo n.º 16
0
def api_create_drop():
    """API view function to create a new drop."""
    if request.form.get('data') == None:
        return jsonify(
            {"error":
             "You must provide some data when creating a new drop."}), 400
    drop = models.Drop()
    title = request.form.get('title')
    if title != None and title != '':
        drop.title = title
    drop.created_at = datetime.datetime.utcnow()
    drop.data = request.form.get('data')
    publicly_listed = request.form.get('publicly_listed',
                                       app.config['DATADROP_DEFAULT_LISTED'])
    drop.publicly_listed = publicly_listed
    expires = request.form.get('expires',
                               app.config['DATADROP_DEFAULT_EXPIRES'])
    drop.expires = expires
    expires_in_string = request.form.get(
        'expires_in', app.config['DATADROP_DEFAULT_EXPIRES_IN'])
    # set the parsed expire time (in seconds); if it's invalid, the default is used
    drop.expires_in = pytimeparse.parse(expires_in_string)
    self_destructs = request.form.get(
        'self_destructs', app.config['DATADROP_DEFAULT_SELF_DESTRUCTS'])
    drop.self_destructs = self_destructs
    self_destructs_in = int(
        request.form.get('self_destructs_in',
                         app.config['DATADROP_DEFAULT_SELF_DESTRUCTS_IN']))
    drop.self_destructs_in = self_destructs_in
    drop_key_strings = request.form.getlist('drop_keys')
    if drop_key_strings != None and drop_key_strings != []:
        # for each drop key that was provided, check if it exists in the database;
        # if it doesn't, create it
        drop_keys = []  # a list of key instances
        for k in drop_key_strings:
            potential_key = db.DropKey.filter_by(key=k.lower()).one_or_none()
            if potential_key == None:
                potential_key = db.save(models.DropKey(key=k.lower()))
            drop_keys.append(potential_key)
        drop.drop_keys = drop_keys
    # to avoid unique urlstring errors
    made_urlstring = False
    while made_urlstring == False:
        u = utils.random_string(app.config['DATADROP_URLSTRING_LENGTH'])
        if db.Drop.filter_by(urlstring=u).one_or_none() == None:
            made_urlstring = True
            drop.urlstring = u
    db.save(drop)
    # now build the response
    r = get_drop_dict(drop, include_drop_keys=True, just_created=True)
    return jsonify(r), 201
Ejemplo n.º 17
0
    def handle(self, *args, **options):

        for traider_number in range(0, TRAIDERS_COUNT):
            log.debug('Traider {}'.format(traider_number))

            # create trader
            trader, _ = Trader.objects.get_or_create(
                name=utils.random_string(10), )

            # create trader deals
            deals = []
            deal_amounts = []
            for deal_number in range(0, TRAIDER_DEALS_COUNT):

                deal_amount = utils.random_decimal()
                deal_result_amount = deal_amount + utils.random_decimal()

                deal = Deal(trader=trader,
                            amount=deal_amount,
                            result_amount=deal_result_amount,
                            created_at=utils.random_date())
                deals.append(deal)
                deal_amounts.append(deal_result_amount)

            # create trader transactions
            transactions = []
            transaction_amounts = []
            for deal_number in range(0, TRAIDER_TRANSACTIONS_COUNT):

                _transaction = Transaction(
                    trader=trader,
                    amount=utils.random_positive_decimal(),
                    type=random.choice([
                        Transaction.TRANSACTION_TYPE_DEPOSIT,
                        Transaction.TRANSACTION_TYPE_WITHDRAWAL,
                    ]),
                    created_at=utils.random_date(),
                )
                transactions.append(_transaction)
                if _transaction.is_type_deposit():
                    transaction_amounts.append(_transaction.amount)
                else:
                    transaction_amounts.append(_transaction.amount * (-1))

            Deal.objects.bulk_create(deals)
            Transaction.objects.bulk_create(transactions)

            # update trader balance by deal_amounts and transaction_amounts
            trader.balance = sum(deal_amounts) + sum(transaction_amounts)
            trader.save(update_fields=['balance'])
Ejemplo n.º 18
0
    def create(cls, email, name, password=None, **kwargs):
        user: User = super(User, cls).create(email=email, name=name, **kwargs)

        if not password:
            # set a random password
            password = random_string(20)

        user.set_password(password)
        db.session.flush()

        # create a first alias mail to show user how to use when they login
        GenEmail.create_new(user.id, prefix="my-first-alias")
        db.session.flush()

        return user
Ejemplo n.º 19
0
def handle_sender_email(envelope: Envelope):
    filename = (arrow.now().format("YYYY-MM-DD_HH-mm-ss") + "_" +
                random_string(10) + ".eml")
    filepath = os.path.join(SENDER_DIR, filename)

    with open(filepath, "wb") as f:
        f.write(envelope.original_content)

    LOG.d("Write email to sender at %s", filepath)

    msg = email.message_from_bytes(envelope.original_content)
    orig = get_orig_message_from_bounce(msg)
    if orig:
        LOG.warning("Original message %s -> %s saved at %s", orig["From"],
                    orig["To"], filepath)

    return "250 email to sender accepted"
Ejemplo n.º 20
0
def construct_redirect_args(client, client_user, nonce, redirect_uri,
                            response_types, scope, state) -> dict:
    redirect_args = {}
    if state:
        redirect_args["state"] = state
    else:
        LOG.w("more security reason, state should be added. client %s", client)
    if scope:
        redirect_args["scope"] = scope

    auth_code = None
    if ResponseType.CODE in response_types:
        auth_code = AuthorizationCode.create(
            client_id=client.id,
            user_id=current_user.id,
            code=random_string(),
            scope=scope,
            redirect_uri=redirect_uri,
            response_type=response_types_to_str(response_types),
            nonce=nonce,
        )
        redirect_args["code"] = auth_code.code

    oauth_token = None
    if ResponseType.TOKEN in response_types:
        # create access-token
        oauth_token = OauthToken.create(
            client_id=client.id,
            user_id=current_user.id,
            scope=scope,
            redirect_uri=redirect_uri,
            access_token=generate_access_token(),
            response_type=response_types_to_str(response_types),
        )
        Session.add(oauth_token)
        redirect_args["access_token"] = oauth_token.access_token
    if ResponseType.ID_TOKEN in response_types:
        redirect_args["id_token"] = make_id_token(
            client_user,
            nonce,
            oauth_token.access_token if oauth_token else None,
            auth_code.code if auth_code else None,
        )
    Session.commit()
    return redirect_args
Ejemplo n.º 21
0
def batch_import_route():
    # only for users who have custom domains
    if not current_user.verified_custom_domains():
        flash("Alias batch import is only available for custom domains",
              "warning")

    if current_user.disable_import:
        flash(
            "you cannot use the import feature, please contact SimpleLogin team",
            "error",
        )
        return redirect(url_for("dashboard.index"))

    batch_imports = BatchImport.filter_by(user_id=current_user.id).all()

    if request.method == "POST":
        alias_file = request.files["alias-file"]

        file_path = random_string(20) + ".csv"
        file = File.create(user_id=current_user.id, path=file_path)
        s3.upload_from_bytesio(file_path, alias_file)
        Session.flush()
        LOG.d("upload file %s to s3 at %s", file, file_path)

        bi = BatchImport.create(user_id=current_user.id, file_id=file.id)
        Session.flush()
        LOG.d("Add a batch import job %s for %s", bi, current_user)

        # Schedule batch import job
        Job.create(
            name=JOB_BATCH_IMPORT,
            payload={"batch_import_id": bi.id},
            run_at=arrow.now(),
        )
        Session.commit()

        flash(
            "The file has been uploaded successfully and the import will start shortly",
            "success",
        )

        return redirect(url_for("dashboard.batch_import_route"))

    return render_template("dashboard/batch_import.html",
                           batch_imports=batch_imports)
Ejemplo n.º 22
0
def client_detail(client_id):
    form = EditClientForm()

    is_new = "is_new" in request.args

    client = Client.get(client_id)
    if not client:
        flash("no such client", "warning")
        return redirect(url_for("developer.index"))

    if client.user_id != current_user.id:
        flash("you cannot see this app", "warning")
        return redirect(url_for("developer.index"))

    if form.validate_on_submit():
        client.name = form.name.data
        client.home_url = form.home_url.data

        if form.icon.data:
            # todo: remove current icon if any
            # todo: handle remove icon
            file_path = random_string(30)
            file = File.create(path=file_path)

            s3.upload_from_bytesio(file_path, BytesIO(form.icon.data.read()))

            db.session.flush()
            LOG.d("upload file %s to s3", file)

            client.icon_id = file.id
            db.session.flush()

        db.session.commit()

        flash(f"{client.name} has been updated", "success")

        return redirect(url_for("developer.client_detail",
                                client_id=client.id))

    return render_template(
        "developer/client_details/basic_info.html",
        form=form,
        client=client,
        is_new=is_new,
    )
Ejemplo n.º 23
0
def send_reset_password_email(user):
    """
    generate a new ResetPasswordCode and send it over email to user
    """
    # the activation code is valid for 1h
    reset_password_code = ResetPasswordCode.create(user_id=user.id,
                                                   code=random_string(60))
    db.session.commit()

    reset_password_link = f"{URL}/auth/reset_password?code={reset_password_code.code}"

    email_utils.send_reset_password_email(user.email, user.name,
                                          reset_password_link)

    flash(
        "You are going to receive an email containing instruction to change your password",
        "success",
    )
Ejemplo n.º 24
0
def cut():
    print(request.form.get("longUrl"))
    longUrl = utils.convert(str(request.form.get("longUrl")))
    req = u.query.filter(u.longUrl == longUrl).first()
    print(req)
    if (req != None):
        shortUrl = req.shortUrl
    else:
        shortUrl = utils.random_string(size=4)
        db_session.add(Urls(shortUrl, longUrl))
        db_session.commit()
    response = {"status": True, "shortUrl": shortUrl, "longUrl": longUrl}
    print(longUrl)
    try:
        requests.get(response["longUrl"])
    except:
        response["status"] = False
    return render_template('cut.html', response=response)
Ejemplo n.º 25
0
    def post(self):
        """Endpoint to reset a user password"""
        data = request.get_json()
        email = data.get('email')

        user_data = dict(email=email)
        if check_missing_field(**user_data):
            return jsonify(check_missing_field(**user_data)), 422
        if check_email(email):
            return check_email(email)

        email = normalise_email(email)
        user = User.query.filter_by(email=email).first()
        if not user:
            return self.generate_response(messages['valid_email'], 401)
        password = random_string()
        hash_password = Bcrypt().generate_password_hash(password).decode()
        send_reset_password(email, password)
        user.update(user, password=hash_password)
        return self.generate_response(messages['sent_mail'], 201)
Ejemplo n.º 26
0
def reset_password1():
    form = Reset_pwd1()
    if form.validate_on_submit():
        global authcode
        authcode = random_string(length=6)

        user = User.query.filter(
            or_(User.username == form.username.data,
                User.email == form.username.data)).first()
        if user:
            global uname
            uname = user.username
            send_mail([user.email],
                      '验证码',
                      'users/mail_auth',
                      username=user.username,
                      authcode=authcode)
            flash('验证码已经发送')
            return redirect(url_for('users.reset_password2', ))
        else:
            flash('没有此用户!')
    return render_template('users/reset_pwd1.html', form=form)
Ejemplo n.º 27
0
    def create(cls, email, name, password=None, **kwargs):
        user: User = super(User, cls).create(email=email, name=name, **kwargs)

        if not password:
            # set a random password
            password = random_string(20)

        user.set_password(password)
        db.session.flush()

        # create a first alias mail to show user how to use when they login
        GenEmail.create_new(user.id, prefix="my-first-alias")
        db.session.flush()

        # Schedule onboarding emails
        Job.create(
            name=JOB_ONBOARDING_1,
            payload={"user_id": user.id},
            run_at=arrow.now().shift(days=1),
        )
        db.session.flush()

        return user
Ejemplo n.º 28
0
def setting():
    form = SettingForm()
    promo_form = PromoCodeForm()
    change_email_form = ChangeEmailForm()

    email_change = EmailChange.get_by(user_id=current_user.id)
    if email_change:
        pending_email = email_change.new_email
    else:
        pending_email = None

    if request.method == "POST":
        if request.form.get("form-name") == "update-email":
            if change_email_form.validate():
                # whether user can proceed with the email update
                new_email_valid = True
                if (sanitize_email(change_email_form.email.data) !=
                        current_user.email and not pending_email):
                    new_email = sanitize_email(change_email_form.email.data)

                    # check if this email is not already used
                    if personal_email_already_used(new_email) or Alias.get_by(
                            email=new_email):
                        flash(f"Email {new_email} already used", "error")
                        new_email_valid = False
                    elif not email_can_be_used_as_mailbox(new_email):
                        flash(
                            "You cannot use this email address as your personal inbox.",
                            "error",
                        )
                        new_email_valid = False
                    # a pending email change with the same email exists from another user
                    elif EmailChange.get_by(new_email=new_email):
                        other_email_change: EmailChange = EmailChange.get_by(
                            new_email=new_email)
                        LOG.warning(
                            "Another user has a pending %s with the same email address. Current user:%s",
                            other_email_change,
                            current_user,
                        )

                        if other_email_change.is_expired():
                            LOG.d("delete the expired email change %s",
                                  other_email_change)
                            EmailChange.delete(other_email_change.id)
                            db.session.commit()
                        else:
                            flash(
                                "You cannot use this email address as your personal inbox.",
                                "error",
                            )
                            new_email_valid = False

                    if new_email_valid:
                        email_change = EmailChange.create(
                            user_id=current_user.id,
                            code=random_string(
                                60),  # todo: make sure the code is unique
                            new_email=new_email,
                        )
                        db.session.commit()
                        send_change_email_confirmation(current_user,
                                                       email_change)
                        flash(
                            "A confirmation email is on the way, please check your inbox",
                            "success",
                        )
                        return redirect(url_for("dashboard.setting"))
        if request.form.get("form-name") == "update-profile":
            if form.validate():
                profile_updated = False
                # update user info
                if form.name.data != current_user.name:
                    current_user.name = form.name.data
                    db.session.commit()
                    profile_updated = True

                if form.profile_picture.data:
                    file_path = random_string(30)
                    file = File.create(user_id=current_user.id, path=file_path)

                    s3.upload_from_bytesio(
                        file_path, BytesIO(form.profile_picture.data.read()))

                    db.session.flush()
                    LOG.d("upload file %s to s3", file)

                    current_user.profile_picture_id = file.id
                    db.session.commit()
                    profile_updated = True

                if profile_updated:
                    flash("Your profile has been updated", "success")
                    return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "change-password":
            flash(
                "You are going to receive an email containing instructions to change your password",
                "success",
            )
            send_reset_password_email(current_user)
            return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "notification-preference":
            choose = request.form.get("notification")
            if choose == "on":
                current_user.notification = True
            else:
                current_user.notification = False
            db.session.commit()
            flash("Your notification preference has been updated", "success")
            return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "delete-account":
            # Schedule delete account job
            LOG.warning("schedule delete account job for %s", current_user)
            Job.create(
                name=JOB_DELETE_ACCOUNT,
                payload={"user_id": current_user.id},
                run_at=arrow.now(),
                commit=True,
            )

            flash(
                "Your account deletion has been scheduled. "
                "You'll receive an email when the deletion is finished",
                "success",
            )
            return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "change-alias-generator":
            scheme = int(request.form.get("alias-generator-scheme"))
            if AliasGeneratorEnum.has_value(scheme):
                current_user.alias_generator = scheme
                db.session.commit()
            flash("Your preference has been updated", "success")
            return redirect(url_for("dashboard.setting"))

        elif request.form.get(
                "form-name") == "change-random-alias-default-domain":
            default_domain = request.form.get("random-alias-default-domain")

            if default_domain:
                sl_domain: SLDomain = SLDomain.get_by(domain=default_domain)
                if sl_domain:
                    if sl_domain.premium_only and not current_user.is_premium(
                    ):
                        flash("You cannot use this domain", "error")
                        return redirect(url_for("dashboard.setting"))

                    current_user.default_alias_public_domain_id = sl_domain.id
                    current_user.default_alias_custom_domain_id = None
                else:
                    custom_domain = CustomDomain.get_by(domain=default_domain)
                    if custom_domain:
                        # sanity check
                        if (custom_domain.user_id != current_user.id
                                or not custom_domain.verified):
                            LOG.exception("%s cannot use domain %s",
                                          current_user, default_domain)
                        else:
                            current_user.default_alias_custom_domain_id = (
                                custom_domain.id)
                            current_user.default_alias_public_domain_id = None

            else:
                current_user.default_alias_custom_domain_id = None
                current_user.default_alias_public_domain_id = None

            db.session.commit()
            flash("Your preference has been updated", "success")
            return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "change-sender-format":
            sender_format = int(request.form.get("sender-format"))
            if SenderFormatEnum.has_value(sender_format):
                current_user.sender_format = sender_format
                current_user.sender_format_updated_at = arrow.now()
                db.session.commit()
                flash("Your sender format preference has been updated",
                      "success")
            db.session.commit()
            return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "replace-ra":
            choose = request.form.get("replace-ra")
            if choose == "on":
                current_user.replace_reverse_alias = True
            else:
                current_user.replace_reverse_alias = False
            db.session.commit()
            flash("Your preference has been updated", "success")
            return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "sender-in-ra":
            choose = request.form.get("enable")
            if choose == "on":
                current_user.include_sender_in_reverse_alias = True
            else:
                current_user.include_sender_in_reverse_alias = False
            db.session.commit()
            flash("Your preference has been updated", "success")
            return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "export-data":
            return redirect(url_for("api.export_data"))
        elif request.form.get("form-name") == "export-alias":
            return redirect(url_for("api.export_aliases"))

    manual_sub = ManualSubscription.get_by(user_id=current_user.id)
    apple_sub = AppleSubscription.get_by(user_id=current_user.id)
    coinbase_sub = CoinbaseSubscription.get_by(user_id=current_user.id)

    return render_template(
        "dashboard/setting.html",
        form=form,
        PlanEnum=PlanEnum,
        SenderFormatEnum=SenderFormatEnum,
        promo_form=promo_form,
        change_email_form=change_email_form,
        pending_email=pending_email,
        AliasGeneratorEnum=AliasGeneratorEnum,
        manual_sub=manual_sub,
        apple_sub=apple_sub,
        coinbase_sub=coinbase_sub,
        FIRST_ALIAS_DOMAIN=FIRST_ALIAS_DOMAIN,
    )
Ejemplo n.º 29
0
def setting():
    form = SettingForm()
    promo_form = PromoCodeForm()
    change_email_form = ChangeEmailForm()

    email_change = EmailChange.get_by(user_id=current_user.id)
    if email_change:
        pending_email = email_change.new_email
    else:
        pending_email = None

    if request.method == "POST":
        if request.form.get("form-name") == "update-email":
            if change_email_form.validate():
                # whether user can proceed with the email update
                new_email_valid = True
                if (change_email_form.email.data.lower().strip() !=
                        current_user.email and not pending_email):
                    new_email = change_email_form.email.data.strip().lower()

                    # check if this email is not already used
                    if personal_email_already_used(new_email) or Alias.get_by(
                            email=new_email):
                        flash(f"Email {new_email} already used", "error")
                        new_email_valid = False
                    elif not email_can_be_used_as_mailbox(new_email):
                        flash(
                            "You cannot use this email address as your personal inbox.",
                            "error",
                        )
                        new_email_valid = False
                    # a pending email change with the same email exists from another user
                    elif EmailChange.get_by(new_email=new_email):
                        other_email_change: EmailChange = EmailChange.get_by(
                            new_email=new_email)
                        LOG.warning(
                            "Another user has a pending %s with the same email address. Current user:%s",
                            other_email_change,
                            current_user,
                        )

                        if other_email_change.is_expired():
                            LOG.d("delete the expired email change %s",
                                  other_email_change)
                            EmailChange.delete(other_email_change.id)
                            db.session.commit()
                        else:
                            flash(
                                "You cannot use this email address as your personal inbox.",
                                "error",
                            )
                            new_email_valid = False

                    if new_email_valid:
                        email_change = EmailChange.create(
                            user_id=current_user.id,
                            code=random_string(
                                60),  # todo: make sure the code is unique
                            new_email=new_email,
                        )
                        db.session.commit()
                        send_change_email_confirmation(current_user,
                                                       email_change)
                        flash(
                            "A confirmation email is on the way, please check your inbox",
                            "success",
                        )
                        return redirect(url_for("dashboard.setting"))
        if request.form.get("form-name") == "update-profile":
            if form.validate():
                profile_updated = False
                # update user info
                if form.name.data != current_user.name:
                    current_user.name = form.name.data
                    db.session.commit()
                    profile_updated = True

                if form.profile_picture.data:
                    file_path = random_string(30)
                    file = File.create(user_id=current_user.id, path=file_path)

                    s3.upload_from_bytesio(
                        file_path, BytesIO(form.profile_picture.data.read()))

                    db.session.flush()
                    LOG.d("upload file %s to s3", file)

                    current_user.profile_picture_id = file.id
                    db.session.commit()
                    profile_updated = True

                if profile_updated:
                    flash(f"Your profile has been updated", "success")
                    return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "change-password":
            flash(
                "You are going to receive an email containing instructions to change your password",
                "success",
            )
            send_reset_password_email(current_user)
            return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "notification-preference":
            choose = request.form.get("notification")
            if choose == "on":
                current_user.notification = True
            else:
                current_user.notification = False
            db.session.commit()
            flash("Your notification preference has been updated", "success")
            return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "delete-account":
            LOG.warning("Delete account %s", current_user)
            User.delete(current_user.id)
            db.session.commit()
            flash("Your account has been deleted", "success")
            logout_user()
            return redirect(url_for("auth.register"))

        elif request.form.get("form-name") == "change-alias-generator":
            scheme = int(request.form.get("alias-generator-scheme"))
            if AliasGeneratorEnum.has_value(scheme):
                current_user.alias_generator = scheme
                db.session.commit()
            flash("Your preference has been updated", "success")
            return redirect(url_for("dashboard.setting"))

        elif request.form.get(
                "form-name") == "change-random-alias-default-domain":
            default_domain = request.form.get("random-alias-default-domain")

            if default_domain:
                sl_domain: SLDomain = SLDomain.get_by(domain=default_domain)
                if sl_domain:
                    if sl_domain.premium_only and not current_user.is_premium(
                    ):
                        flash("You cannot use this domain", "error")
                        return redirect(url_for("dashboard.setting"))

                    # make sure only default_random_alias_domain_id or default_random_alias_public_domain_id is set
                    current_user.default_random_alias_public_domain_id = sl_domain.id
                    current_user.default_random_alias_domain_id = None
                else:
                    custom_domain = CustomDomain.get_by(domain=default_domain)
                    if custom_domain:
                        # sanity check
                        if (custom_domain.user_id != current_user.id
                                or not custom_domain.verified):
                            LOG.exception("%s cannot use domain %s",
                                          current_user, default_domain)
                        else:
                            # make sure only default_random_alias_domain_id or
                            # default_random_alias_public_domain_id is set
                            current_user.default_random_alias_domain_id = (
                                custom_domain.id)
                            current_user.default_random_alias_public_domain_id = None

            else:
                current_user.default_random_alias_domain_id = None
                current_user.default_random_alias_public_domain_id = None

            db.session.commit()
            flash("Your preference has been updated", "success")
            return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "change-sender-format":
            sender_format = int(request.form.get("sender-format"))
            if SenderFormatEnum.has_value(sender_format):
                current_user.sender_format = sender_format
                db.session.commit()
                flash("Your sender format preference has been updated",
                      "success")
            db.session.commit()
            return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "replace-ra":
            choose = request.form.get("replace-ra")
            if choose == "on":
                current_user.replace_reverse_alias = True
            else:
                current_user.replace_reverse_alias = False
            db.session.commit()
            flash("Your preference has been updated", "success")
            return redirect(url_for("dashboard.setting"))

        elif request.form.get("form-name") == "export-data":
            data = {
                "email": current_user.email,
                "name": current_user.name,
                "aliases": [],
                "apps": [],
                "custom_domains": [],
            }

            for alias in Alias.filter_by(
                    user_id=current_user.id).all():  # type: Alias
                data["aliases"].append(
                    dict(email=alias.email, enabled=alias.enabled))

            for custom_domain in CustomDomain.filter_by(
                    user_id=current_user.id).all():
                data["custom_domains"].append(custom_domain.domain)

            for app in Client.filter_by(
                    user_id=current_user.id):  # type: Client
                data["apps"].append(
                    dict(name=app.name,
                         home_url=app.home_url,
                         published=app.published))

            return Response(
                json.dumps(data),
                mimetype="text/json",
                headers={
                    "Content-Disposition": "attachment;filename=data.json"
                },
            )
        elif request.form.get("form-name") == "export-alias":
            data = [["alias", "note", "enabled"]]
            for alias in Alias.filter_by(
                    user_id=current_user.id).all():  # type: Alias
                data.append([alias.email, alias.note, alias.enabled])

            si = StringIO()
            cw = csv.writer(si)
            cw.writerows(data)
            output = make_response(si.getvalue())
            output.headers[
                "Content-Disposition"] = "attachment; filename=aliases.csv"
            output.headers["Content-type"] = "text/csv"
            return output

    manual_sub = ManualSubscription.get_by(user_id=current_user.id)
    return render_template(
        "dashboard/setting.html",
        form=form,
        PlanEnum=PlanEnum,
        SenderFormatEnum=SenderFormatEnum,
        promo_form=promo_form,
        change_email_form=change_email_form,
        pending_email=pending_email,
        AliasGeneratorEnum=AliasGeneratorEnum,
        manual_sub=manual_sub,
        FIRST_ALIAS_DOMAIN=FIRST_ALIAS_DOMAIN,
    )
Ejemplo n.º 30
0
def setting():
    form = SettingForm()
    promo_form = PromoCodeForm()

    email_change = EmailChange.get_by(user_id=current_user.id)
    if email_change:
        pending_email = email_change.new_email
    else:
        pending_email = None

    if request.method == "POST":
        if request.form.get("form-name") == "update-profile":
            if form.validate():
                profile_updated = False
                # update user info
                if form.name.data != current_user.name:
                    current_user.name = form.name.data
                    db.session.commit()
                    profile_updated = True

                if form.profile_picture.data:
                    file_path = random_string(30)
                    file = File.create(path=file_path)

                    s3.upload_from_bytesio(
                        file_path, BytesIO(form.profile_picture.data.read()))

                    db.session.flush()
                    LOG.d("upload file %s to s3", file)

                    current_user.profile_picture_id = file.id
                    db.session.commit()
                    profile_updated = True

                if profile_updated:
                    flash(f"Your profile has been updated", "success")

                if (form.email.data and form.email.data != current_user.email
                        and not pending_email):
                    new_email = form.email.data

                    # check if this email is not used by other user, or as alias
                    if (User.get_by(email=new_email)
                            or GenEmail.get_by(email=new_email)
                            or DeletedAlias.get_by(email=new_email)):
                        flash(f"Email {new_email} already used", "error")
                    elif new_email.endswith(EMAIL_DOMAIN):
                        flash(
                            "You cannot use alias as your personal inbox. Nice try though 😉",
                            "error",
                        )
                    else:
                        email_change = EmailChange.create(
                            user_id=current_user.id,
                            code=random_string(
                                60),  # todo: make sure the code is unique
                            new_email=new_email,
                        )
                        db.session.commit()
                        send_change_email_confirmation(current_user,
                                                       email_change)
                        flash(
                            "A confirmation email is on the way, please check your inbox",
                            "success",
                        )

        elif request.form.get("form-name") == "change-password":
            send_reset_password_email(current_user)

        elif request.form.get("form-name") == "notification-preference":
            choose = request.form.get("notification")
            if choose == "on":
                current_user.notification = True
            else:
                current_user.notification = False
            db.session.commit()
            flash("Your notification preference has been updated", "success")

        elif request.form.get("form-name") == "delete-account":
            User.delete(current_user.id)
            db.session.commit()
            flash("Your account has been deleted", "success")
            logout_user()
            return redirect(url_for("auth.register"))

        elif request.form.get("form-name") == "change-alias-generator":
            scheme = int(request.form.get("alias-generator-scheme"))
            if AliasGeneratorEnum.has_value(scheme):
                current_user.alias_generator = scheme
                db.session.commit()
            flash("Your preference has been updated", "success")

        elif request.form.get("form-name") == "export-data":
            data = {
                "email": current_user.email,
                "name": current_user.name,
                "aliases": [],
                "apps": [],
                "custom_domains": [],
            }

            for alias in GenEmail.filter_by(
                    user_id=current_user.id).all():  # type: GenEmail
                data["aliases"].append(
                    dict(email=alias.email, enabled=alias.enabled))

            for custom_domain in CustomDomain.filter_by(
                    user_id=current_user.id).all():
                data["custom_domains"].append(custom_domain.domain)

            for app in Client.filter_by(
                    user_id=current_user.id):  # type: Client
                data["apps"].append(
                    dict(name=app.name,
                         home_url=app.home_url,
                         published=app.published))

            return Response(
                json.dumps(data),
                mimetype="text/json",
                headers={
                    "Content-Disposition": "attachment;filename=data.json"
                },
            )

        return redirect(url_for("dashboard.setting"))

    return render_template(
        "dashboard/setting.html",
        form=form,
        PlanEnum=PlanEnum,
        promo_form=promo_form,
        pending_email=pending_email,
        AliasGeneratorEnum=AliasGeneratorEnum,
    )
Ejemplo n.º 31
0
def authorize():
    """
    Redirected from client when user clicks on "Login with Server".
    This is a GET request with the following field in url
    - client_id
    - (optional) state
    - response_type: must be code
    """
    oauth_client_id = request.args.get("client_id")
    state = request.args.get("state")
    scope = request.args.get("scope")
    redirect_uri = request.args.get("redirect_uri")
    response_mode = request.args.get("response_mode")
    nonce = request.args.get("nonce")

    try:
        response_types: [ResponseType] = get_response_types(request)
    except ValueError:
        return (
            "response_type must be code, token, id_token or certain combination of these."
            " Please see /.well-known/openid-configuration to see what response_type are supported ",
            400,
        )

    if set(response_types) not in SUPPORTED_OPENID_FLOWS:
        return (
            f"SimpleLogin only support the following OIDC flows: {SUPPORTED_OPENID_FLOWS_STR}",
            400,
        )

    if not redirect_uri:
        LOG.d("no redirect uri")
        return "redirect_uri must be set", 400

    client = Client.get_by(oauth_client_id=oauth_client_id)
    if not client:
        final_redirect_uri = (
            f"{redirect_uri}?error=invalid_client_id&client_id={oauth_client_id}"
        )
        return redirect(final_redirect_uri)

    # check if redirect_uri is valid
    # allow localhost by default
    hostname, scheme = get_host_name_and_scheme(redirect_uri)
    if hostname != "localhost" and hostname != "127.0.0.1":
        # support custom scheme for mobile app
        if scheme == "http":
            final_redirect_uri = f"{redirect_uri}?error=http_not_allowed"
            return redirect(final_redirect_uri)

        if not RedirectUri.get_by(client_id=client.id, uri=redirect_uri):
            final_redirect_uri = f"{redirect_uri}?error=unknown_redirect_uri"
            return redirect(final_redirect_uri)

    # redirect from client website
    if request.method == "GET":
        if current_user.is_authenticated:
            suggested_email, other_emails, email_suffix = None, [], None
            suggested_name, other_names = None, []

            # user has already allowed this client
            client_user: ClientUser = ClientUser.get_by(
                client_id=client.id, user_id=current_user.id)
            user_info = {}
            if client_user:
                LOG.debug("user %s has already allowed client %s",
                          current_user, client)
                user_info = client_user.get_user_info()
            else:
                suggested_email, other_emails = current_user.suggested_emails(
                    client.name)
                suggested_name, other_names = current_user.suggested_names()

                user_custom_domains = [
                    cd.domain for cd in current_user.verified_custom_domains()
                ]
                # List of (is_custom_domain, alias-suffix, time-signed alias-suffix)
                suffixes = available_suffixes(current_user)

            return render_template(
                "oauth/authorize.html",
                Scope=Scope,
                EMAIL_DOMAIN=EMAIL_DOMAIN,
                **locals(),
            )
        else:
            # after user logs in, redirect user back to this page
            return render_template(
                "oauth/authorize_nonlogin_user.html",
                client=client,
                next=request.url,
                Scope=Scope,
            )
    else:  # POST - user allows or denies
        if request.form.get("button") == "deny":
            LOG.debug("User %s denies Client %s", current_user, client)
            final_redirect_uri = f"{redirect_uri}?error=deny&state={state}"
            return redirect(final_redirect_uri)

        LOG.debug("User %s allows Client %s", current_user, client)
        client_user = ClientUser.get_by(client_id=client.id,
                                        user_id=current_user.id)

        # user has already allowed this client, user cannot change information
        if client_user:
            LOG.d("user %s has already allowed client %s", current_user,
                  client)
        else:
            alias_prefix = request.form.get("prefix")
            signed_suffix = request.form.get("suffix")

            alias = None

            # user creates a new alias, not using suggested alias
            if alias_prefix:
                # should never happen as this is checked on the front-end
                if not current_user.can_create_new_alias():
                    raise Exception(
                        f"User {current_user} cannot create custom email")

                # hypothesis: user will click on the button in the 600 secs
                try:
                    alias_suffix = signer.unsign(signed_suffix,
                                                 max_age=600).decode()
                except SignatureExpired:
                    LOG.warning("Alias creation time expired for %s",
                                current_user)
                    flash("Alias creation time is expired, please retry",
                          "warning")
                    return redirect(request.url)
                except Exception:
                    LOG.exception("Alias suffix is tampered, user %s",
                                  current_user)
                    flash("Unknown error, refresh the page", "error")
                    return redirect(request.url)

                user_custom_domains = [
                    cd.domain for cd in current_user.verified_custom_domains()
                ]

                from app.dashboard.views.custom_alias import verify_prefix_suffix

                if verify_prefix_suffix(current_user, alias_prefix,
                                        alias_suffix):
                    full_alias = alias_prefix + alias_suffix

                    if (Alias.get_by(email=full_alias)
                            or DeletedAlias.get_by(email=full_alias)
                            or DomainDeletedAlias.get_by(email=full_alias)):
                        LOG.exception("alias %s already used, very rare!",
                                      full_alias)
                        flash(f"Alias {full_alias} already used", "error")
                        return redirect(request.url)
                    else:
                        alias = Alias.create(
                            user_id=current_user.id,
                            email=full_alias,
                            mailbox_id=current_user.default_mailbox_id,
                        )

                        # get the custom_domain_id if alias is created with a custom domain
                        if alias_suffix.startswith("@"):
                            alias_domain = alias_suffix[1:]
                            domain = CustomDomain.get_by(domain=alias_domain)
                            if domain:
                                alias.custom_domain_id = domain.id

                        db.session.flush()
                        flash(f"Alias {full_alias} has been created",
                              "success")
                # only happen if the request has been "hacked"
                else:
                    flash("something went wrong", "warning")
                    return redirect(request.url)
            # User chooses one of the suggestions
            else:
                chosen_email = request.form.get("suggested-email")
                # todo: add some checks on chosen_email
                if chosen_email != current_user.email:
                    alias = Alias.get_by(email=chosen_email)
                    if not alias:
                        alias = Alias.create(
                            email=chosen_email,
                            user_id=current_user.id,
                            mailbox_id=current_user.default_mailbox_id,
                        )
                        db.session.flush()

            suggested_name = request.form.get("suggested-name")
            custom_name = request.form.get("custom-name")

            use_default_avatar = request.form.get("avatar-choice") == "default"

            client_user = ClientUser.create(client_id=client.id,
                                            user_id=current_user.id)
            if alias:
                client_user.alias_id = alias.id

            if custom_name:
                client_user.name = custom_name
            elif suggested_name != current_user.name:
                client_user.name = suggested_name

            if use_default_avatar:
                # use default avatar
                LOG.d("use default avatar for user %s client %s", current_user,
                      client)
                client_user.default_avatar = True

            db.session.flush()
            LOG.d("create client-user for client %s, user %s", client,
                  current_user)

        redirect_args = {}

        if state:
            redirect_args["state"] = state
        else:
            LOG.warning(
                "more security reason, state should be added. client %s",
                client)

        if scope:
            redirect_args["scope"] = scope

        auth_code = None
        if ResponseType.CODE in response_types:
            # Create authorization code
            auth_code = AuthorizationCode.create(
                client_id=client.id,
                user_id=current_user.id,
                code=random_string(),
                scope=scope,
                redirect_uri=redirect_uri,
                response_type=response_types_to_str(response_types),
            )
            db.session.add(auth_code)
            redirect_args["code"] = auth_code.code

        oauth_token = None
        if ResponseType.TOKEN in response_types:
            # create access-token
            oauth_token = OauthToken.create(
                client_id=client.id,
                user_id=current_user.id,
                scope=scope,
                redirect_uri=redirect_uri,
                access_token=generate_access_token(),
                response_type=response_types_to_str(response_types),
            )
            db.session.add(oauth_token)
            redirect_args["access_token"] = oauth_token.access_token

        if ResponseType.ID_TOKEN in response_types:
            redirect_args["id_token"] = make_id_token(
                client_user,
                nonce,
                oauth_token.access_token if oauth_token else None,
                auth_code.code if auth_code else None,
            )

        db.session.commit()

        # should all params appended the url using fragment (#) or query
        fragment = False

        if response_mode and response_mode == "fragment":
            fragment = True

        # if response_types contain "token" => implicit flow => should use fragment
        # except if client sets explicitly response_mode
        if not response_mode:
            if ResponseType.TOKEN in response_types:
                fragment = True

        # construct redirect_uri with redirect_args
        return redirect(construct_url(redirect_uri, redirect_args, fragment))