ACL filters are no more LDAP filters but user attribute mappings.

This commit is contained in:
Éloi Rivard 2023-04-14 18:46:42 +02:00
parent 3884a1d37c
commit edb64cbfe1
7 changed files with 84 additions and 31 deletions

View file

@ -13,6 +13,7 @@ Changed
- Moved OIDC related configuration entries in ``OIDC``
- Moved ``LDAP`` configuration entry to ``BACKENDS.LDAP``
- Bumped to htmx 1.9.0 :pr:`124`
- ACL filters are no more LDAP filters but user attribute mappings. :pr:`125`
Fixed
*****

View file

@ -91,18 +91,25 @@ GROUP_BASE = "ou=groups,dc=mydomain,dc=tld"
# A 'user' variable is available.
# GROUP_USER_FILTER = "member={user.dn}"
[ACL]
# You can define access controls that define what users can do on canaille
# An access control consists in a FILTER to match users, a list of PERMISSIONS
# matched users will be able to perform, and fields users will be able
# to READ and WRITE. Users matching several filters will cumulate permissions.
#
# A 'FILTER' parameter that is a LDAP filter used to determine if a user
# belongs to an access control. If absent, all the users will match this
# access control. If your LDAP server has the 'memberof' overlay, you can
# filter against group membership.
# 'FILTER' parameter can be:
# - absent, in which case all the users will match this access control
# - a mapping where keys are user attributes name and the values those user
# attribute values. All the values must be matched for the user to be part
# of the access control.
# - a list of those mappings. If a user values match at least one mapping,
# then the user will be part of the access control
#
# Here are some examples
# FILTER = 'uid=admin'
# FILTER = 'memberof=cn=admins,ou=groups,dc=mydomain,dc=tld'
# FILTER = {'user_name': 'admin'}
# FILTER =
# - {'groups': 'admin'}
# - {'groups': 'moderators'}
#
# The 'PERMISSIONS' parameter that is an list of items the users in the access
# control will be able to manage. 'PERMISSIONS' is optionnal. Values can be:
@ -142,7 +149,7 @@ WRITE = [
]
[ACL.ADMIN]
FILTER = "memberof=cn=moderators,ou=groups,dc=mydomain,dc=tld"
FILTER = {"groups": "admins"}
PERMISSIONS = [
"manage_users",
"manage_groups",

View file

@ -63,6 +63,27 @@ class User(LDAPObject):
return user
@classmethod
def acl_filter_to_ldap_filter(cls, filter_):
if isinstance(filter_, dict):
return (
"(&"
+ "".join(
f"({cls.attribute_table.get(key, key)}={value})"
for key, value in filter_.items()
)
+ ")"
)
if isinstance(filter_, list):
return (
"(|"
+ "".join(cls.acl_filter_to_ldap_filter(mapping) for mapping in filter_)
+ ")"
)
return filter_
def load_groups(self):
group_filter = (
current_app.config["BACKENDS"]["LDAP"]
@ -159,9 +180,9 @@ class User(LDAPObject):
conn = self.ldap_connection()
for access_group_name, details in current_app.config["ACL"].items():
if not details.get("FILTER") or (
self.id
and conn.search_s(self.id, ldap.SCOPE_SUBTREE, details["FILTER"])
filter_ = self.acl_filter_to_ldap_filter(details.get("FILTER"))
if not filter_ or (
self.id and conn.search_s(self.id, ldap.SCOPE_SUBTREE, filter_)
):
self.permissions |= set(details.get("PERMISSIONS", []))
self.read |= set(details.get("READ", []))

View file

@ -92,18 +92,25 @@ GROUP_BASE = "ou=groups,dc=mydomain,dc=tld"
# A 'user' variable is available.
# GROUP_USER_FILTER = "member={user.dn}"
[ACL]
# You can define access controls that define what users can do on canaille
# An access control consists in a FILTER to match users, a list of PERMISSIONS
# matched users will be able to perform, and fields users will be able
# to READ and WRITE. Users matching several filters will cumulate permissions.
#
# A 'FILTER' parameter that is a LDAP filter used to determine if a user
# belongs to an access control. If absent, all the users will match this
# access control. If your LDAP server has the 'memberof' overlay, you can
# filter against group membership.
# 'FILTER' parameter can be:
# - absent, in which case all the users will match this access control
# - a mapping where keys are user attributes name and the values those user
# attribute values. All the values must be matched for the user to be part
# of the access control.
# - a list of those mappings. If a user values match at least one mapping,
# then the user will be part of the access control
#
# Here are some examples
# FILTER = 'uid=admin'
# FILTER = 'memberof=cn=admins,ou=groups,dc=mydomain,dc=tld'
# FILTER = {'user_name': 'admin'}
# FILTER =
# - {'groups': 'admin'}
# - {'groups': 'moderators'}
#
# The 'PERMISSIONS' parameter that is an list of items the users in the access
# control will be able to manage. 'PERMISSIONS' is optionnal. Values can be:
@ -143,7 +150,7 @@ WRITE = [
]
[ACL.ADMIN]
FILTER = "memberof=cn=admins,ou=groups,dc=mydomain,dc=tld"
FILTER = {"groups": "admins"}
PERMISSIONS = [
"manage_users",
"manage_groups",
@ -154,7 +161,7 @@ PERMISSIONS = [
WRITE = ["groups"]
[ACL.HALF_ADMIN]
FILTER = "memberof=cn=moderators,ou=groups,dc=mydomain,dc=tld"
FILTER = {"groups": "moderators"}
PERMISSIONS = ["manage_users", "manage_groups", "delete_account"]
WRITE = ["groups"]

View file

@ -92,18 +92,25 @@ GROUP_BASE = "ou=groups,dc=mydomain,dc=tld"
# A 'user' variable is available.
# GROUP_USER_FILTER = "member={user.dn}"
[ACL]
# You can define access controls that define what users can do on canaille
# An access control consists in a FILTER to match users, a list of PERMISSIONS
# matched users will be able to perform, and fields users will be able
# to READ and WRITE. Users matching several filters will cumulate permissions.
#
# A 'FILTER' parameter that is a LDAP filter used to determine if a user
# belongs to an access control. If absent, all the users will match this
# access control. If your LDAP server has the 'memberof' overlay, you can
# filter against group membership.
# 'FILTER' parameter can be:
# - absent, in which case all the users will match this access control
# - a mapping where keys are user attributes name and the values those user
# attribute values. All the values must be matched for the user to be part
# of the access control.
# - a list of those mappings. If a user values match at least one mapping,
# then the user will be part of the access control
#
# Here are some examples
# FILTER = 'uid=admin'
# FILTER = 'memberof=cn=admins,ou=groups,dc=mydomain,dc=tld'
# FILTER = {'user_name': 'admin'}
# FILTER =
# - {'groups': 'admin'}
# - {'groups': 'moderators'}
#
# The 'PERMISSIONS' parameter that is an list of items the users in the access
# control will be able to manage. 'PERMISSIONS' is optionnal. Values can be:
@ -143,7 +150,7 @@ WRITE = [
]
[ACL.ADMIN]
FILTER = "memberof=cn=admins,ou=groups,dc=mydomain,dc=tld"
FILTER = {"groups": "admins"}
PERMISSIONS = [
"manage_users",
"manage_groups",
@ -154,7 +161,7 @@ PERMISSIONS = [
WRITE = ["groups"]
[ACL.HALF_ADMIN]
FILTER = "memberof=cn=moderators,ou=groups,dc=mydomain,dc=tld"
FILTER = {"groups": "moderators"}
PERMISSIONS = ["manage_users", "manage_groups", "delete_account"]
WRITE = ["groups"]

View file

@ -132,9 +132,19 @@ The 'READ' and 'WRITE' attributes are the LDAP attributes of the user
object that users will be able to read and/or write.
:FILTER:
*Optional.* A filter to test on the users to test if they belong to this ACL.
If absent, all the users will have the permissions in this ACL.
e.g. ``uid=admin`` or ``memberof=cn=admin,ou=groups,dc=mydomain,dc=tld``
*Optional.* It can be:
- absent, in which case all the users will have the permissions in this ACL.
- a mapping where keys are user attributes name and the values those user
attribute values. All the values must be matched for the user to be part
of the access control.
- a list of those mappings. If a user values match at least one mapping,
then the user will be part of the access control
Here are some examples:
- ``FILTER = {'user_name': 'admin'}``
- ``FILTER = [{'groups': 'admin'}, {'groups': 'moderators'}]``
:PERMISSIONS:
*Optional.* A list of items the users in the access control will be able to manage. Values can be:

View file

@ -115,7 +115,7 @@ def configuration(slapd_server, smtpd):
],
},
"ADMIN": {
"FILTER": "(|(uid=admin)(sn=admin))",
"FILTER": [{"user_name": "admin"}, {"family_name": "admin"}],
"PERMISSIONS": [
"manage_users",
"manage_oidc",
@ -128,7 +128,7 @@ def configuration(slapd_server, smtpd):
],
},
"MODERATOR": {
"FILTER": "(|(uid=moderator)(sn=moderator))",
"FILTER": [{"user_name": "moderator"}, {"family_name": "moderator"}],
"PERMISSIONS": ["manage_users", "manage_groups", "delete_account"],
"WRITE": [
"groups",