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 OIDC related configuration entries in ``OIDC``
- Moved ``LDAP`` configuration entry to ``BACKENDS.LDAP`` - Moved ``LDAP`` configuration entry to ``BACKENDS.LDAP``
- Bumped to htmx 1.9.0 :pr:`124` - Bumped to htmx 1.9.0 :pr:`124`
- ACL filters are no more LDAP filters but user attribute mappings. :pr:`125`
Fixed Fixed
***** *****

View file

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

View file

@ -63,6 +63,27 @@ class User(LDAPObject):
return user 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): def load_groups(self):
group_filter = ( group_filter = (
current_app.config["BACKENDS"]["LDAP"] current_app.config["BACKENDS"]["LDAP"]
@ -159,9 +180,9 @@ class User(LDAPObject):
conn = self.ldap_connection() conn = self.ldap_connection()
for access_group_name, details in current_app.config["ACL"].items(): for access_group_name, details in current_app.config["ACL"].items():
if not details.get("FILTER") or ( filter_ = self.acl_filter_to_ldap_filter(details.get("FILTER"))
self.id if not filter_ or (
and conn.search_s(self.id, ldap.SCOPE_SUBTREE, details["FILTER"]) self.id and conn.search_s(self.id, ldap.SCOPE_SUBTREE, filter_)
): ):
self.permissions |= set(details.get("PERMISSIONS", [])) self.permissions |= set(details.get("PERMISSIONS", []))
self.read |= set(details.get("READ", [])) 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. # A 'user' variable is available.
# GROUP_USER_FILTER = "member={user.dn}" # GROUP_USER_FILTER = "member={user.dn}"
[ACL]
# You can define access controls that define what users can do on canaille # 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 # 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 # matched users will be able to perform, and fields users will be able
# to READ and WRITE. Users matching several filters will cumulate permissions. # 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 # 'FILTER' parameter can be:
# belongs to an access control. If absent, all the users will match this # - absent, in which case all the users will match this access control
# access control. If your LDAP server has the 'memberof' overlay, you can # - a mapping where keys are user attributes name and the values those user
# filter against group membership. # 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 # Here are some examples
# FILTER = 'uid=admin' # FILTER = {'user_name': 'admin'}
# FILTER = 'memberof=cn=admins,ou=groups,dc=mydomain,dc=tld' # FILTER =
# - {'groups': 'admin'}
# - {'groups': 'moderators'}
# #
# The 'PERMISSIONS' parameter that is an list of items the users in the access # 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: # control will be able to manage. 'PERMISSIONS' is optionnal. Values can be:
@ -143,7 +150,7 @@ WRITE = [
] ]
[ACL.ADMIN] [ACL.ADMIN]
FILTER = "memberof=cn=admins,ou=groups,dc=mydomain,dc=tld" FILTER = {"groups": "admins"}
PERMISSIONS = [ PERMISSIONS = [
"manage_users", "manage_users",
"manage_groups", "manage_groups",
@ -154,7 +161,7 @@ PERMISSIONS = [
WRITE = ["groups"] WRITE = ["groups"]
[ACL.HALF_ADMIN] [ACL.HALF_ADMIN]
FILTER = "memberof=cn=moderators,ou=groups,dc=mydomain,dc=tld" FILTER = {"groups": "moderators"}
PERMISSIONS = ["manage_users", "manage_groups", "delete_account"] PERMISSIONS = ["manage_users", "manage_groups", "delete_account"]
WRITE = ["groups"] WRITE = ["groups"]

View file

@ -92,18 +92,25 @@ GROUP_BASE = "ou=groups,dc=mydomain,dc=tld"
# A 'user' variable is available. # A 'user' variable is available.
# GROUP_USER_FILTER = "member={user.dn}" # GROUP_USER_FILTER = "member={user.dn}"
[ACL]
# You can define access controls that define what users can do on canaille # 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 # 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 # matched users will be able to perform, and fields users will be able
# to READ and WRITE. Users matching several filters will cumulate permissions. # 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 # 'FILTER' parameter can be:
# belongs to an access control. If absent, all the users will match this # - absent, in which case all the users will match this access control
# access control. If your LDAP server has the 'memberof' overlay, you can # - a mapping where keys are user attributes name and the values those user
# filter against group membership. # 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 # Here are some examples
# FILTER = 'uid=admin' # FILTER = {'user_name': 'admin'}
# FILTER = 'memberof=cn=admins,ou=groups,dc=mydomain,dc=tld' # FILTER =
# - {'groups': 'admin'}
# - {'groups': 'moderators'}
# #
# The 'PERMISSIONS' parameter that is an list of items the users in the access # 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: # control will be able to manage. 'PERMISSIONS' is optionnal. Values can be:
@ -143,7 +150,7 @@ WRITE = [
] ]
[ACL.ADMIN] [ACL.ADMIN]
FILTER = "memberof=cn=admins,ou=groups,dc=mydomain,dc=tld" FILTER = {"groups": "admins"}
PERMISSIONS = [ PERMISSIONS = [
"manage_users", "manage_users",
"manage_groups", "manage_groups",
@ -154,7 +161,7 @@ PERMISSIONS = [
WRITE = ["groups"] WRITE = ["groups"]
[ACL.HALF_ADMIN] [ACL.HALF_ADMIN]
FILTER = "memberof=cn=moderators,ou=groups,dc=mydomain,dc=tld" FILTER = {"groups": "moderators"}
PERMISSIONS = ["manage_users", "manage_groups", "delete_account"] PERMISSIONS = ["manage_users", "manage_groups", "delete_account"]
WRITE = ["groups"] 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. object that users will be able to read and/or write.
:FILTER: :FILTER:
*Optional.* A filter to test on the users to test if they belong to this ACL. *Optional.* It can be:
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`` - 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: :PERMISSIONS:
*Optional.* A list of items the users in the access control will be able to manage. Values can be: *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": { "ADMIN": {
"FILTER": "(|(uid=admin)(sn=admin))", "FILTER": [{"user_name": "admin"}, {"family_name": "admin"}],
"PERMISSIONS": [ "PERMISSIONS": [
"manage_users", "manage_users",
"manage_oidc", "manage_oidc",
@ -128,7 +128,7 @@ def configuration(slapd_server, smtpd):
], ],
}, },
"MODERATOR": { "MODERATOR": {
"FILTER": "(|(uid=moderator)(sn=moderator))", "FILTER": [{"user_name": "moderator"}, {"family_name": "moderator"}],
"PERMISSIONS": ["manage_users", "manage_groups", "delete_account"], "PERMISSIONS": ["manage_users", "manage_groups", "delete_account"],
"WRITE": [ "WRITE": [
"groups", "groups",