Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion freenit/api/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ async def register(credentials: LoginInput, host=Header(default="")):
msg["From"] = mail.from_addr
msg["Subject"] = mail.register_subject
sendmail(user.email, msg)
return {"status": True}
return user


@api.post("/auth/verify", response_model=UserSafe, tags=["auth"])
Expand Down
1 change: 1 addition & 0 deletions freenit/api/role/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from .sql import RoleListAPI, RoleDetailAPI, RoleUserAPI
elif Role.dbtype() == "ldap":
from .ldap import RoleListAPI, RoleDetailAPI, RoleUserAPI
from .ldap_group import GroupListAPI, GroupDetailAPI, GroupUserAPI
74 changes: 34 additions & 40 deletions freenit/api/role/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
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.base import get_client
from freenit.models.ldap.role import RoleCreate
from freenit.models.pagination import Page
from freenit.models.role import Role
from freenit.models.safe import RoleSafe, UserSafe
from freenit.models.safe import RoleSafe
from freenit.models.user import User
from freenit.permissions import role_perms

tags = ["role"]
config = getConfig()


@route("/roles", tags=tags)
Expand All @@ -19,68 +21,60 @@ class RoleListAPI:
@description("Get roles")
async def get(
page: int = Header(default=1),
_: int = Header(default=10),
user: User = Depends(role_perms),
perpage: int = Header(default=10),
_: User = Depends(role_perms),
) -> Page[RoleSafe]:
data = await Role.get_all()
total = len(data)
page = Page(total=total, page=1, pages=1, perpage=total, data=data)
return page
perpage = len(data)
data = Page(total=perpage, page=page, pages=1, perpage=perpage, data=data)
return data

@staticmethod
async def post(role: Role, user: User = Depends(role_perms)) -> RoleSafe:
async def post(data: RoleCreate, user: User = Depends(role_perms)) -> RoleSafe:
if data.name == "":
raise HTTPException(status_code=409, detail="Name is mandatory")
role = Role.create(data.name)
try:
await role.create(user)
await role.save(user)
except bonsai.errors.AlreadyExists:
raise HTTPException(status_code=409, detail="Role already exists")
return role


@route("/roles/{id}", tags=tags)
@route("/roles/{name}", tags=tags)
class RoleDetailAPI:
@staticmethod
async def get(id, _: User = Depends(role_perms)) -> RoleSafe:
role = await Role.get(id)
async def get(name, _: User = Depends(role_perms)) -> RoleSafe:
role = await Role.get(name)
return role

@staticmethod
async def delete(id, _: User = Depends(role_perms)) -> RoleSafe:
client = get_client()
async def delete(name, _: User = Depends(role_perms)) -> RoleSafe:
try:
async with client.connect(is_async=True) as conn:
res = await conn.search(
id, bonsai.LDAPSearchScope.SUB, "objectClass=groupOfUniqueNames"
)
if len(res) < 1:
raise HTTPException(status_code=404, detail="No such role")
if len(res) > 1:
raise HTTPException(status_code=409, detail="Multiple role found")
existing = res[0]
role = Role(
cn=existing["cn"][0],
dn=str(existing["dn"]),
users=existing["uniqueMember"],
)
await existing.delete()
return role
role = await Role.get(name)
await role.destroy()
return role
except bonsai.errors.AuthenticationError:
raise HTTPException(status_code=403, detail="Failed to login")


@route("/roles/{role_id}/{user_id}", tags=tags)
@route("/roles/{role_name}/{id}", tags=tags)
class RoleUserAPI:
@staticmethod
@description("Assign user to role")
async def post(role_id, user_id, _: User = Depends(role_perms)) -> UserSafe:
user = await User.get(user_id)
role = await Role.get(role_id)
async def post(role_name, id, _: User = Depends(role_perms)) -> RoleSafe:
user = await User.get_by_uid(id)
role = await Role.get(role_name)
await role.add(user)
return user
return role

@staticmethod
@description("Deassign user to role")
async def delete(role_id, user_id, _: User = Depends(role_perms)) -> UserSafe:
user = await User.get(user_id)
role = await Role.get(role_id)
@description("Remove user from role")
async def delete(role_name, id, _: User = Depends(role_perms)) -> RoleSafe:
user = await User.get_by_uid(id)
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")
await role.remove(user)
return user
return role
77 changes: 77 additions & 0 deletions freenit/api/role/ldap_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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, id, _: User = Depends(group_perms)) -> Group:
user = await User.get_by_uid(id)
group = await Group.get(name, domain)
await group.add(user)
return group

@staticmethod
@description("Remove user from group")
async def delete(domain, name, id, _: User = Depends(group_perms)) -> Group:
user = await User.get_by_uid(id)
group = await Group.get(name, domain)
await group.remove(user)
return group
34 changes: 8 additions & 26 deletions freenit/api/user/ldap.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import bonsai
from bonsai import errors
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.base import get_client
from freenit.models.pagination import Page
from freenit.models.safe import UserSafe
from freenit.models.user import User, UserOptional
Expand All @@ -26,15 +25,15 @@ async def get(
) -> Page[UserSafe]:
users = await User.get_all()
total = len(users)
page = Page(total=total, page=1, pages=1, perpage=total, data=users)
return page
data = Page(total=total, page=1, pages=1, perpage=total, data=users)
return data


@route("/users/{id}", tags=tags)
class UserDetailAPI:
@staticmethod
async def get(id, _: User = Depends(user_perms)) -> UserSafe:
user = await User.get(id)
user = await User.get_by_uid(id)
return user

@staticmethod
Expand All @@ -50,29 +49,12 @@ async def patch(id, data: UserOptional, _: User = Depends(user_perms)) -> UserSa

@staticmethod
async def delete(id, _: User = Depends(user_perms)) -> UserSafe:
client = get_client()
try:
async with client.connect(is_async=True) as conn:
res = await conn.search(
id, bonsai.LDAPSearchScope.SUB, "objectClass=person"
)
if len(res) < 1:
raise HTTPException(status_code=404, detail="No such user")
if len(res) > 1:
raise HTTPException(status_code=409, detail="Multiple users found")
existing = res[0]
user = User(
email=existing["mail"][0],
sn=existing["sn"][0],
cn=existing["cn"][0],
dn=str(existing["dn"]),
uid=existing["uid"][0],
userClass=existing["userClass"][0],
)
await existing.delete()
return user
except bonsai.errors.AuthenticationError:
user = await User.get_by_uid(id)
await user.destroy()
except errors.AuthenticationError:
raise HTTPException(status_code=403, detail="Failed to login")
return user


@route("/profile", tags=["profile"])
Expand Down
4 changes: 2 additions & 2 deletions freenit/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ def encode(user):
config = getConfig()
payload = {}
if user.dbtype() == "sql":
payload = {"pk": user.pk, "type": "ormar"}
payload = {"pk": user.pk, "type": "sql"}
elif user.dbtype() == "ldap":
payload = {"pk": user.dn, "type": "bonsai"}
payload = {"pk": user.dn, "type": "ldap"}
return jwt.encode(payload, config.secret, algorithm="HS256")


Expand Down
35 changes: 31 additions & 4 deletions freenit/base_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,46 @@ def __init__(
self,
host="ldap.example.com",
tls=True,
base="uid={},ou={},dc=account,dc=ldap",
service_dn="cn=freenit,dc=service,dc=ldap",
service_pw="",
roleDN="cn={}",
roleBase="dc=group,dc=ldap",
roleClasses=["groupOfUniqueNames"],
roleMemberAttr="uniqueMember",
groupBase="ou={},dc=group,dc=ldap",
groupDN="cn={}",
groupClasses=["posixGroup"],
userBase="dc=account,dc=ldap",
userDN="uid={},ou={}",
userClasses=["pilotPerson", "posixAccount"],
groupClasses=["groupOfUniqueNames"],
userMemberAttr="memberOf",
uidNextClass="uidNext",
uidNextDN="cn=uidnext,dc=ldap",
uidNextField="uidNumber",
gidNextClass="gidNext",
gidNextDN="cn=gidnext,dc=ldap",
gidNextField="gidNumber",
):
self.host = host
self.tls = tls
self.base = base
self.service_dn = service_dn
self.service_pw = service_pw
self.userClasses = userClasses
self.roleBase = roleBase
self.roleClasses = roleClasses
self.roleDN = f"{roleDN},{roleBase}"
self.roleMemberAttr = roleMemberAttr
self.groupClasses = groupClasses
self.groupDN = f"{groupDN},{groupBase}"
self.userBase = userBase
self.userDN = f"{userDN},{userBase}"
self.userClasses = userClasses
self.userMemberAttr = userMemberAttr
self.uidNextClass = uidNextClass
self.uidNextDN = uidNextDN
self.uidNextField = uidNextField
self.gidNextClass = gidNextClass
self.gidNextDN = gidNextDN
self.gidNextField = gidNextField


class BaseConfig:
Expand Down
Loading