Ejemplo n.º 1
0
def test_available_suffixes(flask_client):
    user = login(flask_client)

    CustomDomain.create(user_id=user.id, domain="test.com", verified=True)

    assert len(get_available_suffixes(user)) > 0

    # first suffix is custom domain
    first_suffix = get_available_suffixes(user)[0]
    assert first_suffix.is_custom
    assert first_suffix.suffix == "@test.com"
    assert first_suffix.signed_suffix.startswith("@test.com")
Ejemplo n.º 2
0
def test_available_suffixes_default_domain(flask_client):
    user = login(flask_client)

    sl_domain = SLDomain.query.first()
    CustomDomain.create(user_id=user.id, domain="test.com", verified=True, commit=True)

    user.default_alias_public_domain_id = sl_domain.id

    # first suffix is SL Domain
    first_suffix = get_available_suffixes(user)[0]
    assert first_suffix.suffix.endswith(f"@{sl_domain.domain}")

    user.default_alias_public_domain_id = None
    # first suffix is custom domain
    first_suffix = get_available_suffixes(user)[0]
    assert first_suffix.suffix == "@test.com"
Ejemplo n.º 3
0
def options_v4():
    """
    Return what options user has when creating new alias.
    Same as v3 but return time-based signed-suffix in addition to suffix. To be used with /v2/alias/custom/new
    Input:
        a valid api-key in "Authentication" header and
        optional "hostname" in args
    Output: cf README
        can_create: bool
        suffixes: [[suffix, signed_suffix]]
        prefix_suggestion: str
        recommendation: Optional dict
            alias: str
            hostname: str


    """
    user = g.user
    hostname = request.args.get("hostname")

    ret = {
        "can_create": user.can_create_new_alias(),
        "suffixes": [],
        "prefix_suggestion": "",
    }

    # recommendation alias if exist
    if hostname:
        # put the latest used alias first
        q = (Session.query(AliasUsedOn, Alias, User).filter(
            AliasUsedOn.alias_id == Alias.id,
            Alias.user_id == user.id,
            AliasUsedOn.hostname == hostname,
        ).order_by(desc(AliasUsedOn.created_at)))

        r = q.first()
        if r:
            _, alias, _ = r
            LOG.d("found alias %s %s %s", alias, hostname, user)
            ret["recommendation"] = {
                "alias": alias.email,
                "hostname": hostname
            }

    # custom alias suggestion and suffix
    if hostname:
        # keep only the domain name of hostname, ignore TLD and subdomain
        # for ex www.groupon.com -> groupon
        ext = tldextract.extract(hostname)
        prefix_suggestion = ext.domain
        prefix_suggestion = convert_to_id(prefix_suggestion)
        ret["prefix_suggestion"] = prefix_suggestion

    suffixes = get_available_suffixes(user)

    # custom domain should be put first
    ret["suffixes"] = list([suffix.suffix, suffix.signed_suffix]
                           for suffix in suffixes)

    return jsonify(ret)
Ejemplo n.º 4
0
def test_available_suffixes_random_prefix_generation(flask_client):
    user = login(flask_client)

    CustomDomain.create(user_id=user.id, domain="test.com", verified=True, commit=True)
    cd2 = CustomDomain.create(
        user_id=user.id, domain="test2.com", verified=True, commit=True
    )

    user.default_alias_custom_domain_id = cd2.id

    # first suffix is test2.com
    first_suffix = get_available_suffixes(user)[0]
    assert first_suffix.suffix == "@test2.com"

    cd2.random_prefix_generation = True
    # e.g. [email protected]
    first_suffix = get_available_suffixes(user)[0]
    assert first_suffix.suffix.endswith("@test2.com")
    assert first_suffix.suffix.startswith(".")
Ejemplo n.º 5
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
    # allow any redirect_uri if the app isn't approved
    hostname, scheme = get_host_name_and_scheme(redirect_uri)
    if hostname != "localhost" and hostname != "127.0.0.1" and client.approved:
        # 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.d("user %s has already allowed client %s", current_user,
                      client)
                user_info = client_user.get_user_info()

                # redirect user to the client page
                redirect_args = construct_redirect_args(
                    client,
                    client_user,
                    nonce,
                    redirect_uri,
                    response_types,
                    scope,
                    state,
                )
                fragment = get_fragment(response_mode, response_types)

                # construct redirect_uri with redirect_args
                return redirect(
                    construct_url(redirect_uri, redirect_args, fragment))
            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()
                ]
                suffixes = get_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 not current_user.is_authenticated or not current_user.is_active:
            LOG.i(
                "Attempt to validate a OAUth allow request by an unauthenticated user"
            )
            return redirect(url_for("auth.login", next=request.url))

        if request.form.get("button") == "deny":
            LOG.d("User %s denies Client %s", current_user, client)
            final_redirect_uri = f"{redirect_uri}?error=deny&state={state}"
            return redirect(final_redirect_uri)

        LOG.d("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")

                alias_prefix = alias_prefix.strip().lower().replace(" ", "")

                if not check_alias_prefix(alias_prefix):
                    flash(
                        "Only lowercase letters, numbers, dashes (-), dots (.) and underscores (_) "
                        "are currently supported for alias prefix. Cannot be more than 40 letters",
                        "error",
                    )
                    return redirect(request.url)

                # 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.w("Alias creation time expired for %s", current_user)
                    flash("Alias creation time is expired, please retry",
                          "warning")
                    return redirect(request.url)
                except Exception:
                    LOG.w("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.e("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,
                        )

                        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,
                        )
                        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

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

        redirect_args = construct_redirect_args(client, client_user, nonce,
                                                redirect_uri, response_types,
                                                scope, state)
        fragment = get_fragment(response_mode, response_types)

        # construct redirect_uri with redirect_args
        return redirect(construct_url(redirect_uri, redirect_args, fragment))
Ejemplo n.º 6
0
def new_random_alias():
    """
    Create a new random alias
    Input:
        (Optional) note
    Output:
        201 if success

    """
    user = g.user
    if not user.can_create_new_alias():
        LOG.d("user %s cannot create new random alias", user)
        return (
            jsonify(
                error=
                f"You have reached the limitation of a free account with the maximum of "
                f"{MAX_NB_EMAIL_FREE_PLAN} aliases, please upgrade your plan to create more aliases"
            ),
            400,
        )

    note = None
    data = request.get_json(silent=True)
    if data:
        note = data.get("note")

    alias = None

    # custom alias suggestion and suffix
    hostname = request.args.get("hostname")
    if hostname and user.include_website_in_one_click_alias:
        LOG.d("Use %s to create new alias", hostname)
        # keep only the domain name of hostname, ignore TLD and subdomain
        # for ex www.groupon.com -> groupon
        ext = tldextract.extract(hostname)
        prefix_suggestion = ext.domain
        prefix_suggestion = convert_to_id(prefix_suggestion)

        suffixes = get_available_suffixes(user)
        # use the first suffix
        suggested_alias = prefix_suggestion + suffixes[0].suffix

        alias = Alias.get_by(email=suggested_alias)

        # cannot use this alias as it belongs to another user
        if alias and not alias.user_id == user.id:
            LOG.d("%s belongs to another user", alias)
            alias = None
        elif alias and alias.user_id == user.id:
            # make sure alias was created for this website
            if AliasUsedOn.get_by(alias_id=alias.id,
                                  hostname=hostname,
                                  user_id=alias.user_id):
                LOG.d("Use existing alias %s", alias)
            else:
                LOG.d("%s wasn't created for this website %s", alias, hostname)
                alias = None
        elif not alias:
            LOG.d("create new alias %s", suggested_alias)
            try:
                alias = Alias.create(
                    user_id=user.id,
                    email=suggested_alias,
                    note=note,
                    mailbox_id=user.default_mailbox_id,
                    commit=True,
                )
            except AliasInTrashError:
                LOG.i("Alias %s is in trash", suggested_alias)
                alias = None

    if not alias:
        scheme = user.alias_generator
        mode = request.args.get("mode")
        if mode:
            if mode == "word":
                scheme = AliasGeneratorEnum.word.value
            elif mode == "uuid":
                scheme = AliasGeneratorEnum.uuid.value
            else:
                return jsonify(
                    error=f"{mode} must be either word or uuid"), 400

        alias = Alias.create_new_random(user=user, scheme=scheme, note=note)
        Session.commit()

    if hostname and not AliasUsedOn.get_by(alias_id=alias.id,
                                           hostname=hostname):
        AliasUsedOn.create(alias_id=alias.id,
                           hostname=hostname,
                           user_id=alias.user_id)
        Session.commit()

    return (
        jsonify(alias=alias.email,
                **serialize_alias_info_v2(get_alias_info_v2(alias))),
        201,
    )
Ejemplo n.º 7
0
def options_v5():
    """
    Return what options user has when creating new alias.
    Same as v4 but uses a better format. To be used with /v2/alias/custom/new
    Input:
        a valid api-key in "Authentication" header and
        optional "hostname" in args
    Output: cf README
        can_create: bool
        suffixes: [
            {
                suffix: "suffix",
                signed_suffix: "signed_suffix"
            }
        ]
        prefix_suggestion: str
        recommendation: Optional dict
            alias: str
            hostname: str


    """
    user = g.user
    hostname = request.args.get("hostname")

    ret = {
        "can_create": user.can_create_new_alias(),
        "suffixes": [],
        "prefix_suggestion": "",
    }

    # recommendation alias if exist
    if hostname:
        # put the latest used alias first
        q = (
            db.session.query(AliasUsedOn, Alias, User)
            .filter(
                AliasUsedOn.alias_id == Alias.id,
                Alias.user_id == user.id,
                AliasUsedOn.hostname == hostname,
            )
            .order_by(desc(AliasUsedOn.created_at))
        )

        r = q.first()
        if r:
            _, alias, _ = r
            LOG.d("found alias %s %s %s", alias, hostname, user)
            ret["recommendation"] = {"alias": alias.email, "hostname": hostname}

    # custom alias suggestion and suffix
    if hostname:
        # keep only the domain name of hostname, ignore TLD and subdomain
        # for ex www.groupon.com -> groupon
        domain_name = hostname
        if "." in hostname:
            parts = hostname.split(".")
            domain_name = parts[-2]
            domain_name = convert_to_id(domain_name)
        ret["prefix_suggestion"] = domain_name

    suffixes = get_available_suffixes(user)

    # custom domain should be put first
    ret["suffixes"] = [
        {"suffix": suffix.suffix, "signed_suffix": suffix.signed_suffix}
        for suffix in suffixes
    ]

    return jsonify(ret)