Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add possibility to allow/disallow main email, email alias and email forward form portal #1997

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,13 @@
"global_settings_setting_pop3_enabled": "Enable POP3",
"global_settings_setting_pop3_enabled_help": "Enable the POP3 protocol for the mail server. POP3 is an older protocol to access mailboxes from email clients and is more lightweight, but has less features than IMAP (enabled by default)",
"global_settings_setting_pop3_name": "POP3",
"global_settings_setting_portal_name": "Portal",
"global_settings_setting_portal_allow_edit_email": "Allow users to edit email",
"global_settings_setting_portal_allow_edit_email_alias": "Allow users to edit mail alias",
"global_settings_setting_portal_allow_edit_email_alias_help": "Allow users to edit their email address alias.",
"global_settings_setting_portal_allow_edit_email_forward": "Allow users to edit email forward",
"global_settings_setting_portal_allow_edit_email_forward_help": "Allow users to edit their main email forward.",
"global_settings_setting_portal_allow_edit_email_help": "Allow users to edit their main email address.",
"global_settings_setting_postfix_compatibility": "Postfix Compatibility",
"global_settings_setting_postfix_compatibility_help": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)",
"global_settings_setting_postfix_name": "Postfix (SMTP email server)",
Expand Down Expand Up @@ -603,6 +610,7 @@
"log_user_update": "Update info for user '{}'",
"mail_alias_remove_failed": "Could not remove e-mail alias '{mail}'",
"mail_alias_unauthorized": "You are not authorized to add aliases related to domain '{domain}'",
"mail_edit_operation_unauthorized": "You are not authorized to do this change for your account.",
"mail_already_exists": "Mail address '{mail}' already exists",
"mail_domain_unknown": "Invalid e-mail address for domain '{domain}'. Please, use a domain administrated by this server.",
"mail_forward_remove_failed": "Could not remove e-mail forwarding '{mail}'",
Expand Down
10 changes: 7 additions & 3 deletions share/actionsmap-portal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ portal:
pattern: &pattern_fullname
- !!str ^([^\W_]{1,30}[ ,.'-]{0,3})+$
- "pattern_fullname"
--mainemail:
help: Main email
extra:
pattern: &pattern_email
- !!str ^[\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$
- "pattern_email"
--mailforward:
help: Mailforward addresses to add
nargs: "*"
Expand All @@ -44,9 +50,7 @@ portal:
nargs: "*"
metavar: MAIL
extra:
pattern: &pattern_email
- !!str ^[\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$
- "pattern_email"
pattern: *pattern_email
--currentpassword:
help: Current password
nargs: "?"
Expand Down
12 changes: 12 additions & 0 deletions share/config_global.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ i18n = "global_settings_setting"
optional = true
default = ""

[security.portal]
[security.portal.portal_allow_edit_email]
type = "boolean"

[security.portal.portal_allow_edit_email_alias]
type = "boolean"
default = 1

[security.portal.portal_allow_edit_email_forward]
type = "boolean"
default = 1

[security.root_access]
[security.root_access.root_access_explain]
type = "alert"
Expand Down
52 changes: 49 additions & 3 deletions src/portal.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,22 @@ def _get_portal_settings(
"portal_title": "YunoHost",
"show_other_domains_apps": False,
"domain": domain,
"allow_edit_email": False,
"allow_edit_email_alias": False,
"allow_edit_email_forward": False,
}

portal_settings_path = Path(f"{PORTAL_SETTINGS_DIR}/{domain}.json")
global_portal_settings_path = Path(f"{PORTAL_SETTINGS_DIR}/global_settings.json")

if portal_settings_path.exists():
settings.update(read_json(str(portal_settings_path)))
# Portal may be public (no login required)
settings["public"] = bool(settings.pop("enable_public_apps_page", False))

if global_portal_settings_path.exists():
settings.update(read_json(str(global_portal_settings_path)))

# First clear apps since it may contains private apps
apps: dict[str, Any] = settings.pop("apps", {})
settings["apps"] = {}
Expand Down Expand Up @@ -173,6 +180,7 @@ def portal_me():

def portal_update(
fullname: Union[str, None] = None,
mainemail: Union[str, None] = None,
mailforward: Union[list[str], None] = None,
mailalias: Union[list[str], None] = None,
currentpassword: Union[str, None] = None,
Expand All @@ -185,6 +193,7 @@ def portal_update(
["givenName", "sn", "cn", "mail", "maildrop", "memberOf"]
)
new_attr_dict = {}
portal_settings = _get_portal_settings(domain, username)

if fullname is not None and fullname != current_user["cn"]:
fullname = fullname.strip()
Expand All @@ -198,14 +207,47 @@ def portal_update(
(firstname + " " + lastname).strip()
]

new_mails = current_user["mail"]

if mainemail is not None:
is_allowed_to_edit_main_email = portal_settings["allow_edit_email"]
if is_allowed_to_edit_main_email != 1:
raise YunohostValidationError("mail_edit_operation_unauthorized")

if mainemail not in new_mails:
local_part, domain = mainemail.split("@")
if local_part in ADMIN_ALIASES:
raise YunohostValidationError("mail_unavailable")

try:
_get_ldap_interface().validate_uniqueness({"mail": mainemail})
except YunohostError:
raise YunohostValidationError(
"mail_already_exists", mail=mainemail
)

if domain not in domains or not user_is_allowed_on_domain(username, domain):
raise YunohostValidationError("mail_alias_unauthorized", domain=domain)
new_mails[0] = mainemail
else:
# email already exist in the list we just move it on the first place
new_mails.remove(mainemail)
new_mails.insert(0, mainemail)

new_attr_dict["mail"] = new_mails

if mailalias is not None:
is_allowed_to_edit_mail_alias = portal_settings["allow_edit_email_alias"]
if is_allowed_to_edit_mail_alias != 1:
raise YunohostValidationError("mail_edit_operation_unauthorized")

mailalias = [mail.strip() for mail in mailalias if mail and mail.strip()]
# keep first current mail unaltered
mails = [current_user["mail"][0]]
mails = [new_mails[0]]

for index, mail in enumerate(mailalias):
if mail in current_user["mail"]:
if mail != current_user["mail"][0] and mail not in mails:
if mail in new_mails:
if mail != new_mails[0] and mail not in mails:
mails.append(mail)
continue # already in mails, skip validation

Expand All @@ -230,6 +272,10 @@ def portal_update(
new_attr_dict["mail"] = mails

if mailforward is not None:
is_allowed_to_edit_mail_forward = portal_settings["allow_edit_email_forward"]
if is_allowed_to_edit_mail_forward != 1:
raise YunohostValidationError("mail_edit_operation_unauthorized")

new_attr_dict["maildrop"] = [current_user["maildrop"][0]] + [
mail.strip()
for mail in mailforward
Expand Down
14 changes: 14 additions & 0 deletions src/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@
import os
import subprocess
from logging import getLogger
from pathlib import Path
from typing import TYPE_CHECKING, Any, Union

from moulinette import m18n
from moulinette.utils.filesystem import write_to_json
from yunohost.utils.error import YunohostError, YunohostValidationError
from yunohost.utils.configpanel import ConfigPanel, parse_filter_key
from yunohost.utils.form import BaseOption
from yunohost.regenconf import regen_conf
from yunohost.firewall import firewall_reload
from yunohost.log import is_unit_operation
from yunohost.portal import PORTAL_SETTINGS_DIR

if TYPE_CHECKING:
from typing import cast
Expand Down Expand Up @@ -246,6 +249,17 @@ def _apply(
{"sudoOption": ["!authenticate"] if passwordless_sudo else []},
)

global_portal_settings_path = Path(f"{PORTAL_SETTINGS_DIR}/global_settings.json")
portal_allow_edit_email = form.get("portal_allow_edit_email", self.get("security.portal.portal_allow_edit_email"))
portal_allow_edit_email_alias = form.get("portal_allow_edit_email_alias", self.get("security.portal.portal_allow_edit_email_alias"))
portal_allow_edit_email_forward = form.get("portal_allow_edit_email_forward", self.get("security.portal.portal_allow_edit_email_forward"))
write_to_json(
str(global_portal_settings_path), {"allow_edit_email": portal_allow_edit_email,
"allow_edit_email_alias": portal_allow_edit_email_alias,
"allow_edit_email_forward":portal_allow_edit_email_forward
}, sort_keys=True, indent=4
)

# First save settings except virtual + default ones
super()._apply(form, config, previous_settings, exclude=self.virtual_settings)
next_settings = {
Expand Down
Loading