app/permissions: Move permissions data out of LDAP

This commit is contained in:
Alexandre Aubin 2024-07-21 20:00:36 +02:00
parent 54fd311bec
commit c5580a0551
5 changed files with 153 additions and 91 deletions

View file

@ -68,28 +68,16 @@ groupPermission: cn=all_users,ou=groups,dc=yunohost,dc=org
cn: mail.main cn: mail.main
objectClass: posixGroup objectClass: posixGroup
objectClass: permissionYnh objectClass: permissionYnh
isProtected: TRUE
label: E-mail
gidNumber: 5001 gidNumber: 5001
showTile: FALSE
authHeader: FALSE
dn: cn=ssh.main,ou=permission,dc=yunohost,dc=org dn: cn=ssh.main,ou=permission,dc=yunohost,dc=org
cn: ssh.main cn: ssh.main
objectClass: posixGroup objectClass: posixGroup
objectClass: permissionYnh objectClass: permissionYnh
isProtected: TRUE
label: SSH
gidNumber: 5003 gidNumber: 5003
showTile: FALSE
authHeader: FALSE
dn: cn=sftp.main,ou=permission,dc=yunohost,dc=org dn: cn=sftp.main,ou=permission,dc=yunohost,dc=org
cn: sftp.main cn: sftp.main
objectClass: posixGroup objectClass: posixGroup
objectClass: permissionYnh objectClass: permissionYnh
isProtected: TRUE
label: SFTP
gidNumber: 5004 gidNumber: 5004
showTile: FALSE
authHeader: FALSE

View file

@ -14,24 +14,6 @@ olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.2 NAME 'groupPermission'
olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.3 NAME 'inheritPermission' olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.3 NAME 'inheritPermission'
DESC 'YunoHost permission for user on permission side' DESC 'YunoHost permission for user on permission side'
SUP distinguishedName ) SUP distinguishedName )
olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.4 NAME 'URL'
DESC 'YunoHost permission main URL'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.5 NAME 'additionalUrls'
DESC 'YunoHost permission additionnal URL'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.6 NAME 'authHeader'
DESC 'YunoHost application, enable authentication header'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.7 NAME 'label'
DESC 'YunoHost permission label, also used for the tile name in the SSO'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.8 NAME 'showTile'
DESC 'YunoHost application, show/hide the tile in the SSO for this permission'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.9 NAME 'isProtected'
DESC 'YunoHost application permission protection'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
# OBJECTCLASS # OBJECTCLASS
# For Applications # For Applications
olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh' olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh'
@ -41,8 +23,8 @@ olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh'
olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.2 NAME 'permissionYnh' olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.2 NAME 'permissionYnh'
DESC 'a YunoHost application' DESC 'a YunoHost application'
SUP top AUXILIARY SUP top AUXILIARY
MUST ( cn $ authHeader $ label $ showTile $ isProtected ) MUST ( cn )
MAY ( groupPermission $ inheritPermission $ URL $ additionalUrls ) ) MAY ( groupPermission $ inheritPermission ) )
# For User # For User
olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.3 NAME 'userPermissionYnh' olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.3 NAME 'userPermissionYnh'
DESC 'a YunoHost application' DESC 'a YunoHost application'

View file

@ -0,0 +1,74 @@
from logging import getLogger
from yunohost.tools import Migration
from yunohost.permission import permission_sync_to_user
from yunohost.app import app_setting
logger = getLogger("yunohost.migration")
###################################################
# Tools used also for restoration
###################################################
class MyMigration(Migration):
introduced_in_version = "12.0" # FIXME ?
dependencies = []
ldap_migration_started = False
@Migration.ldap_migration
def run(self, *args):
self.ldap_migration_started = True
permissions_per_app = self.read_legacy_permissions_per_app()
for app, permissions in permissions_per_app.items():
app_setting(app, "_permissions", permissions)
permission_sync_to_user()
def run_after_system_restore(self):
self.run()
def read_legacy_permissions_per_app(self):
from yunohost.utils.ldap import _get_ldap_interface
SYSTEM_PERMS = ["mail", "sftp", "ssh"]
ldap = _get_ldap_interface()
permissions_infos = ldap.search(
"ou=permission",
"(objectclass=permissionYnh)",
[
"cn",
"URL",
"additionalUrls",
"authHeader",
"label",
"showTile",
"isProtected",
],
)
permissions_per_app = {}
for infos in permissions_infos:
app, name = infos["cn"][0].split(".")
if app in SYSTEM_PERMS:
continue
if app not in permissions_per_app:
permissions_per_app[app] = {}
permissions_per_app[app][name] = {
"label": infos.get("label", [None])[0],
"show_tile": infos.get("showTile", [False])[0] == "TRUE",
"auth_header": infos.get("authHeader", [False])[0] == "TRUE",
"protected": infos.get("isProtected", [False])[0] == "TRUE",
"url": infos.get("URL", [None])[0],
"additional_urls": infos.get("additionalUrls", []),
}
return permissions_per_app

View file

@ -49,19 +49,13 @@ def user_permission_list(
from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
permissions_infos = ldap.search( ldap_permissions_infos = ldap.search(
"ou=permission", "ou=permission",
"(objectclass=permissionYnh)", "(objectclass=permissionYnh)",
[ [
"cn", "cn",
"groupPermission", "groupPermission",
"inheritPermission", "inheritPermission",
"URL",
"additionalUrls",
"authHeader",
"label",
"showTile",
"isProtected",
], ],
) )
@ -78,9 +72,9 @@ def user_permission_list(
} }
permissions = {} permissions = {}
for infos in permissions_infos: for infos in ldap_permissions_infos:
name = infos["cn"][0] name = infos["cn"][0]
app = name.split(".")[0] app, subperm = name.split(".")
if ignore_system_perms and app in SYSTEM_PERMS: if ignore_system_perms and app in SYSTEM_PERMS:
continue continue
@ -88,20 +82,19 @@ def user_permission_list(
continue continue
perm = {} perm = {}
perm["allowed"] = [ if full and app not in SYSTEM_PERMS:
_ldap_path_extract(p, "cn") for p in infos.get("groupPermission", []) # Default stuff
] perm = {
"url": None,
if full: "additional_urls": [],
perm["corresponding_users"] = [ "auth_header": True,
_ldap_path_extract(p, "uid") for p in infos.get("inheritPermission", []) "show_tile": None, # To be automagically set to True by default if an url is defined and show_tile not provided
] "protected": False,
perm["auth_header"] = infos.get("authHeader", [False])[0] == "TRUE" }
perm["label"] = infos.get("label", [None])[0] perm_settings = (app_setting(app, "_permissions") or {}).get(subperm, {})
perm["show_tile"] = infos.get("showTile", [False])[0] == "TRUE" perm.update(perm_settings)
perm["protected"] = infos.get("isProtected", [False])[0] == "TRUE" if perm["show_tile"] is None and perm["url"] is not None:
perm["url"] = infos.get("URL", [None])[0] perm["show_tile"] = True
perm["additional_urls"] = infos.get("additionalUrls", [])
if absolute_urls: if absolute_urls:
app_base_path = ( app_base_path = (
@ -113,6 +106,14 @@ def user_permission_list(
for url in perm["additional_urls"] for url in perm["additional_urls"]
] ]
perm["allowed"] = [
_ldap_path_extract(p, "cn") for p in infos.get("groupPermission", [])
]
if full:
perm["corresponding_users"] = [
_ldap_path_extract(p, "uid") for p in infos.get("inheritPermission", [])
]
permissions[name] = perm permissions[name] = perm
# Make sure labels for sub-permissions are the form " Applabel (Sublabel) " # Make sure labels for sub-permissions are the form " Applabel (Sublabel) "
@ -414,16 +415,6 @@ def permission_create(
"objectClass": ["top", "permissionYnh", "posixGroup"], "objectClass": ["top", "permissionYnh", "posixGroup"],
"cn": str(permission), "cn": str(permission),
"gidNumber": gid, "gidNumber": gid,
"authHeader": ["TRUE"],
"label": [
str(label) if label else (subperm if subperm != "main" else app.title())
],
"showTile": [
"FALSE"
], # Dummy value, it will be fixed when we call '_update_ldap_group_permission'
"isProtected": [
"FALSE"
], # Dummy value, it will be fixed when we call '_update_ldap_group_permission'
} }
if allowed is not None: if allowed is not None:
@ -446,6 +437,8 @@ def permission_create(
"permission_creation_failed", permission=permission, error=e "permission_creation_failed", permission=permission, error=e
) )
label = str(label) if label else (subperm if subperm != "main" else app.title())
try: try:
permission_url( permission_url(
permission, permission,
@ -463,6 +456,7 @@ def permission_create(
protected=protected, protected=protected,
sync_perm=sync_perm, sync_perm=sync_perm,
) )
except Exception: except Exception:
permission_delete(permission, force=True) permission_delete(permission, force=True)
raise raise
@ -496,15 +490,15 @@ def permission_url(
clear_urls -- (optional) Clean all urls (url and additional_urls) clear_urls -- (optional) Clean all urls (url and additional_urls)
""" """
from yunohost.app import app_setting from yunohost.app import app_setting
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
# By default, manipulate main permission # By default, manipulate main permission
if "." not in permission: if "." not in permission:
permission = permission + ".main" permission = permission + ".main"
app = permission.split(".")[0] app, sub_permission = permission.split(".")
if app in SYSTEM_PERMS:
logger.warning(f"Cannot change urls / auth_header for system perm {permission}")
if url or add_url: if url or add_url:
domain = app_setting(app, "domain") domain = app_setting(app, "domain")
@ -573,19 +567,20 @@ def permission_url(
# Actually commit the change # Actually commit the change
operation_logger.related_to.append(("app", permission.split(".")[0])) operation_logger.related_to.append(("app", app))
operation_logger.start() operation_logger.start()
try: try:
ldap.update( perm_settings = app_setting(app, "_permissions") or {}
f"cn={permission},ou=permission", if sub_permission not in perm_settings:
{ perm_settings[sub_permission] = {}
"URL": [url] if url is not None else [], perm_settings[sub_permission].update({
"additionalUrls": new_additional_urls, "url": url,
"authHeader": [str(auth_header).upper()], "additional_urls": new_additional_urls,
"showTile": [str(show_tile).upper()], "auth_header": auth_header,
}, "show_tile": show_tile,
) })
app_setting(app, "_permissions", perm_settings)
except Exception as e: except Exception as e:
raise YunohostError("permission_update_failed", permission=permission, error=e) raise YunohostError("permission_update_failed", permission=permission, error=e)
@ -714,31 +709,42 @@ def _update_ldap_group_permission(
- the 'allowed' list contains *existing* groups. - the 'allowed' list contains *existing* groups.
""" """
from yunohost.app import app_setting
from yunohost.hook import hook_callback from yunohost.hook import hook_callback
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
app, sub_permission = permission.split(".")
existing_permission = user_permission_info(permission) existing_permission = user_permission_info(permission)
update = {} update_ldap = {}
update_settings = {}
if allowed is not None: if allowed is not None:
allowed = [allowed] if not isinstance(allowed, list) else allowed allowed = [allowed] if not isinstance(allowed, list) else allowed
# Guarantee uniqueness of values in allowed, which would otherwise make ldap.update angry. # Guarantee uniqueness of values in allowed, which would otherwise make ldap.update angry.
allowed = set(allowed) allowed = set(allowed)
update["groupPermission"] = [ update_ldap["groupPermission"] = [
"cn=" + g + ",ou=groups,dc=yunohost,dc=org" for g in allowed "cn=" + g + ",ou=groups,dc=yunohost,dc=org" for g in allowed
] ]
if label is not None: if label is not None:
update["label"] = [str(label)] if app in SYSTEM_PERMS:
logger.warning(f"Can't change 'label' for system permission {permission}")
else:
update_settings["label"] = str(label)
if protected is not None: if protected is not None:
update["isProtected"] = [str(protected).upper()] if app in SYSTEM_PERMS:
logger.warning(f"Can't change 'protected' for system permission {permission}")
else:
update_settings["protected"] = protected
if show_tile is not None: if show_tile is not None:
if show_tile is True: if app in SYSTEM_PERMS:
logger.warning(f"Can't change 'show_tile' for system permission {permission}")
elif show_tile is True:
if not existing_permission["url"]: if not existing_permission["url"]:
logger.warning( logger.warning(
m18n.n( m18n.n(
@ -746,16 +752,22 @@ def _update_ldap_group_permission(
permission=permission, permission=permission,
) )
) )
show_tile = False update_settings["show_tile"] = False
elif existing_permission["url"].startswith("re:"): elif existing_permission["url"].startswith("re:"):
logger.warning( logger.warning(
m18n.n("show_tile_cant_be_enabled_for_regex", permission=permission) m18n.n("show_tile_cant_be_enabled_for_regex", permission=permission)
) )
show_tile = False update_settings["show_tile"] = False
update["showTile"] = [str(show_tile).upper()]
if app not in SYSTEM_PERMS:
perm_settings = app_setting(app, "_permissions") or {}
if sub_permission not in perm_settings:
perm_settings[sub_permission] = {}
perm_settings[sub_permission].update(update_settings)
app_setting(app, "_permissions", perm_settings)
try: try:
ldap.update(f"cn={permission},ou=permission", update) ldap.update(f"cn={permission},ou=permission", update_ldap)
except Exception as e: except Exception as e:
raise YunohostError("permission_update_failed", permission=permission, error=e) raise YunohostError("permission_update_failed", permission=permission, error=e)
@ -768,9 +780,6 @@ def _update_ldap_group_permission(
# Trigger app callbacks # Trigger app callbacks
app = permission.split(".")[0]
sub_permission = permission.split(".")[1]
old_corresponding_users = set(existing_permission["corresponding_users"]) old_corresponding_users = set(existing_permission["corresponding_users"])
new_corresponding_users = set(new_permission["corresponding_users"]) new_corresponding_users = set(new_permission["corresponding_users"])

View file

@ -288,6 +288,15 @@ def tools_regen_conf(
names=[], with_diff=False, force=False, dry_run=False, list_pending=False names=[], with_diff=False, force=False, dry_run=False, list_pending=False
): ):
# Make sure the permission infos are migrated before running the regenconf,
# which may otherwise fuck things up because the slapd conf will be regenerated etc
# We do this here because the regen-conf is called before the migration in debian/postinst
if os.system(f"grep --quiet 0031_rework_permission_infos {MIGRATIONS_STATE_PATH}") != 0:
try:
tools_migrations_run(["0031_rework_permission_infos"])
except Exception as e:
logger.error(e)
from yunohost.regenconf import regen_conf from yunohost.regenconf import regen_conf
return regen_conf(names, with_diff, force, dry_run, list_pending) return regen_conf(names, with_diff, force, dry_run, list_pending)