@@ -22,10 +22,13 @@ class UserSafe(LDAPBaseModel):
2222 email : EmailStr = Field ("" , description = ("Email" ))
2323 cn : str = Field ("" , description = ("Common name" ))
2424 sn : str = Field ("" , description = ("Surname" ))
25- userClass : str = Field ("" , description = ("User class" ))
25+ userClass : list [ str ] = Field ([] , description = ("User class" ))
2626 roles : list = Field ([], description = ("Roles the user is a member of" ))
27- uidNumber : int = Field (0 , description = ("UID" ))
28- gidNumber : int = Field (0 , description = ("GID" ))
27+ groups : list = Field ([], description = ("Groups the user is a member of" ))
28+ uidNumber : int = Field (0 , description = ("User ID number" ))
29+ gidNumber : int = Field (0 , description = ("Group ID number" ))
30+ active : bool = Field (False , description = ("Active user" ))
31+ admin : bool = Field (False , description = ("Admin user" ))
2932
3033 @classmethod
3134 async def _login (cls , credentials ) -> dict :
@@ -36,17 +39,37 @@ async def _login(cls, credentials) -> dict:
3639 dn = config .ldap .userDN .format (username , domain )
3740 res = await conn .search (dn , LDAPSearchScope .BASE , "objectClass=person" )
3841 except errors .ConnectionError :
39- raise HTTPException (status_code = 409 , detail = "Can not connect to LDAP server" )
42+ raise HTTPException (
43+ status_code = 409 , detail = "Can not connect to LDAP server"
44+ )
4045 except errors .AuthenticationError :
4146 raise HTTPException (status_code = 403 , detail = "Failed to login" )
4247 data = res [0 ]
4348 return data
4449
50+ async def _fill_groups (self ):
51+ _ , domain = self .email .split ('@' )
52+ classes = class2filter (config .ldap .groupClasses )
53+ dn = f"{ config .ldap .domainDN .format (domain )} ,{ config .ldap .roleBase } "
54+ client = get_client ()
55+ async with client .connect (is_async = True ) as conn :
56+ try :
57+ res = await conn .search (
58+ dn ,
59+ LDAPSearchScope .SUB ,
60+ f"(&{ classes } (memberUid={ self .uidNumber } ))" ,
61+ )
62+ except errors .AuthenticationError :
63+ raise HTTPException (status_code = 403 , detail = "Failed to login" )
64+ self .groups = [g ["gidNumber" ][0 ] for g in res ]
65+
4566 @classmethod
4667 async def login (cls , credentials ):
4768 data = await cls ._login (credentials )
4869 user = cls .from_entry (data )
49- return user
70+ if user .active :
71+ return user
72+ raise HTTPException (status_code = 403 , detail = "Failed to login" )
5073
5174 @classmethod
5275 async def register (cls , credentials ):
@@ -61,32 +84,49 @@ async def register(cls, credentials):
6184 except errors .UnwillingToPerform :
6285 raise HTTPException (status_code = 409 , detail = "Can not bind to LDAP" )
6386 except errors .AuthenticationError :
64- raise HTTPException (status_code = 409 , detail = "Can not bind to LDAP" )
87+ raise HTTPException (
88+ status_code = 409 , detail = "Can not login as service to LDAP"
89+ )
6590 user = cls (
6691 dn = dn ,
6792 cn = "Common Name" ,
6893 sn = "Surname" ,
6994 uid = username ,
7095 email = credentials .email ,
71- userClass = "disabled" ,
96+ userClass = [] ,
7297 uidNumber = 65535 ,
7398 gidNumber = 65535 ,
7499 roles = [],
100+ groups = [],
101+ active = False ,
102+ admin = False ,
75103 )
76104 return user
77105
78106 @classmethod
79- def from_entry (cls , entry ) -> UserSafe :
107+ def from_entry (cls , entry , groups = []) -> UserSafe :
108+ active = False
109+ admin = False
110+ userClass = entry .get ("userClass" , [])
111+ if "active" in userClass :
112+ userClass .remove ("active" )
113+ active = True
114+ if "admin" in userClass :
115+ userClass .remove ("admin" )
116+ admin = True
80117 user = cls (
81118 email = entry ["mail" ][0 ],
82119 sn = entry ["sn" ][0 ],
83120 cn = entry ["cn" ][0 ],
84121 dn = str (entry ["dn" ]),
85122 uid = entry ["uid" ][0 ],
86- userClass = entry [ " userClass" ][ 0 ] ,
123+ userClass = userClass ,
87124 roles = entry .get ("memberOf" , []),
125+ groups = groups ,
88126 uidNumber = entry ["uidNumber" ][0 ],
89127 gidNumber = entry ["gidNumber" ][0 ],
128+ active = active ,
129+ admin = admin ,
90130 )
91131 return user
92132
@@ -107,6 +147,7 @@ async def get_all(cls):
107147 data = []
108148 for udata in res :
109149 user = cls .from_entry (udata )
150+ await user ._fill_groups ()
110151 data .append (user )
111152 return data
112153
@@ -131,26 +172,28 @@ async def get(cls, dn):
131172 raise HTTPException (status_code = 409 , detail = "Multiple users found" )
132173 data = res [0 ]
133174 user = cls .from_entry (data )
175+ await user ._fill_groups ()
134176 return user
135177
136178 @classmethod
137179 async def get_by_uid (cls , uid : int ):
138180 client = get_client ()
139- try :
140- async with client . connect ( is_async = True ) as conn :
181+ async with client . connect ( is_async = True ) as conn :
182+ try :
141183 res = await conn .search (
142184 config .ldap .userBase ,
143185 LDAPSearchScope .SUB ,
144186 f"(&(objectClass=person)(uidNumber={ uid } ))" ,
145187 ["*" , config .ldap .userMemberAttr ],
146188 )
147- except errors .AuthenticationError :
148- raise HTTPException (status_code = 403 , detail = "Failed to login" )
149- if len (res ) < 1 :
150- raise HTTPException (status_code = 404 , detail = "No such user" )
151- if len (res ) > 1 :
152- raise HTTPException (status_code = 409 , detail = "Multiple users found" )
189+ except errors .AuthenticationError :
190+ raise HTTPException (status_code = 403 , detail = "Failed to login" )
191+ if len (res ) < 1 :
192+ raise HTTPException (status_code = 404 , detail = "No such user" )
193+ if len (res ) > 1 :
194+ raise HTTPException (status_code = 409 , detail = "Multiple users found" )
153195 user = cls .from_entry (res [0 ])
196+ await user ._fill_groups ()
154197 return user
155198
156199 @classmethod
@@ -173,6 +216,7 @@ async def get_by_email(cls, email):
173216 if len (res ) > 1 :
174217 raise HTTPException (status_code = 409 , detail = "Multiple users found" )
175218 user = cls .from_entry (res [0 ])
219+ await user ._fill_groups ()
176220 return user
177221
178222
@@ -191,6 +235,11 @@ async def save(self):
191235 data ["memberUid" ] = uidNext
192236 await save_data (data )
193237
238+ userClass = self .userClass .copy ()
239+ if self .active :
240+ userClass .append ("active" )
241+ if self .admin :
242+ userClass .append ("admin" )
194243 data = LDAPEntry (self .dn )
195244 data ["objectClass" ] = config .ldap .userClasses
196245 data ["uid" ] = self .uid
@@ -212,44 +261,78 @@ async def save(self):
212261 async with client .connect (is_async = True ) as conn :
213262 await conn .modify_password (self .dn , self .password )
214263
215- async def update (self , active = False , ** kwargs ):
264+ async def update (
265+ self ,
266+ active = None ,
267+ admin = None ,
268+ password = None ,
269+ roles = None ,
270+ userClass = None ,
271+ ** kwargs ,
272+ ):
216273 client = get_client ()
217- userClass = "disabled"
218- if active :
219- userClass = "enabled"
220- special = {"password" , "roles" }
221- filtered = {x : kwargs [x ] for x in kwargs if x not in special }
222274 async with client .connect (is_async = True ) as conn :
223275 res = await conn .search (self .dn , LDAPSearchScope .BASE )
224276 data = res [0 ]
225- if len (filtered .keys ()) > 0 :
226- for field in filtered :
227- data [field ] = filtered [field ]
228- password = kwargs .get ("password" , None )
277+ for field in kwargs :
278+ if field == 'uidNumber' or field == 'gidNumber' :
279+ if kwargs [field ] == 0 :
280+ continue
281+ data [field ] = kwargs [field ]
282+ uclass = self .userClass .copy ()
283+ if self .active :
284+ uclass .append ('active' )
285+ if self .admin :
286+ uclass .append ('admin' )
287+ if active is not None :
288+ if active :
289+ if 'active' not in uclass :
290+ uclass .append ('active' )
291+ self .active = True
292+ else :
293+ if 'active' in uclass :
294+ uclass .remove ('active' )
295+ self .active = False
296+ if admin is not None :
297+ if admin :
298+ if 'admin' not in uclass :
299+ uclass .append ('admin' )
300+ self .admin = True
301+ else :
302+ if 'admin' in uclass :
303+ uclass .remove ('admin' )
304+ self .admin = False
305+ data ["userClass" ] = uclass
306+ await data .modify ()
229307 if password is not None :
230308 await conn .modify_password (self .dn , password )
231- data ["userClass" ] = userClass
232- data .change_attribute ("userClass" , LDAPModOp .REPLACE , userClass )
233- self .userClass = userClass
234- await data .modify ()
235- for field in filtered :
236- setattr (self , field , filtered [field ])
309+ for field in kwargs :
310+ if field == 'uidNumber' or field == 'gidNumber' :
311+ if kwargs [field ] == 0 :
312+ continue
313+ setattr (self , field , kwargs [field ])
237314
238315 async def destroy (self ):
239316 client = get_client ()
240317 async with client .connect (is_async = True ) as conn :
318+ classes = class2filter (config .ldap .roleClasses )
319+ filter_exp = f"(&(uniqueMember={ self .dn } ){ classes } )"
320+ res = await conn .search (
321+ config .ldap .roleBase , LDAPSearchScope .SUB , filter_exp
322+ )
323+ for role in res :
324+ if len (role ["uniqueMember" ]) == 1 :
325+ raise ValueError (
326+ f"Can not destroy user as it is only member of role { role .cn } !"
327+ )
241328 classes = class2filter (config .ldap .groupClasses )
242- filter_exp = f"(&(memberUid={ self .uidNumber } ){ classes } )"
243- res = await conn .search (config .ldap .roleBase , LDAPSearchScope .SUB , filter_exp )
329+ filter_exp = f"(&(memberUid={ self .uidNumber } ){ classes } )"
330+ res = await conn .search (
331+ config .ldap .roleBase , LDAPSearchScope .SUB , filter_exp
332+ )
244333 for group in res :
245- if len (group [' memberUid' ]) :
334+ if len (group [" memberUid" ]) == 1 :
246335 await group .delete ()
247- classes = class2filter (config .ldap .roleClasses )
248- filter_exp = f"(&(uniqueMember={ self .dn } ){ classes } )"
249- res = await conn .search (config .ldap .roleBase , LDAPSearchScope .SUB , filter_exp )
250- for role in res :
251- if len (role ['uniqueMember' ]):
252- raise ValueError (f"Can not destroy user as it is only member of role { role .cn } !" )
253336 res = await conn .search (self .dn , LDAPSearchScope .BASE )
254337 data = res [0 ]
255338 await data .delete ()
0 commit comments