Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement LDAP for auth DB #135

Merged
merged 1 commit into from
Apr 15, 2024
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/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.3.1"
__version__ = "0.3.2"
19 changes: 16 additions & 3 deletions freenit/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,8 @@ async def login(credentials: LoginInput, response: Response):
}


@api.post("/auth/register", tags=["auth"])
async def register(credentials: LoginInput, host=Header(default="")):
async def register_ormar(credentials: LoginInput) -> User:
import ormar.exceptions

try:
user = await User.objects.get(email=credentials.email)
raise HTTPException(status_code=409, detail="User already registered")
Expand All @@ -73,6 +71,21 @@ async def register(credentials: LoginInput, host=Header(default="")):
active=False,
)
await user.save()
return user


async def register_bonsai(credentials: LoginInput) -> User:
user = await User.register(credentials)
await user.save()
return user


@api.post("/auth/register", tags=["auth"])
async def register(credentials: LoginInput, host=Header(default="")):
if User.dbtype() == "ormar":
user = await register_ormar(credentials)
else:
user = await register_bonsai(credentials)
token = encode(user)
print(token)
mail = config.mail
Expand Down
196 changes: 138 additions & 58 deletions freenit/api/role.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import List

import ormar
import ormar.exceptions
from fastapi import Depends, Header, HTTPException
Expand All @@ -23,85 +21,167 @@ async def get(
perpage: int = Header(default=10),
_: User = Depends(role_perms),
) -> Page[RoleSafe]:
roles = Role.objects
return await paginate(roles, page, perpage)
if Role.dbtype() == "ormar":
return await paginate(Role.objects, page, perpage)
elif Role.dbtype() == "bonsai":
import bonsai

from freenit.models.ldap.base import get_client

client = get_client()
try:
async with client.connect(is_async=True) as conn:
res = await conn.search(
f"dc=group,dc=ldap",
bonsai.LDAPSearchScope.SUB,
"objectClass=groupOfUniqueNames",
)
except bonsai.errors.AuthenticationError:
raise HTTPException(status_code=403, detail="Failed to login")
data = []
for gdata in res:
role = Role(
cn=gdata["cn"][0],
dn=str(gdata["dn"]),
uniqueMembers=gdata["uniqueMember"],
)
data.append(role)

total = len(res)
page = Page(total=total, page=1, pages=1, perpage=total, data=data)
return page
raise HTTPException(status_code=409, detail="Unknown group type")

@staticmethod
async def post(role: Role, _: User = Depends(role_perms)) -> RoleSafe:
await role.save()
async def post(role: Role, user: User = Depends(role_perms)) -> RoleSafe:
if Role.dbtype() == "ormar":
await role.save()
elif Role.dbtype() == "bonsai":
import bonsai
try:
await role.create(user)
except bonsai.errors.AlreadyExists:
raise HTTPException(status_code=409, detail="Role already exists")
return role


@route("/roles/{id}", tags=tags)
class RoleDetailAPI:
@staticmethod
async def get(id: int, _: User = Depends(role_perms)) -> RoleSafe:
try:
role = await Role.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such role")
await role.load_all(follow=True)
return role
async def get(id, _: User = Depends(role_perms)) -> RoleSafe:
if Role.dbtype() == "ormar":
try:
role = await Role.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such role")
await role.load_all(follow=True)
return role
elif Role.dbtype() == "bonsai":
role = Role.get(id)
return role
raise HTTPException(status_code=409, detail="Unknown role type")

@staticmethod
async def patch(
id: int, role_data: RoleOptional, _: User = Depends(role_perms)
id, role_data: RoleOptional, _: User = Depends(role_perms)
) -> RoleSafe:
try:
role = await Role.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such role")
await role.patch(role_data)
return role
if Role.dbtype() == "ormar":
try:
role = await Role.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such role")
await role.patch(role_data)
return role
raise HTTPException(status_code=409, detail=f"Role type {Role.dbtype()} doesn't support PATCH method")

@staticmethod
async def delete(id: int, _: User = Depends(role_perms)) -> RoleSafe:
try:
role = await Role.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such role")
await role.delete()
return role
async def delete(id, _: User = Depends(role_perms)) -> RoleSafe:
if Role.dbtype() == "ormar":
try:
role = await Role.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such role")
await role.delete()
return role
elif Role.dbtype() == "bonsai":
import bonsai

from freenit.models.ldap.base import get_client

client = get_client()
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"]),
uniqueMembers=existing["uniqueMember"],
)
await existing.delete()
return role
except bonsai.errors.AuthenticationError:
raise HTTPException(status_code=403, detail="Failed to login")
raise HTTPException(status_code=409, detail="Unknown role type")


@route("/roles/{role_id}/{user_id}", tags=tags)
class RoleUserAPI:
@staticmethod
@description("Assign user to role")
async def post(
role_id: int, user_id: int, _: User = Depends(role_perms)
role_id, user_id, _: User = Depends(role_perms)
) -> UserSafe:
try:
user = await User.objects.get(pk=user_id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such user")
await user.load_all()
for role in user.roles:
if role.id == role_id:
raise HTTPException(status_code=409, detail="User already assigned")
try:
role = await Role.objects.get(pk=role_id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such role")
await user.roles.add(role)
return user
if Role.dbtype() == "ormar":
try:
user = await User.objects.get(pk=user_id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such user")
await user.load_all()
for role in user.roles:
if role.id == role_id:
raise HTTPException(status_code=409, detail="User already assigned")
try:
role = await Role.objects.get(pk=role_id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such role")
await user.roles.add(role)
return user
elif Role.dbtype() == "bonsai":
user = await User.get(user_id)
role = await Role.get(role_id)
await role.add(user)
return user
raise HTTPException(status_code=409, detail="Unknown role type")

@staticmethod
@description("Deassign user to role")
async def delete(
role_id: int, user_id: int, _: User = Depends(role_perms)
role_id, user_id, _: User = Depends(role_perms)
) -> UserSafe:
try:
user = await User.objects.get(pk=user_id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such user")
try:
role = await Role.objects.get(pk=role_id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such role")
await user.load_all()
try:
await user.roles.remove(role)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="User is not part of role")
return user
if Role.dbtype() == "ormar":
try:
user = await User.objects.get(pk=user_id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such user")
try:
role = await Role.objects.get(pk=role_id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such role")
await user.load_all()
try:
await user.roles.remove(role)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="User is not part of role")
return user
elif Role.dbtype() == "bonsai":
user = await User.get(user_id)
role = await Role.get(role_id)
await role.remove(user)
return user
raise HTTPException(status_code=409, detail="Unknown role type")
94 changes: 70 additions & 24 deletions freenit/api/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ async def get(
elif User.dbtype() == "bonsai":
import bonsai

client = bonsai.LDAPClient(f"ldap://{config.ldap.host}", config.ldap.tls)
from freenit.models.ldap.base import get_client

client = get_client()
try:
async with client.connect(is_async=True) as conn:
res = await conn.search(
Expand Down Expand Up @@ -64,35 +66,79 @@ async def get(
@route("/users/{id}", tags=tags)
class UserDetailAPI:
@staticmethod
async def get(id: int, _: User = Depends(user_perms)) -> UserSafe:
try:
user = await User.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such user")
await user.load_all(follow=True)
return user
async def get(id, _: User = Depends(user_perms)) -> UserSafe:
if User.dbtype() == "ormar":
try:
user = await User.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such user")
await user.load_all(follow=True)
return user
elif User.dbtype() == "bonsai":
user = await User.get(id)
return user
raise HTTPException(status_code=409, detail="Unknown user type")

@staticmethod
async def patch(
id: int, data: UserOptional, _: User = Depends(user_perms)
id, data: UserOptional, _: User = Depends(user_perms)
) -> UserSafe:
if data.password:
data.password = encrypt(data.password)
try:
user = await User.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such user")
await user.patch(data)
return user
if User.dbtype() == "ormar":
if data.password:
data.password = encrypt(data.password)
try:
user = await User.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such user")
await user.patch(data)
return user
elif User.dbtype() == "bonsai":
user = await User.get(id)
update = {
field: getattr(data, field) for field in data.__fields__ if getattr(data, field) != ''
}
await user.update(active=user.userClass, **update)
return user
raise HTTPException(status_code=409, detail="Unknown user type")

@staticmethod
async def delete(id: int, _: User = Depends(user_perms)) -> UserSafe:
try:
user = await User.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such user")
await user.delete()
return user
async def delete(id, _: User = Depends(user_perms)) -> UserSafe:
if User.dbtype() == "ormar":
try:
user = await User.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such user")
await user.delete()
return user
elif User.dbtype() == "bonsai":
import bonsai

from freenit.models.ldap.base import get_client

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:
raise HTTPException(status_code=403, detail="Failed to login")
raise HTTPException(status_code=409, detail="Unknown user type")


@route("/profile", tags=["profile"])
Expand Down
Loading
Loading