def available_suffixes_more_info(user: User) -> [SuffixInfo]: """ Similar to as available_suffixes() but also return whether the suffix comes from a premium domain Note that is-premium-domain is only relevant for SL domain """ user_custom_domains = user.verified_custom_domains() suffixes: [SuffixInfo] = [] # put custom domain first # for each user domain, generate both the domain and a random suffix version for alias_domain in user_custom_domains: suffix = "@" + alias_domain.domain suffixes.append( SuffixInfo(True, suffix, signer.sign(suffix).decode(), False)) if alias_domain.random_prefix_generation: suffix = "." + random_word() + "@" + alias_domain.domain suffixes.append( SuffixInfo(True, suffix, signer.sign(suffix).decode(), False)) # then SimpleLogin domain for sl_domain in user.get_sl_domains(): suffix = (("" if DISABLE_ALIAS_SUFFIX else "." + random_word()) + "@" + sl_domain.domain) suffixes.append( SuffixInfo(False, suffix, signer.sign(suffix).decode(), sl_domain.premium_only)) return suffixes
def get_available_suffixes(user: User) -> [SuffixInfo]: """ Similar to as available_suffixes() but also return whether the suffix comes from a premium domain Note that is-premium-domain is only relevant for SL domain """ user_custom_domains = user.verified_custom_domains() suffixes: [SuffixInfo] = [] # put custom domain first # for each user domain, generate both the domain and a random suffix version for custom_domain in user_custom_domains: if custom_domain.random_prefix_generation: suffix = "." + random_word() + "@" + custom_domain.domain suffix_info = SuffixInfo(True, suffix, signer.sign(suffix).decode(), False) if user.default_alias_custom_domain_id == custom_domain.id: suffixes.insert(0, suffix_info) else: suffixes.append(suffix_info) suffix = "@" + custom_domain.domain suffix_info = SuffixInfo(True, suffix, signer.sign(suffix).decode(), False) # put the default domain to top # only if random_prefix_generation isn't enabled if ( user.default_alias_custom_domain_id == custom_domain.id and not custom_domain.random_prefix_generation ): suffixes.insert(0, suffix_info) else: suffixes.append(suffix_info) # then SimpleLogin domain for sl_domain in user.get_sl_domains(): suffix = ( ("" if DISABLE_ALIAS_SUFFIX else "." + random_word()) + "@" + sl_domain.domain ) suffix_info = SuffixInfo( False, suffix, signer.sign(suffix).decode(), sl_domain.premium_only ) # put the default domain to top if user.default_alias_public_domain_id == sl_domain.id: suffixes.insert(0, suffix_info) else: suffixes.append(suffix_info) return suffixes
def test_out_of_quota(flask_client): user = login(flask_client) user.trial_end = None Session.commit() # create MAX_NB_EMAIL_FREE_PLAN custom alias to run out of quota for _ in range(MAX_NB_EMAIL_FREE_PLAN): Alias.create_new(user, prefix="test") word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" signed_suffix = signer.sign(suffix).decode() r = flask_client.post( "/api/v3/alias/custom/new", json={ "alias_prefix": "prefix", "signed_suffix": signed_suffix, "note": "test note", "mailbox_ids": [user.default_mailbox_id], "name": "your name", }, ) assert r.status_code == 400 assert r.json == { "error": "You have reached the limitation of a " "free account with the maximum of 3 aliases, please upgrade your plan to create more aliases" }
def test_v1(flask_client): login(flask_client) word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" r = flask_client.post( "/api/alias/custom/new", json={ "alias_prefix": "prefix", "alias_suffix": suffix, }, ) assert r.status_code == 201 assert r.json["alias"] == f"prefix.{word}@{EMAIL_DOMAIN}" res = r.json assert "id" in res assert "email" in res assert "creation_date" in res assert "creation_timestamp" in res assert "nb_forward" in res assert "nb_block" in res assert "nb_reply" in res assert "enabled" in res new_alias: Alias = Alias.get_by(email=r.json["alias"]) assert len(new_alias.mailboxes) == 1
def test_out_of_quota(flask_client): user = User.create(email="[email protected]", password="******", name="Test User", activated=True) user.trial_end = None db.session.commit() # create api_key api_key = ApiKey.create(user.id, "for test") db.session.commit() # create MAX_NB_EMAIL_FREE_PLAN custom alias to run out of quota for _ in range(MAX_NB_EMAIL_FREE_PLAN): GenEmail.create_new(user.id, prefix="test") word = random_word() r = flask_client.post( url_for("api.new_custom_alias", hostname="www.test.com"), headers={"Authentication": api_key.code}, json={ "alias_prefix": "prefix", "alias_suffix": f".{word}@{EMAIL_DOMAIN}" }, ) assert r.status_code == 400 assert r.json == { "error": "You have reached the limitation of a free account with the maximum of 3 aliases, please upgrade your plan to create more aliases" }
def test_add_alias_multiple_mailboxes(flask_client): user = login(flask_client) db.session.commit() word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" suffix = signer.sign(suffix).decode() # create with a multiple mailboxes mb1 = Mailbox.create(user_id=user.id, email="*****@*****.**", verified=True) db.session.commit() r = flask_client.post( url_for("dashboard.custom_alias"), data={ "prefix": "prefix", "suffix": suffix, "mailboxes": [user.default_mailbox_id, mb1.id], }, follow_redirects=True, ) assert r.status_code == 200 assert f"Alias prefix.{word}@{EMAIL_DOMAIN} has been created" in str( r.data) alias = Alias.query.order_by(Alias.created_at.desc()).first() assert alias._mailboxes
def test_create_custom_alias_without_note(flask_client): user = User.create(email="[email protected]", password="******", name="Test User", activated=True) db.session.commit() # create api_key api_key = ApiKey.create(user.id, "for test") db.session.commit() # create alias without note word = random_word() r = flask_client.post( url_for("api.new_custom_alias", hostname="www.test.com"), headers={"Authentication": api_key.code}, json={ "alias_prefix": "prefix", "alias_suffix": f".{word}@{EMAIL_DOMAIN}" }, ) assert r.status_code == 201 assert r.json["alias"] == f"prefix.{word}@{EMAIL_DOMAIN}" new_ge = Alias.get_by(email=r.json["alias"]) assert new_ge.note is None
def test_minimal_payload(flask_client): user = login(flask_client) word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" signed_suffix = signer.sign(suffix).decode() r = flask_client.post( "/api/v3/alias/custom/new", json={ "alias_prefix": "prefix", "signed_suffix": signed_suffix, "mailbox_ids": [user.default_mailbox_id], }, ) assert r.status_code == 201 assert r.json["alias"] == f"prefix.{word}@{EMAIL_DOMAIN}" res = r.json assert "id" in res assert "email" in res assert "creation_date" in res assert "creation_timestamp" in res assert "nb_forward" in res assert "nb_block" in res assert "nb_reply" in res assert "enabled" in res new_alias: Alias = Alias.get_by(email=r.json["alias"]) assert len(new_alias.mailboxes) == 1
def test_verify_prefix_suffix(flask_client): user = login(flask_client) db.session.commit() CustomDomain.create(user_id=user.id, domain="test.com", verified=True) assert verify_prefix_suffix(user, "prefix", "@test.com") assert not verify_prefix_suffix(user, "prefix", "@abcd.com") word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" assert verify_prefix_suffix(user, "prefix", suffix)
def test_success_v3(flask_client): user = User.create( email="[email protected]", password="******", name="Test User", activated=True, ) db.session.commit() # create api_key api_key = ApiKey.create(user.id, "for test") db.session.commit() # create another mailbox mb = Mailbox.create(user_id=user.id, email="*****@*****.**", verified=True) db.session.commit() # create new alias with note word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" suffix = signer.sign(suffix).decode() r = flask_client.post( url_for("api.new_custom_alias_v3", hostname="www.test.com"), headers={"Authentication": api_key.code}, json={ "alias_prefix": "prefix", "signed_suffix": suffix, "note": "test note", "mailbox_ids": [user.default_mailbox_id, mb.id], "name": "your name", }, ) assert r.status_code == 201 assert r.json["alias"] == f"prefix.{word}@{EMAIL_DOMAIN}" # assert returned field res = r.json assert "id" in res assert "email" in res assert "creation_date" in res assert "creation_timestamp" in res assert "nb_forward" in res assert "nb_block" in res assert "nb_reply" in res assert "enabled" in res assert "note" in res assert res["name"] == "your name" new_alias: Alias = Alias.get_by(email=r.json["alias"]) assert new_alias.note == "test note" assert len(new_alias.mailboxes) == 2
def custom_alias(): # check if user has not exceeded the alias quota if not current_user.can_create_new_alias(): # notify admin LOG.error("user %s tries to create custom alias", current_user) flash("ony premium user can choose custom alias", "warning") return redirect(url_for("dashboard.index")) user_custom_domains = [cd.domain for cd in current_user.verified_custom_domains()] # List of (is_custom_domain, alias-suffix) suffixes = [] # put custom domain first for alias_domain in user_custom_domains: suffixes.append((True, "@" + alias_domain)) # then default domain for domain in ALIAS_DOMAINS: suffixes.append( ( False, ("" if DISABLE_ALIAS_SUFFIX else "." + random_word()) + "@" + domain, ) ) if request.method == "POST": alias_prefix = request.form.get("prefix") alias_suffix = request.form.get("suffix") if verify_prefix_suffix( current_user, alias_prefix, alias_suffix, user_custom_domains ): full_alias = alias_prefix + alias_suffix if GenEmail.get_by(email=full_alias): LOG.d("full alias already used %s", full_alias) flash( f"Alias {full_alias} already exists, please choose another one", "warning", ) else: gen_email = GenEmail.create(user_id=current_user.id, email=full_alias) db.session.commit() flash(f"Alias {full_alias} has been created", "success") session[HIGHLIGHT_GEN_EMAIL_ID] = gen_email.id return redirect(url_for("dashboard.index")) # only happen if the request has been "hacked" else: flash("something went wrong", "warning") return render_template("dashboard/custom_alias.html", **locals())
def create_new(cls, user_id, prefix, note=None): if not prefix: raise Exception("alias prefix cannot be empty") # find the right suffix - avoid infinite loop by running this at max 1000 times for i in range(1000): suffix = random_word() email = f"{prefix}.{suffix}@{EMAIL_DOMAIN}" if not cls.get_by(email=email): break return GenEmail.create(user_id=user_id, email=email, note=note)
def available_suffixes(user: User) -> [bool, str, str]: """Return (is_custom_domain, alias-suffix, time-signed alias-suffix)""" user_custom_domains = user.verified_custom_domains() # List of (is_custom_domain, alias-suffix, time-signed alias-suffix) suffixes = [] # put custom domain first # for each user domain, generate both the domain and a random suffix version for alias_domain in user_custom_domains: suffix = "@" + alias_domain.domain suffixes.append((True, suffix, signer.sign(suffix).decode())) if alias_domain.random_prefix_generation: suffix = "." + random_word() + "@" + alias_domain.domain suffixes.append((True, suffix, signer.sign(suffix).decode())) # then SimpleLogin domain for domain in user.available_sl_domains(): suffix = ("" if DISABLE_ALIAS_SUFFIX else "." + random_word()) + "@" + domain suffixes.append((False, suffix, signer.sign(suffix).decode())) return suffixes
def test_full_payload(flask_client): """Create alias with: - additional mailbox - note - name - hostname (in URL) """ user = login(flask_client) # create another mailbox mb = Mailbox.create(user_id=user.id, email="*****@*****.**", verified=True) Session.commit() word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" signed_suffix = signer.sign(suffix).decode() assert AliasUsedOn.count() == 0 r = flask_client.post( "/api/v3/alias/custom/new?hostname=example.com", json={ "alias_prefix": "prefix", "signed_suffix": signed_suffix, "note": "test note", "mailbox_ids": [user.default_mailbox_id, mb.id], "name": "your name", }, ) assert r.status_code == 201 assert r.json["alias"] == f"prefix.{word}@{EMAIL_DOMAIN}" # assert returned field res = r.json assert res["note"] == "test note" assert res["name"] == "your name" new_alias: Alias = Alias.get_by(email=r.json["alias"]) assert new_alias.note == "test note" assert len(new_alias.mailboxes) == 2 alias_used_on = AliasUsedOn.first() assert alias_used_on.alias_id == new_alias.id assert alias_used_on.hostname == "example.com"
def create_new(cls, user, prefix, note=None, mailbox_id=None): if not prefix: raise Exception("alias prefix cannot be empty") # find the right suffix - avoid infinite loop by running this at max 1000 times for i in range(1000): suffix = random_word() email = f"{prefix}.{suffix}@{FIRST_ALIAS_DOMAIN}" if not cls.get_by(email=email) and not DeletedAlias.get_by(email=email): break return Alias.create( user_id=user.id, email=email, note=note, mailbox_id=mailbox_id or user.default_mailbox_id, )
def available_suffixes(user: User) -> [bool, str, str]: """Return (is_custom_domain, alias-suffix, time-signed alias-suffix)""" user_custom_domains = [cd.domain for cd in user.verified_custom_domains()] # List of (is_custom_domain, alias-suffix, time-signed alias-suffix) suffixes = [] # put custom domain first for alias_domain in user_custom_domains: suffix = "@" + alias_domain suffixes.append((True, suffix, signer.sign(suffix).decode())) # then default domain for domain in ALIAS_DOMAINS: suffix = ("" if DISABLE_ALIAS_SUFFIX else "." + random_word()) + "@" + domain suffixes.append((False, suffix, signer.sign(suffix).decode())) return suffixes
def test_mailbox_ids_is_not_an_array(flask_client): login(flask_client) word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" signed_suffix = signer.sign(suffix).decode() r = flask_client.post( "/api/v3/alias/custom/new", json={ "alias_prefix": "prefix", "signed_suffix": signed_suffix, "mailbox_ids": "not an array", }, ) assert r.status_code == 400 assert r.json == {"error": "mailbox_ids must be an array of id"}
def test_add_alias_success(flask_client): user = login(flask_client) db.session.commit() word = random_word() r = flask_client.post( url_for("dashboard.custom_alias"), data={ "prefix": "prefix", "suffix": f".{word}@{EMAIL_DOMAIN}", "mailbox": user.email, }, follow_redirects=True, ) assert r.status_code == 200 assert f"Alias prefix.{word}@{EMAIL_DOMAIN} has been created" in str(r.data)
def test_add_alias_in_global_trash(flask_client): user = login(flask_client) Session.commit() another_user = User.create( email="[email protected]", password="******", name="Test User", activated=True, commit=True, ) word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" alias_suffix = AliasSuffix(is_custom=False, suffix=suffix, is_premium=False, domain=EMAIL_DOMAIN) signed_alias_suffix = signer.sign(alias_suffix.serialize()).decode() # delete an alias: alias should go the DeletedAlias alias = Alias.create( user_id=another_user.id, email=f"prefix{suffix}", mailbox_id=another_user.default_mailbox_id, commit=True, ) assert DeletedAlias.count() == 0 delete_alias(alias, another_user) assert DeletedAlias.count() == 1 # create the same alias, should return error r = flask_client.post( url_for("dashboard.custom_alias"), data={ "prefix": "prefix", "signed-alias-suffix": signed_alias_suffix, "mailboxes": [user.default_mailbox_id], }, follow_redirects=True, ) assert r.status_code == 200 assert f"prefix{suffix} cannot be used" in r.get_data(True)
def test_success_v2(flask_client): user = User.create(email="[email protected]", password="******", name="Test User", activated=True) db.session.commit() # create api_key api_key = ApiKey.create(user.id, "for test") db.session.commit() # create new alias with note word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" suffix = signer.sign(suffix).decode() r = flask_client.post( url_for("api.new_custom_alias_v2", hostname="www.test.com"), headers={"Authentication": api_key.code}, json={ "alias_prefix": "prefix", "signed_suffix": suffix, "note": "test note", }, ) assert r.status_code == 201 assert r.json["alias"] == f"prefix.{word}@{EMAIL_DOMAIN}" # assert returned field res = r.json assert "id" in res assert "email" in res assert "creation_date" in res assert "creation_timestamp" in res assert "nb_forward" in res assert "nb_block" in res assert "nb_reply" in res assert "enabled" in res assert "note" in res new_ge = Alias.get_by(email=r.json["alias"]) assert new_ge.note == "test note"
def test_invalid_alias_2_consecutive_dots(flask_client): user = login(flask_client) word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" signed_suffix = signer.sign(suffix).decode() r = flask_client.post( "/api/v3/alias/custom/new", json={ "alias_prefix": "prefix.", # with the trailing dot, the alias will have 2 consecutive dots "signed_suffix": signed_suffix, "mailbox_ids": [user.default_mailbox_id], }, ) assert r.status_code == 400 assert r.json == { "error": "2 consecutive dot signs aren't allowed in an email address" }
def test_add_alias_success(flask_client): user = login(flask_client) word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" suffix = signer.sign(suffix).decode() # create with a single mailbox r = flask_client.post( url_for("dashboard.custom_alias"), data={ "prefix": "prefix", "suffix": suffix, "mailboxes": [user.default_mailbox_id], }, follow_redirects=True, ) assert r.status_code == 200 assert f"Alias prefix.{word}@{EMAIL_DOMAIN} has been created" in str( r.data) alias = Alias.query.order_by(Alias.created_at.desc()).first() assert not alias._mailboxes
def test_add_already_existed_alias(flask_client): user = login(flask_client) db.session.commit() another_user = User.create( email="[email protected]", password="******", name="Test User", activated=True, commit=True, ) word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" signed_suffix = signer.sign(suffix).decode() # alias already exist Alias.create( user_id=another_user.id, email=f"prefix{suffix}", mailbox_id=another_user.default_mailbox_id, commit=True, ) # create the same alias, should return error r = flask_client.post( url_for("dashboard.custom_alias"), data={ "prefix": "prefix", "suffix": signed_suffix, "mailboxes": [user.default_mailbox_id], }, follow_redirects=True, ) assert r.status_code == 200 assert f"prefix{suffix} cannot be used" in r.get_data(True)
def custom_alias(): # check if user has not exceeded the alias quota if not current_user.can_create_new_alias(): # notify admin LOG.error("user %s tries to create custom alias", current_user) flash( "You have reached free plan limit, please upgrade to create new aliases", "warning", ) return redirect(url_for("dashboard.index")) user_custom_domains = [cd.domain for cd in current_user.verified_custom_domains()] # List of (is_custom_domain, alias-suffix) suffixes = [] # put custom domain first for alias_domain in user_custom_domains: suffixes.append((True, "@" + alias_domain)) # then default domain for domain in ALIAS_DOMAINS: suffixes.append( ( False, ("" if DISABLE_ALIAS_SUFFIX else "." + random_word()) + "@" + domain, ) ) if request.method == "POST": alias_prefix = request.form.get("prefix") alias_suffix = request.form.get("suffix") alias_note = request.form.get("note") if verify_prefix_suffix( current_user, alias_prefix, alias_suffix, user_custom_domains ): full_alias = alias_prefix + alias_suffix if GenEmail.get_by(email=full_alias) or DeletedAlias.get_by( email=full_alias ): LOG.d("full alias already used %s", full_alias) flash( f"Alias {full_alias} already exists, please choose another one", "warning", ) else: gen_email = GenEmail.create( user_id=current_user.id, email=full_alias, note=alias_note ) # get the custom_domain_id if alias is created with a custom domain alias_domain = get_email_domain_part(full_alias) custom_domain = CustomDomain.get_by(domain=alias_domain) if custom_domain: gen_email.custom_domain_id = custom_domain.id db.session.commit() flash(f"Alias {full_alias} has been created", "success") return redirect( url_for("dashboard.index", highlight_gen_email_id=gen_email.id) ) # only happen if the request has been "hacked" else: flash("something went wrong", "warning") return render_template("dashboard/custom_alias.html", **locals())
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() email_suffix = random_word() return render_template( "oauth/authorize.html", client=client, user_info=user_info, client_user=client_user, Scope=Scope, suggested_email=suggested_email, personal_email=current_user.email, suggested_name=suggested_name, other_names=other_names, other_emails=other_emails, email_suffix=email_suffix, EMAIL_DOMAIN=EMAIL_DOMAIN, ) 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: # 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: email_suffix = request.form.get("email-suffix") custom_email_prefix = request.form.get("custom-email-prefix") chosen_email = request.form.get("suggested-email") suggested_name = request.form.get("suggested-name") custom_name = request.form.get("custom-name") use_default_avatar = request.form.get("avatar-choice") == "default" gen_email = None if custom_email_prefix: # check if user can generate custom email if not current_user.can_create_new_alias(): raise Exception(f"User {current_user} cannot create custom email") email = f"{convert_to_id(custom_email_prefix)}.{email_suffix}@{EMAIL_DOMAIN}" LOG.d("create custom email alias %s for user %s", email, current_user) if GenEmail.get_by(email=email) or DeletedAlias.get_by(email=email): LOG.error("email %s already used, very rare!", email) flash(f"alias {email} already used", "error") return redirect(request.url) gen_email = GenEmail.create(email=email, user_id=current_user.id) db.session.flush() else: # user picks an email from suggestion if chosen_email != current_user.email: gen_email = GenEmail.get_by(email=chosen_email) if not gen_email: gen_email = GenEmail.create( email=chosen_email, user_id=current_user.id ) db.session.flush() client_user = ClientUser.create( client_id=client.id, user_id=current_user.id ) if gen_email: client_user.gen_email_id = gen_email.id if custom_name: LOG.d( "use custom name %s for user %s client %s", custom_name, current_user, client, ) client_user.name = custom_name elif suggested_name != current_user.name: LOG.d( "use another name %s for user %s client %s", custom_name, current_user, client, ) 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))
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) suffixes = [] # put custom domain first for alias_domain in user_custom_domains: suffixes.append((True, "@" + alias_domain)) # then default domain for domain in ALIAS_DOMAINS: suffixes.append(( False, ("" if DISABLE_ALIAS_SUFFIX else "." + random_word()) + "@" + domain, )) 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") alias_suffix = request.form.get("suffix") gen_email = 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") 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, user_custom_domains): full_alias = alias_prefix + alias_suffix if GenEmail.get_by( email=full_alias) or DeletedAlias.get_by( email=full_alias): LOG.error("alias %s already used, very rare!", full_alias) flash(f"Alias {full_alias} already used", "error") return redirect(request.url) else: gen_email = GenEmail.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 alias_domain = get_email_domain_part(full_alias) custom_domain = CustomDomain.get_by( domain=alias_domain) if custom_domain: gen_email.custom_domain_id = custom_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: gen_email = GenEmail.get_by(email=chosen_email) if not gen_email: gen_email = GenEmail.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 gen_email: client_user.gen_email_id = gen_email.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))
def custom_alias(): # check if user has the right to create custom alias if not current_user.can_create_new_alias(): # notify admin LOG.error("user %s tries to create custom alias", current_user) flash("ony premium user can choose custom alias", "warning") return redirect(url_for("dashboard.index")) error = "" if request.method == "POST": if request.form.get("form-name") == "non-custom-domain-name": email_prefix = request.form.get("email-prefix") email_prefix = convert_to_id(email_prefix) email_suffix = request.form.get("email-suffix") if not email_prefix: error = "alias prefix cannot be empty" else: full_email = f"{email_prefix}.{email_suffix}@{EMAIL_DOMAIN}" # check if email already exists if GenEmail.get_by(email=full_email) or DeletedAlias.get_by( email=full_email): error = "email already chosen, please choose another one" else: # create the new alias LOG.d("create custom alias %s for user %s", full_email, current_user) gen_email = GenEmail.create(email=full_email, user_id=current_user.id) db.session.commit() flash(f"Alias {full_email} has been created", "success") session[HIGHLIGHT_GEN_EMAIL_ID] = gen_email.id return redirect(url_for("dashboard.index")) elif request.form.get("form-name") == "custom-domain-name": custom_domain_id = request.form.get("custom-domain-id") email = request.form.get("email") custom_domain = CustomDomain.get(custom_domain_id) if not custom_domain: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.custom_alias")) elif custom_domain.user_id != current_user.id: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.custom_alias")) elif not custom_domain.verified: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.custom_alias")) full_email = f"{email}@{custom_domain.domain}" if GenEmail.get_by(email=full_email): error = f"{full_email} already exist, please choose another one" else: LOG.d( "create custom alias %s for custom domain %s", full_email, custom_domain.domain, ) gen_email = GenEmail.create( email=full_email, user_id=current_user.id, custom_domain_id=custom_domain.id, ) db.session.commit() flash(f"Alias {full_email} has been created", "success") session[HIGHLIGHT_GEN_EMAIL_ID] = gen_email.id return redirect(url_for("dashboard.index")) email_suffix = random_word() return render_template( "dashboard/custom_alias.html", error=error, email_suffix=email_suffix, EMAIL_DOMAIN=EMAIL_DOMAIN, custom_domains=current_user.verified_custom_domains(), )