Merge pull request #1406 from Tagadda/enh-domain-default-app

Manage default application with DomainConfigPanel
This commit is contained in:
Alexandre Aubin 2022-01-24 18:49:06 +01:00 committed by GitHub
commit 981c7b5649
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 127 additions and 31 deletions

View file

@ -310,6 +310,7 @@
"domain_config_auth_key": "Authentication key", "domain_config_auth_key": "Authentication key",
"domain_config_auth_secret": "Authentication secret", "domain_config_auth_secret": "Authentication secret",
"domain_config_auth_token": "Authentication token", "domain_config_auth_token": "Authentication token",
"domain_config_default_app": "Default app",
"domain_config_features_disclaimer": "So far, enabling/disabling mail or XMPP features only impact the recommended and automatic DNS configuration, not system configurations!", "domain_config_features_disclaimer": "So far, enabling/disabling mail or XMPP features only impact the recommended and automatic DNS configuration, not system configurations!",
"domain_config_mail_in": "Incoming emails", "domain_config_mail_in": "Incoming emails",
"domain_config_mail_out": "Outgoing emails", "domain_config_mail_out": "Outgoing emails",

View file

@ -896,6 +896,10 @@ app:
-d: -d:
full: --domain full: --domain
help: Specific domain to put app on (the app domain by default) help: Specific domain to put app on (the app domain by default)
-u:
full: --undo
help: Undo redirection
action: store_true
### app_ssowatconf() ### app_ssowatconf()
ssowatconf: ssowatconf:

View file

@ -11,6 +11,11 @@ i18n = "domain_config"
# #
[feature] [feature]
[feature.app]
[feature.app.default_app]
type = "app"
filters = ["is_webapp"]
default = "_none"
[feature.mail] [feature.mail]
#services = ['postfix', 'dovecot'] #services = ['postfix', 'dovecot']

View file

@ -117,6 +117,7 @@ def app_info(app, full=False):
Get info for a specific app Get info for a specific app
""" """
from yunohost.permission import user_permission_list from yunohost.permission import user_permission_list
from yunohost.domain import domain_config_get
_assert_is_installed(app) _assert_is_installed(app)
@ -153,6 +154,9 @@ def app_info(app, full=False):
ret["is_webapp"] = "domain" in settings and "path" in settings ret["is_webapp"] = "domain" in settings and "path" in settings
if ret["is_webapp"]:
ret["is_default"] = domain_config_get(settings["domain"], "feature.app.default_app") == app
ret["supports_change_url"] = os.path.exists( ret["supports_change_url"] = os.path.exists(
os.path.join(setting_path, "scripts", "change_url") os.path.join(setting_path, "scripts", "change_url")
) )
@ -989,6 +993,7 @@ def app_remove(operation_logger, app, purge=False):
permission_delete, permission_delete,
permission_sync_to_user, permission_sync_to_user,
) )
from yunohost.domain import domain_list, domain_config_get, domain_config_set
if not _is_installed(app): if not _is_installed(app):
raise YunohostValidationError( raise YunohostValidationError(
@ -1048,12 +1053,16 @@ def app_remove(operation_logger, app, purge=False):
hook_remove(app) hook_remove(app)
for domain in domain_list()["domains"]:
if (domain_config_get(domain, "feature.app.default_app") == app):
domain_config_set(domain, "feature.app.default_app", "_none")
permission_sync_to_user() permission_sync_to_user()
_assert_system_is_sane_for_app(manifest, "post") _assert_system_is_sane_for_app(manifest, "post")
@is_unit_operation() @is_unit_operation()
def app_makedefault(operation_logger, app, domain=None): def app_makedefault(operation_logger, app, domain=None, undo=False):
""" """
Redirect domain root to an app Redirect domain root to an app
@ -1062,11 +1071,10 @@ def app_makedefault(operation_logger, app, domain=None):
domain domain
""" """
from yunohost.domain import _assert_domain_exists from yunohost.domain import _assert_domain_exists, domain_config_set
app_settings = _get_app_settings(app) app_settings = _get_app_settings(app)
app_domain = app_settings["domain"] app_domain = app_settings["domain"]
app_path = app_settings["path"]
if domain is None: if domain is None:
domain = app_domain domain = app_domain
@ -1075,36 +1083,12 @@ def app_makedefault(operation_logger, app, domain=None):
operation_logger.related_to.append(("domain", domain)) operation_logger.related_to.append(("domain", domain))
if "/" in app_map(raw=True)[domain]:
raise YunohostValidationError(
"app_make_default_location_already_used",
app=app,
domain=app_domain,
other_app=app_map(raw=True)[domain]["/"]["id"],
)
operation_logger.start() operation_logger.start()
# TODO / FIXME : current trick is to add this to conf.json.persisten if undo:
# This is really not robust and should be improved domain_config_set(domain, 'feature.app.default_app', "_none")
# e.g. have a flag in /etc/yunohost/apps/$app/ to say that this is the
# default app or idk...
if not os.path.exists("/etc/ssowat/conf.json.persistent"):
ssowat_conf = {}
else: else:
ssowat_conf = read_json("/etc/ssowat/conf.json.persistent") domain_config_set(domain, 'feature.app.default_app', app)
if "redirected_urls" not in ssowat_conf:
ssowat_conf["redirected_urls"] = {}
ssowat_conf["redirected_urls"][domain + "/"] = app_domain + app_path
write_to_json(
"/etc/ssowat/conf.json.persistent", ssowat_conf, sort_keys=True, indent=4
)
chmod("/etc/ssowat/conf.json.persistent", 0o644)
logger.success(m18n.n("ssowat_conf_updated"))
def app_setting(app, key, value=None, delete=False): def app_setting(app, key, value=None, delete=False):
@ -1303,7 +1287,7 @@ def app_ssowatconf():
""" """
from yunohost.domain import domain_list, _get_maindomain from yunohost.domain import domain_list, _get_maindomain, domain_config_get
from yunohost.permission import user_permission_list from yunohost.permission import user_permission_list
main_domain = _get_maindomain() main_domain = _get_maindomain()
@ -1341,6 +1325,21 @@ def app_ssowatconf():
redirected_urls.update(app_settings.get("redirected_urls", {})) redirected_urls.update(app_settings.get("redirected_urls", {}))
redirected_regex.update(app_settings.get("redirected_regex", {})) redirected_regex.update(app_settings.get("redirected_regex", {}))
from .utils.legacy import translate_legacy_default_app_in_ssowant_conf_json_persistent
translate_legacy_default_app_in_ssowant_conf_json_persistent()
for domain in domains:
default_app = domain_config_get(domain, "feature.app.default_app")
if default_app != "_none" and _is_installed(default_app):
app_settings = _get_app_settings(default_app)
app_domain = app_settings["domain"]
app_path = app_settings["path"]
# Prevent infinite redirect loop...
if domain + "/" != app_domain + app_path:
redirected_urls[domain + "/"] = app_domain + app_path
# New permission system # New permission system
for perm_name, perm_info in all_permissions.items(): for perm_name, perm_info in all_permissions.items():

View file

@ -454,6 +454,21 @@ class DomainConfigPanel(ConfigPanel):
save_path_tpl = f"{DOMAIN_SETTINGS_DIR}/{{entity}}.yml" save_path_tpl = f"{DOMAIN_SETTINGS_DIR}/{{entity}}.yml"
save_mode = "diff" save_mode = "diff"
def _apply(self):
if ("default_app" in self.future_values and self.future_values["default_app"] != self.values["default_app"]):
from yunohost.app import app_ssowatconf, app_map
if "/" in app_map(raw=True)[self.entity]:
raise YunohostValidationError(
"app_make_default_location_already_used",
app=self.future_values["default_app"],
domain=self.entity,
other_app=app_map(raw=True)[self.entity]["/"]["id"],
)
super()._apply()
app_ssowatconf()
def _get_toml(self): def _get_toml(self):
from yunohost.dns import _get_registrar_config_section from yunohost.dns import _get_registrar_config_section

View file

@ -440,6 +440,7 @@ class ConfigPanel:
"step", "step",
"accept", "accept",
"redact", "redact",
"filters",
], ],
"defaults": {}, "defaults": {},
}, },
@ -705,6 +706,7 @@ class Question:
self.ask = question.get("ask", {"en": self.name}) self.ask = question.get("ask", {"en": self.name})
self.help = question.get("help") self.help = question.get("help")
self.redact = question.get("redact", False) self.redact = question.get("redact", False)
self.filters = question.get("filters", [])
# .current_value is the currently stored value # .current_value is the currently stored value
self.current_value = question.get("current_value") self.current_value = question.get("current_value")
# .value is the "proposed" value which we got from the user # .value is the "proposed" value which we got from the user
@ -1126,6 +1128,28 @@ class DomainQuestion(Question):
return value return value
class AppQuestion(Question):
argument_type = "app"
def __init__(
self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {}
):
from yunohost.app import app_list
super().__init__(question, context, hooks)
apps = app_list(full=True)["apps"]
for _filter in self.filters:
apps = [app for app in apps if _filter in app and app[_filter]]
def _app_display(app):
domain_path = f" ({app['domain_path']})" if 'domain_path' in app else ""
return app["label"] + domain_path
self.choices = {"_none": "---"}
self.choices.update({app['id']: _app_display(app) for app in apps})
class UserQuestion(Question): class UserQuestion(Question):
argument_type = "user" argument_type = "user"
@ -1319,6 +1343,7 @@ ARGUMENTS_TYPE_PARSERS = {
"alert": DisplayTextQuestion, "alert": DisplayTextQuestion,
"markdown": DisplayTextQuestion, "markdown": DisplayTextQuestion,
"file": FileQuestion, "file": FileQuestion,
"app": AppQuestion,
} }

View file

@ -7,6 +7,7 @@ from moulinette.utils.filesystem import (
read_file, read_file,
write_to_file, write_to_file,
write_to_yaml, write_to_yaml,
write_to_json,
read_yaml, read_yaml,
) )
@ -68,6 +69,52 @@ def legacy_permission_label(app, permission_type):
) )
def translate_legacy_default_app_in_ssowant_conf_json_persistent():
from yunohost.app import app_list
from yunohost.domain import domain_config_set
persistent_file_name = "/etc/ssowat/conf.json.persistent"
if not os.path.exists(persistent_file_name):
return
# Ugly hack because for some reason so many people have tabs in their conf.json.persistent ...
os.system(r"sed -i 's/\t/ /g' /etc/ssowat/conf.json.persistent")
# Ugly hack to try not to misarably fail migration
persistent = read_yaml(persistent_file_name)
if "redirected_urls" not in persistent:
return
redirected_urls = persistent["redirected_urls"]
if not any(from_url.count('/') == 1 and from_url.endswith('/') for from_url in redirected_urls):
return
apps = app_list()['apps']
if not any(app.get('domain_path') in redirected_urls.values() for app in apps):
return
for from_url, dest_url in redirected_urls.copy().items():
# Not a root domain, skip
if from_url.count('/') != 1 or not from_url.endswith('/'):
continue
for app in apps:
if app.get('domain_path') != dest_url:
continue
domain_config_set(from_url.strip('/'), "feature.app.default_app", app['id'])
del redirected_urls[from_url]
persistent["redirected_urls"] = redirected_urls
write_to_json(persistent_file_name, persistent, sort_keys=True, indent=4)
logger.warning(
"YunoHost automatically translated some legacy redirections in /etc/ssowat/conf.json.persistent to match the new default application using domain configuration"
)
LEGACY_PHP_VERSION_REPLACEMENTS = [ LEGACY_PHP_VERSION_REPLACEMENTS = [
("/etc/php5", "/etc/php/7.4"), ("/etc/php5", "/etc/php/7.4"),
("/etc/php/7.0", "/etc/php/7.4"), ("/etc/php/7.0", "/etc/php/7.4"),