Skip to content

Commit 78d4216

Browse files
committed
⚙ Group settings propagate
1 parent 54de5ea commit 78d4216

File tree

6 files changed

+137
-17
lines changed

6 files changed

+137
-17
lines changed

docs/source/installation/migration.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ In case you would still want to **also** migrate all users now, you can do this
3838
>>> for user in LDAPUsers.objects.all():
3939
>>> user.sync()
4040

41+
To 1.4.0
42+
--------
43+
44+
- (optional) Remove duplicated groups between ``SUPERUSER_GROUPS``, ``STAFF_GROUPS`` and ``ACTIVE_GROUPS`` ldap settings, or set ``PROPAGATE_GROUPS`` to ``False``.
45+
4146
To 1.3.0
4247
--------
4348

docs/source/reference/change_log.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
Change Log
33
=============
44

5+
1.4.0
6+
-----
7+
8+
Release date: coming soon
9+
10+
- **ADDED**: LDAPUserManager for manually creating users from LDAP.
11+
- **IMPROVED**: LDAP Settings for Group Membership check propagate to one another.
12+
513
1.3.2
614
-----
715

docs/source/reference/ldap_settings.rst

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ If the user is member in **one** of the listed LDAP groups, the ``is_superuser``
351351
The group membership is checked by comparing the **groups listed in this setting** to the **LDAP Group Attributes** listed in ``GROUP_ATTRS`` setting.
352352

353353
STAFF_GROUPS
354-
~~~~~~~~~~~~~~~~
354+
~~~~~~~~~~~~
355355

356356
| Type ``tuple`` or ``str``; Default to ``Administrators``; Not Required.
357357
| LDAP Groups to check membership for setting Django User's "is_staff" flag.
@@ -365,7 +365,7 @@ If the user is member in **one** of the listed LDAP groups, the ``is_staff`` fla
365365
The group membership is checked by comparing the **groups listed in this setting** to the **LDAP Group Attributes** listed in ``GROUP_ATTRS`` setting.
366366

367367
ACTIVE_GROUPS
368-
~~~~~~~~~~~~~~~~
368+
~~~~~~~~~~~~~
369369

370370
| Type ``tuple`` or ``str``; Default to ``None``; Not Required.
371371
| LDAP Groups to check membership for setting Django User's "is_active" flag.
@@ -379,6 +379,16 @@ If the user is member in **one** of the listed LDAP groups, the ``is_active`` fl
379379
The group membership is checked by comparing the **groups listed in this setting** to the **LDAP Group Attributes** listed in ``GROUP_ATTRS`` setting.
380380

381381

382+
PROPAGATE_GROUPS
383+
~~~~~~~~~~~~~~~~
384+
385+
| Type ``bool``; Default to ``True``; Not Required.
386+
| Propagate groups in order Superusers > Staff > Active.
387+
388+
When set to ``True``, all groups configured in ``SUPERUSER_GROUPS`` will be added to ``STAFF_GROUPS``
389+
and ``ACTIVE_GROUPS``, and groups configured in ``STAFF_GROUPS`` with be added to ``ACTIVE_GROUPS``.
390+
391+
382392
GROUP_MAP
383393
~~~~~~~~~
384394

@@ -397,3 +407,21 @@ Groups that are **not listed** in this setting will **not be affected** by this.
397407

398408
.. warning::
399409
When a group that is configured in this setting is missing, it will be **created automatically**.
410+
411+
FLAG_MAP
412+
~~~~~~~~
413+
414+
| Type ``dict``; Default is ``{}``; Not Required.
415+
| Map User object boolean fields to one or more LDAP Groups membership check.
416+
417+
Used to synchronize boolean fields from the User object as a group membership check.
418+
If the user is member in **one** of the listed LDAP groups, the respective boolean field will be set to ``True``,
419+
otherwise it is set to ``False``.
420+
421+
| Configuring this setting to ``None`` will not modify the field.
422+
| Configuring this setting to a **string** is equal to a **single length tuple**.
423+
424+
The group membership is checked by comparing the **groups listed in this setting** to the **LDAP Group Attributes** listed in ``GROUP_ATTRS`` setting.
425+
426+
User's ``is_superuser``, ``is_staff`` and ``is_active`` are added automatically from settings ``SUPERUSER_GROUPS``,
427+
``STAFF_GROUPS`` and ``ACTIVE_GROUPS`` respectively.

testproj/demo/tests.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,52 @@
1-
from django.test import TestCase
1+
from django.test import TestCase, override_settings
22

3-
# Create your tests here.
3+
from windows_auth.models import LDAPUser
4+
from windows_auth.settings import LDAPSettings
5+
6+
7+
class ModelTestCase(TestCase):
8+
9+
def test_create_user(self):
10+
user = LDAPUser.objects.create_user("EXAMPLE\\Administrator")
11+
self.assertEqual(user.ldap.domain, "EXAMPLE")
12+
13+
14+
class SettingsTestCase(TestCase):
15+
16+
def test_flag_settings(self):
17+
settings = LDAPSettings(
18+
SERVER="example.local",
19+
SEARCH_BASE="DC=example,DC=local",
20+
USERNAME="EXAMPLE\\django_sync",
21+
PASSWORD="Aa123456!",
22+
SUPERUSER_GROUPS=None,
23+
STAFF_GROUPS=["List"],
24+
ACTIVE_GROUPS=["Explicit"],
25+
FLAG_MAP={
26+
"extra": "Hello world!",
27+
},
28+
)
29+
# check propagation
30+
self.assertEqual(settings.get_flag_map(), {
31+
"is_superuser": [],
32+
"is_staff": ["List"],
33+
"is_active": ["Explicit", "List"],
34+
"extra": ["Hello world!"],
35+
})
36+
# check without propagation
37+
settings.PROPAGATE_GROUPS = False
38+
self.assertEqual(settings.get_flag_map(), {
39+
"is_superuser": [],
40+
"is_staff": ["List"],
41+
"is_active": ["Explicit"],
42+
"extra": ["Hello world!"],
43+
})
44+
# check unique
45+
settings.PROPAGATE_GROUPS = True
46+
settings.ACTIVE_GROUPS = ["Explicit", "List"]
47+
self.assertEqual(settings.get_flag_map(), {
48+
"is_superuser": [],
49+
"is_staff": ["List"],
50+
"is_active": ["Explicit", "List"],
51+
"extra": ["Hello world!"],
52+
})

windows_auth/models.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -164,17 +164,9 @@ def sync(self) -> None:
164164
}
165165

166166
# check user flags
167-
flags = []
168-
if manager.settings.SUPERUSER_GROUPS:
169-
flags.append(("is_superuser", manager.settings.SUPERUSER_GROUPS, False))
170-
if manager.settings.STAFF_GROUPS:
171-
flags.append(("is_staff", manager.settings.STAFF_GROUPS, False))
172-
if manager.settings.ACTIVE_GROUPS:
173-
flags.append(("is_active", manager.settings.ACTIVE_GROUPS, True))
174-
175-
# update user flags
176-
for flag, groups, default in flags:
177-
updated_fields[flag] = _match_groups(group_reader, groups, manager.settings.GROUP_ATTRS, default=default)
167+
for flag, groups in manager.settings.get_flag_map().items():
168+
if groups:
169+
updated_fields[flag] = _match_groups(group_reader, groups, manager.settings.GROUP_ATTRS)
178170

179171
# check group membership
180172
group_membership: Dict[Group, bool] = {}

windows_auth/settings.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
from dataclasses import dataclass, field, asdict, MISSING, fields
2-
from typing import Dict, Any, Optional, Iterable, Union, Tuple
2+
from typing import Dict, Any, Optional, Iterable, Union, Tuple, List
33

44
from django.core.exceptions import ImproperlyConfigured
55

66
# domain name to use as a fallback setting for domain missing from WAUTH_DOMAINS
77
DEFAULT_DOMAIN_SETTING = "__default__"
88

99

10+
def _get_group_list(value) -> List[str]:
11+
if value is None:
12+
return []
13+
elif isinstance(value, str):
14+
return [value]
15+
else:
16+
return value
17+
18+
1019
@dataclass()
1120
class LDAPSettings:
1221
# connection settings
@@ -39,7 +48,9 @@ class LDAPSettings:
3948
SUPERUSER_GROUPS: Optional[Union[str, Iterable[str]]] = "Domain Admins"
4049
STAFF_GROUPS: Optional[Union[str, Iterable[str]]] = "Administrators"
4150
ACTIVE_GROUPS: Optional[Union[str, Iterable[str]]] = None
42-
GROUP_MAP: Dict[str, str] = field(default_factory=dict)
51+
PROPAGATE_GROUPS: bool = True
52+
GROUP_MAP: Dict[str, Union[str, Iterable[str]]] = field(default_factory=dict)
53+
FLAG_MAP: Dict[str, Union[str, Iterable[str]]] = field(default_factory=dict)
4354

4455
@classmethod
4556
def for_domain(cls, domain: str):
@@ -72,3 +83,30 @@ def for_domain(cls, domain: str):
7283
for setting, value in merged_settings.items()
7384
if setting in cls_fields
7485
})
86+
87+
def get_superuser_groups(self):
88+
return _get_group_list(self.SUPERUSER_GROUPS)
89+
90+
def get_staff_groups(self):
91+
if self.PROPAGATE_GROUPS:
92+
return list({*_get_group_list(self.STAFF_GROUPS), *self.get_superuser_groups()})
93+
else:
94+
return _get_group_list(self.STAFF_GROUPS)
95+
96+
def get_active_groups(self):
97+
if self.PROPAGATE_GROUPS:
98+
return list({*_get_group_list(self.ACTIVE_GROUPS), *self.get_staff_groups()})
99+
else:
100+
return _get_group_list(self.ACTIVE_GROUPS)
101+
102+
def get_flag_map(self):
103+
return {
104+
"is_superuser": self.get_superuser_groups(),
105+
"is_staff": self.get_staff_groups(),
106+
"is_active": self.get_active_groups(),
107+
**{
108+
k: _get_group_list(v)
109+
for k, v in self.FLAG_MAP.items()
110+
}
111+
}
112+

0 commit comments

Comments
 (0)