mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge pull request #1822 from selfhoster1312/portal-without-apps
Allow users to access their own domain portal without app permission
This commit is contained in:
commit
2d8cd9f88a
1 changed files with 50 additions and 17 deletions
|
@ -41,10 +41,15 @@ SESSION_VALIDITY = 3 * 24 * 3600 # 3 days
|
||||||
URI = "ldap://localhost:389"
|
URI = "ldap://localhost:389"
|
||||||
USERDN = "uid={username},ou=users,dc=yunohost,dc=org"
|
USERDN = "uid={username},ou=users,dc=yunohost,dc=org"
|
||||||
|
|
||||||
|
# Cache on-disk settings to RAM for faster access
|
||||||
DOMAIN_USER_ACL_DICT: dict[str, dict] = {}
|
DOMAIN_USER_ACL_DICT: dict[str, dict] = {}
|
||||||
PORTAL_SETTINGS_DIR = "/etc/yunohost/portal"
|
PORTAL_SETTINGS_DIR = "/etc/yunohost/portal"
|
||||||
|
|
||||||
|
# Should a user have *minimal* access to a domain?
|
||||||
|
# - if the user has permission for an application with a URI on the domain, yes
|
||||||
|
# - if the user is an admin, yes
|
||||||
|
# - if the user has an email on the domain, yes
|
||||||
|
# - otherwise, no
|
||||||
def user_is_allowed_on_domain(user: str, domain: str) -> bool:
|
def user_is_allowed_on_domain(user: str, domain: str) -> bool:
|
||||||
|
|
||||||
assert "/" not in domain
|
assert "/" not in domain
|
||||||
|
@ -54,12 +59,14 @@ def user_is_allowed_on_domain(user: str, domain: str) -> bool:
|
||||||
if not portal_settings_path.exists():
|
if not portal_settings_path.exists():
|
||||||
if "." not in domain:
|
if "." not in domain:
|
||||||
return False
|
return False
|
||||||
else:
|
|
||||||
parent_domain = domain.split(".", 1)[-1]
|
parent_domain = domain.split(".", 1)[-1]
|
||||||
return user_is_allowed_on_domain(user, parent_domain)
|
return user_is_allowed_on_domain(user, parent_domain)
|
||||||
|
|
||||||
|
# Check that the domain permissions haven't changed on-disk since we read them
|
||||||
|
# by comparing file mtime. If we haven't read the file yet, read it for the first time.
|
||||||
|
# We compare mtime by equality not superiority because maybe the system clock has changed.
|
||||||
mtime = portal_settings_path.stat().st_mtime
|
mtime = portal_settings_path.stat().st_mtime
|
||||||
if domain not in DOMAIN_USER_ACL_DICT or DOMAIN_USER_ACL_DICT[domain]["mtime"] < time.time():
|
if domain not in DOMAIN_USER_ACL_DICT or DOMAIN_USER_ACL_DICT[domain]["mtime"] != mtime:
|
||||||
users: set[str] = set()
|
users: set[str] = set()
|
||||||
for infos in read_json(str(portal_settings_path))["apps"].values():
|
for infos in read_json(str(portal_settings_path))["apps"].values():
|
||||||
users = users.union(infos["users"])
|
users = users.union(infos["users"])
|
||||||
|
@ -68,9 +75,9 @@ def user_is_allowed_on_domain(user: str, domain: str) -> bool:
|
||||||
DOMAIN_USER_ACL_DICT[domain]["users"] = users
|
DOMAIN_USER_ACL_DICT[domain]["users"] = users
|
||||||
|
|
||||||
if user in DOMAIN_USER_ACL_DICT[domain]["users"]:
|
if user in DOMAIN_USER_ACL_DICT[domain]["users"]:
|
||||||
|
# A user with explicit permission to an application is certainly welcome
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
# Admins can access everything
|
|
||||||
ADMIN_GROUP = "cn=admins,ou=groups"
|
ADMIN_GROUP = "cn=admins,ou=groups"
|
||||||
try:
|
try:
|
||||||
admins = (
|
admins = (
|
||||||
|
@ -81,9 +88,35 @@ def user_is_allowed_on_domain(user: str, domain: str) -> bool:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to list admin users: {e}")
|
logger.error(f"Failed to list admin users: {e}")
|
||||||
return False
|
return False
|
||||||
|
if user in admins:
|
||||||
|
# Admins can access everything
|
||||||
|
return True
|
||||||
|
|
||||||
return user in admins
|
try:
|
||||||
|
user_result = _get_ldap_interface().search("ou=users", f"uid={user}", [ "mail" ])
|
||||||
|
if len(user_result) != 1:
|
||||||
|
logger.error(f"User not found or many users found for {user}. How is this possible after so much validation?")
|
||||||
|
return False
|
||||||
|
|
||||||
|
user_mail = user_result[0]["mail"]
|
||||||
|
if len(user_mail) != 1:
|
||||||
|
logger.error(f"User {user} found, but has the wrong number of email addresses: {user_mail}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
user_mail = user_mail[0]
|
||||||
|
if not "@" in user_mail:
|
||||||
|
logger.error(f"Invalid email address for {user}: {user_mail}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if user_mail.split("@")[1] == domain:
|
||||||
|
# A user from that domain is welcome
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Users from other domains don't belong here
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to get email info for {user}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
# We want to save the password in the cookie, but we should do so in an encrypted fashion
|
# We want to save the password in the cookie, but we should do so in an encrypted fashion
|
||||||
# This is needed because the SSO later needs to possibly inject the Basic Auth header
|
# This is needed because the SSO later needs to possibly inject the Basic Auth header
|
||||||
|
|
Loading…
Add table
Reference in a new issue