From a72a62a3f4282e6be553054f5e7000e354de7117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Goran=20Meki=C4=87?= Date: Thu, 8 Jan 2026 03:07:12 +0100 Subject: [PATCH] Place groups under domains endpoint --- freenit/api/domain/ldap.py | 65 +++++++++++++++++++++++++++- freenit/api/role/__init__.py | 1 - freenit/api/role/ldap.py | 4 +- freenit/api/role/ldap_group.py | 77 ---------------------------------- freenit/api/role/sql.py | 4 +- freenit/models/ldap/domain.py | 8 +++- freenit/models/ldap/group.py | 5 ++- freenit/models/ldap/role.py | 5 +-- freenit/models/safe.py | 5 ++- freenit/models/sql/role.py | 1 + 10 files changed, 85 insertions(+), 90 deletions(-) delete mode 100644 freenit/api/role/ldap_group.py diff --git a/freenit/api/domain/ldap.py b/freenit/api/domain/ldap.py index 6f72a3a..00370f6 100644 --- a/freenit/api/domain/ldap.py +++ b/freenit/api/domain/ldap.py @@ -5,9 +5,10 @@ from freenit.config import getConfig from freenit.decorators import description from freenit.models.ldap.domain import Domain, DomainCreate +from freenit.models.ldap.group import Group, GroupCreate from freenit.models.pagination import Page from freenit.models.user import User -from freenit.permissions import domain_perms +from freenit.permissions import domain_perms, group_perms tags = ["domain"] config = getConfig() @@ -57,3 +58,65 @@ async def delete(name, _: User = Depends(domain_perms)) -> Domain: return domain except bonsai.errors.AuthenticationError: raise HTTPException(status_code=403, detail="Failed to login") + + +@route("/domains/{name}/groups", tags=tags) +class DomainGroupListAPI: + @staticmethod + @description("Get domain groups") + async def get( + name, + page: int = Header(default=1), + perpage: int = Header(default=10), + _: User = Depends(group_perms), + ) -> Page[Group]: + domain = await Domain.get(name) + data = await Group.get_all(domain.ou) + total = len(data) + data = Page(total=total, page=1, pages=1, perpage=total, data=data) + return data + + @staticmethod + async def post(name, data: GroupCreate, _: User = Depends(group_perms)) -> Group: + domain = await Domain.get(name) + if data.name == "": + raise HTTPException(status_code=409, detail="Name is mandatory") + group = Group.create(domain.ou, data.name) + try: + await group.save() + except bonsai.errors.AlreadyExists: + raise HTTPException(status_code=409, detail="Group already exists") + return group + + +@route("/domains/{name}/groups/{group}", tags=tags) +class DomainGroupDetailAPI: + @staticmethod + async def get(name, group, _: User = Depends(group_perms)) -> Group: + return await Group.get(group, name) + + @staticmethod + async def delete(name, group, _: User = Depends(group_perms)) -> Group: + domain = await Domain.get(name) + gr = await Group.get(group, domain.ou) + gr.destroy() + return gr + + +@route("/domains/{name}/groups/{group}/{uid}", tags=tags) +class GroupUserAPI: + @staticmethod + @description("Assign user to group") + async def post(name, group, uid, _: User = Depends(group_perms)) -> Group: + user = await User.get_by_uid(uid) + gr = await Group.get(group, name) + await gr.add(user) + return gr + + @staticmethod + @description("Remove user from group") + async def delete(name, group, uid, _: User = Depends(group_perms)) -> Group: + user = await User.get_by_uid(uid) + gr = await Group.get(group, name) + await gr.remove(user) + return gr diff --git a/freenit/api/role/__init__.py b/freenit/api/role/__init__.py index 190e1d6..3b7b46e 100644 --- a/freenit/api/role/__init__.py +++ b/freenit/api/role/__init__.py @@ -4,4 +4,3 @@ from .sql import RoleListAPI, RoleDetailAPI, RoleUserAPI elif Role.dbtype() == "ldap": from .ldap import RoleListAPI, RoleDetailAPI, RoleUserAPI - from .ldap_group import GroupListAPI, GroupDetailAPI, GroupUserAPI diff --git a/freenit/api/role/ldap.py b/freenit/api/role/ldap.py index 21c4db9..d75a41f 100644 --- a/freenit/api/role/ldap.py +++ b/freenit/api/role/ldap.py @@ -75,6 +75,8 @@ async def delete(role_name, id, _: User = Depends(role_perms)) -> RoleSafe: role = await Role.get(role_name) if len(role.users) == 1: if role.users[0] == user.dn: - raise HTTPException(status_code=409, detail="Can not remove last member") + raise HTTPException( + status_code=409, detail="Can not remove last member" + ) await role.remove(user) return role diff --git a/freenit/api/role/ldap_group.py b/freenit/api/role/ldap_group.py deleted file mode 100644 index 66297ed..0000000 --- a/freenit/api/role/ldap_group.py +++ /dev/null @@ -1,77 +0,0 @@ -import bonsai -from fastapi import Depends, Header, HTTPException - -from freenit.api.router import route -from freenit.config import getConfig -from freenit.decorators import description -from freenit.models.ldap.group import Group, GroupCreate -from freenit.models.pagination import Page -from freenit.models.user import User -from freenit.permissions import group_perms - -tags = ["group"] -config = getConfig() - - -@route("/groups/{domain}", tags=tags) -class GroupListAPI: - @staticmethod - @description("Get groups") - async def get( - domain: str, - page: int = Header(default=1), - perpage: int = Header(default=10), - ) -> Page[Group]: - data = await Group.get_all(domain) - total = len(data) - data = Page(total=total, page=1, pages=1, perpage=total, data=data) - return data - - @staticmethod - async def post( - domain: str, data: GroupCreate, _: User = Depends(group_perms) - ) -> Group: - if data.name == "": - raise HTTPException(status_code=409, detail="Name is mandatory") - group = Group.create(data.name, domain) - try: - await group.save() - except bonsai.errors.AlreadyExists: - raise HTTPException(status_code=409, detail="Group already exists") - return group - - -@route("/groups/{domain}/{name}", tags=tags) -class GroupDetailAPI: - @staticmethod - async def get(domain, name) -> Group: - group = await Group.get(name, domain) - return group - - @staticmethod - async def delete(domain, name) -> Group: - try: - group = await Group.get(name, domain) - await group.destroy() - return group - except bonsai.errors.AuthenticationError: - raise HTTPException(status_code=403, detail="Failed to login") - - -@route("/groups/{domain}/{name}/{uid}", tags=tags) -class GroupUserAPI: - @staticmethod - @description("Assign user to group") - async def post(domain, name, uid, _: User = Depends(group_perms)) -> Group: - user = await User.get_by_uid(uid) - group = await Group.get(name, domain) - await group.add(user) - return group - - @staticmethod - @description("Remove user from group") - async def delete(domain, name, uid, _: User = Depends(group_perms)) -> Group: - user = await User.get_by_uid(uid) - group = await Group.get(name, domain) - await group.remove(user) - return group diff --git a/freenit/api/role/sql.py b/freenit/api/role/sql.py index 3b7b2b3..a05e8a6 100644 --- a/freenit/api/role/sql.py +++ b/freenit/api/role/sql.py @@ -48,7 +48,9 @@ async def get(id, _: User = Depends(role_perms)) -> RoleSafe: return role @staticmethod - async def patch(id, role_data: RoleOptional, _: User = Depends(role_perms)) -> RoleSafe: + async def patch( + id, role_data: RoleOptional, _: User = Depends(role_perms) + ) -> RoleSafe: if Role.dbtype() == "sql": try: role = await Role.objects.get(pk=id) diff --git a/freenit/models/ldap/domain.py b/freenit/models/ldap/domain.py index 4da93a6..5946441 100644 --- a/freenit/models/ldap/domain.py +++ b/freenit/models/ldap/domain.py @@ -31,7 +31,9 @@ async def get(cls, fqdn): try: async with client.connect(is_async=True) as conn: dn = f"{config.ldap.domainDN},{config.ldap.userBase}" - res = await conn.search(dn.format(fqdn), LDAPSearchScope.SUB, f"(|{classes})") + res = await conn.search( + dn.format(fqdn), LDAPSearchScope.SUB, f"(|{classes})" + ) except errors.AuthenticationError: raise HTTPException(status_code=403, detail="Failed to login") if len(res) < 1: @@ -49,7 +51,9 @@ async def get_rdomain(cls, fqdn): try: async with client.connect(is_async=True) as conn: dn = f"{config.ldap.domainDN},{config.ldap.roleBase}" - res = await conn.search(dn.format(fqdn), LDAPSearchScope.SUB, f"(|{classes})") + res = await conn.search( + dn.format(fqdn), LDAPSearchScope.SUB, f"(|{classes})" + ) except errors.AuthenticationError: raise HTTPException(status_code=403, detail="Failed to login") if len(res) < 1: diff --git a/freenit/models/ldap/group.py b/freenit/models/ldap/group.py index 7c91f40..dc46b6c 100644 --- a/freenit/models/ldap/group.py +++ b/freenit/models/ldap/group.py @@ -23,7 +23,7 @@ def from_entry(cls, entry): @classmethod def create(cls, name, domain): - group = Group(dn=config.ldap.groupDn.format(name, domain), cn=name, users=[]) + group = cls(dn=config.ldap.groupDn.format(name, domain), cn=name, users=[]) return group @classmethod @@ -50,7 +50,8 @@ async def get_all(cls, domain): client = get_client() try: async with client.connect(is_async=True) as conn: - dn = config.ldap.roleBase.format(domain) + dom = config.ldap.domainDN.format(domain) + dn = f"{dom},{config.ldap.roleBase}" res = await conn.search(dn, LDAPSearchScope.SUB, f"(|{classes})") data = [] for gdata in res: diff --git a/freenit/models/ldap/role.py b/freenit/models/ldap/role.py index ab6c17b..cc8bb28 100644 --- a/freenit/models/ldap/role.py +++ b/freenit/models/ldap/role.py @@ -20,17 +20,16 @@ def from_entry(cls, entry): users=entry[config.ldap.roleMemberAttr], ) - @classmethod def create(cls, name): - dn=config.ldap.roleDN.format(name) + dn = config.ldap.roleDN.format(name) return Role(dn=dn, cn=name, users=[]) @classmethod async def get(cls, name): classes = class2filter(config.ldap.roleClasses) client = get_client() - dn=config.ldap.roleDN.format(name) + dn = config.ldap.roleDN.format(name) try: async with client.connect(is_async=True) as conn: res = await conn.search(dn, LDAPSearchScope.SUB, f"(|{classes})") diff --git a/freenit/models/safe.py b/freenit/models/safe.py index 8e1143c..a85722c 100644 --- a/freenit/models/safe.py +++ b/freenit/models/safe.py @@ -5,10 +5,10 @@ auth = config.get_model("user") -if auth.User.dbtype() == 'sql': +if auth.User.dbtype() == "sql": UserBase = auth.User.get_pydantic(exclude={"password"}) RoleBase = config.get_model("role").BaseRole -elif auth.User.dbtype() == 'ldap': +elif auth.User.dbtype() == "ldap": UserBase = auth.UserSafe RoleBase = config.get_model("role").Role @@ -16,5 +16,6 @@ class UserSafe(UserBase): pass + class RoleSafe(RoleBase): users: List[str] diff --git a/freenit/models/sql/role.py b/freenit/models/sql/role.py index be51207..118bbda 100644 --- a/freenit/models/sql/role.py +++ b/freenit/models/sql/role.py @@ -1,5 +1,6 @@ from .base import BaseRole, ormar_config, make_optional + class Role(BaseRole): ormar_config = ormar_config.copy()