From 45c4c75a50bef69199b1af3de1369cab4a44238c Mon Sep 17 00:00:00 2001 From: npodewitz Date: Sat, 29 May 2021 13:11:34 +0000 Subject: [PATCH 1/3] Added admin access through admin_groups --- README.md | 16 +++++++++ ldapauthenticator/ldapauthenticator.py | 46 +++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3849329..9671a90 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,22 @@ c.LDAPAuthenticator.allowed_groups = [ ] ``` +#### `LDAPAuthenticator.admin_groups` #### + +LDAP group whose members are granted admin access. This must be +set to either empty `[]` (the default) or to a list of full DNs +that have a `member` attribute that includes the current user +attempting to log in in order to grant this user admin rights. + +As an example, all users in the group `jupyterhub_admins` get +admin access, + +```python +c.LDAPAuthenticator.admin_groups = [ + "cn=jupyterhub_admins,ou=groups,dc=wikimedia,dc=org", +] +``` + #### `LDAPAuthenticator.valid_username_regex` #### All usernames will be checked against this before being sent diff --git a/ldapauthenticator/ldapauthenticator.py b/ldapauthenticator/ldapauthenticator.py index 4e3a809..f63dec3 100644 --- a/ldapauthenticator/ldapauthenticator.py +++ b/ldapauthenticator/ldapauthenticator.py @@ -86,6 +86,23 @@ def _server_port_default(self): """, ) + admin_groups = List( + config=True, + allow_none=True, + default_value=None, + help=""" + List of LDAP group DNs that users could be mebers of to be granted admin access. + + If a user is in any one of the listed groups, then that user is granted admin + access. Membership is tested by fetsching info about each group and looking for + the User's DN to be a value of one of `member` or `uniqueMember`, *or* if the + username being used is value of the `uid`. + + Setting this to an empty list or None does not have any additional effect unlike + allowed_groups. + """, + ) + # FIXME: Use something other than this? THIS IS LAME, akin to websites restricting things you # can use in usernames / passwords to protect from SQL injection! valid_username_regex = Unicode( @@ -457,10 +474,37 @@ def authenticate(self, handler, data): if not self.use_lookup_dn_username: username = data["username"] + is_admin = False + if self.admin_groups: + self.log.debug( + "Searching for admin users with username: %s and dn: %s", + username, + userdn, + ) + found = False + for group in self.admin_groups: + group_filter = ( + "(|" + "(member={userdn})" + "(uniqueMember={userdn})" + "(memberUid={uid})" + ")" + ) + group_filter = group_filter.format(userdn=userdn, uid=username) + group_attributes = ["member", "uniqueMember", "memberUid"] + if conn.search( + group, + search_scope=ldap3.BASE, + search_filter=group_filter, + attributes=group_attributes, + ): + is_admin = True + break + user_info = self.get_user_attributes(conn, userdn) if user_info: self.log.debug("username:%s attributes:%s", username, user_info) - return {"name": username, "auth_state": user_info} + return {"name": username, "auth_state": user_info, "admin": is_admin} return username From ab03c4908de3318548d8a183e79bdee76ca9045d Mon Sep 17 00:00:00 2001 From: npodewitz Date: Sat, 29 May 2021 17:35:18 +0000 Subject: [PATCH 2/3] Add admin_groups Added tests and fixed admin flag not returned if user_info is not set. --- ldapauthenticator/ldapauthenticator.py | 2 +- ldapauthenticator/tests/conftest.py | 4 ++++ ldapauthenticator/tests/test_ldapauthenticator.py | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ldapauthenticator/ldapauthenticator.py b/ldapauthenticator/ldapauthenticator.py index f63dec3..3693062 100644 --- a/ldapauthenticator/ldapauthenticator.py +++ b/ldapauthenticator/ldapauthenticator.py @@ -505,7 +505,7 @@ def authenticate(self, handler, data): if user_info: self.log.debug("username:%s attributes:%s", username, user_info) return {"name": username, "auth_state": user_info, "admin": is_admin} - return username + return {"name": username, "admin": is_admin} if __name__ == "__main__": diff --git a/ldapauthenticator/tests/conftest.py b/ldapauthenticator/tests/conftest.py index 545744f..24f32a7 100644 --- a/ldapauthenticator/tests/conftest.py +++ b/ldapauthenticator/tests/conftest.py @@ -34,4 +34,8 @@ def authenticator(): "cn=ship_crew,ou=people,dc=planetexpress,dc=com", ] + authenticator.admin_groups = [ + "cn=admin_staff,ou=people,dc=planetexpress,dc=com", + ] + return authenticator diff --git a/ldapauthenticator/tests/test_ldapauthenticator.py b/ldapauthenticator/tests/test_ldapauthenticator.py index 6471213..12505ce 100644 --- a/ldapauthenticator/tests/test_ldapauthenticator.py +++ b/ldapauthenticator/tests/test_ldapauthenticator.py @@ -8,6 +8,18 @@ async def test_ldap_auth_allowed(authenticator): ) assert authorized["name"] == "fry" + # allowed user with proper credentials not in admin_groups + authorized = await authenticator.get_authenticated_user( + None, {"username": "fry", "password": "fry"} + ) + assert not authorized.get("admin", True) + + # allowed user with proper credentials not in admin_groups + authorized = await authenticator.get_authenticated_user( + None, {"username": "hermes", "password": "hermes"} + ) + assert authorized.get("admin", False) + async def test_ldap_auth_disallowed(authenticator): # invalid username From 69a441ce1f5dfca64881417179f2e6d454b9a5c6 Mon Sep 17 00:00:00 2001 From: Vendetta01 Date: Sat, 12 Jun 2021 20:58:56 +0200 Subject: [PATCH 3/3] Fixed typo in comment --- ldapauthenticator/tests/test_ldapauthenticator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ldapauthenticator/tests/test_ldapauthenticator.py b/ldapauthenticator/tests/test_ldapauthenticator.py index 12505ce..4a6326f 100644 --- a/ldapauthenticator/tests/test_ldapauthenticator.py +++ b/ldapauthenticator/tests/test_ldapauthenticator.py @@ -14,7 +14,7 @@ async def test_ldap_auth_allowed(authenticator): ) assert not authorized.get("admin", True) - # allowed user with proper credentials not in admin_groups + # allowed user with proper credentials in admin_groups authorized = await authenticator.get_authenticated_user( None, {"username": "hermes", "password": "hermes"} )