6
6
from canaille .backends .models import Model
7
7
8
8
from .backend import Backend
9
+ from .utils import cardinalize_attribute
9
10
from .utils import ldap_to_python
10
11
from .utils import listify
11
12
from .utils import python_to_ldap
@@ -109,7 +110,7 @@ class LDAPObject(Model, metaclass=LDAPObjectMetaclass):
109
110
base = None
110
111
root_dn = None
111
112
rdn_attribute = None
112
- attributes = None
113
+ attribute_map = None
113
114
ldap_object_class = None
114
115
115
116
def __init__ (self , dn = None , ** kwargs ):
@@ -121,38 +122,38 @@ def __init__(self, dn=None, **kwargs):
121
122
setattr (self , name , value )
122
123
123
124
def __repr__ (self ):
124
- reverse_attributes = {v : k for k , v in (self .attributes or {}).items ()}
125
- attribute_name = reverse_attributes .get (self .rdn_attribute , self .rdn_attribute )
125
+ attribute_name = self .ldap_attribute_to_python (self .rdn_attribute )
126
126
return (
127
127
f"<{ self .__class__ .__name__ } { attribute_name } ={ self .rdn_value } >"
128
128
if self .rdn_attribute
129
129
else "<LDAPOBject>"
130
130
)
131
131
132
132
def __eq__ (self , other ):
133
+ ldap_attributes = self .may () + self .must ()
133
134
if not (
134
135
isinstance (other , self .__class__ )
135
136
and self .may () == other .may ()
136
137
and self .must () == other .must ()
137
138
and all (
138
- hasattr ( self , attr ) == hasattr ( other , attr )
139
- for attr in self . may () + self . must ()
139
+ self . has_ldap_attribute ( attr ) == other . has_ldap_attribute ( attr )
140
+ for attr in ldap_attributes
140
141
)
141
142
):
142
143
return False
143
144
144
145
self_attributes = python_attrs_to_ldap (
145
146
{
146
- attr : getattr ( self , attr )
147
- for attr in self . may () + self . must ()
148
- if hasattr ( self , attr )
147
+ attr : self . get_ldap_attribute ( attr )
148
+ for attr in ldap_attributes
149
+ if self . has_ldap_attribute ( attr )
149
150
}
150
151
)
151
152
other_attributes = python_attrs_to_ldap (
152
153
{
153
- attr : getattr ( other , attr )
154
- for attr in self . may () + self . must ()
155
- if hasattr ( self , attr )
154
+ attr : other . get_ldap_attribute ( attr )
155
+ for attr in ldap_attributes
156
+ if other . has_ldap_attribute ( attr )
156
157
}
157
158
)
158
159
return self_attributes == other_attributes
@@ -161,17 +162,40 @@ def __hash__(self):
161
162
return hash (self .id )
162
163
163
164
def __getattr__ (self , name ):
164
- name = self .attributes .get (name , name )
165
-
166
- if name not in self .ldap_object_attributes ():
165
+ if name not in self .attributes :
167
166
return super ().__getattribute__ (name )
168
167
169
- single_value = self .ldap_object_attributes ()[name ].single_value
168
+ ldap_name = self .python_attribute_to_ldap (name )
169
+
170
+ if ldap_name == "dn" :
171
+ return self .dn_for (self .rdn_value )
172
+
173
+ python_single_value = "List" not in str (self .__annotations__ [name ])
174
+ ldap_value = self .get_ldap_attribute (ldap_name )
175
+ return cardinalize_attribute (python_single_value , ldap_value )
176
+
177
+ def __setattr__ (self , name , value ):
178
+ if name not in self .attributes :
179
+ super ().__setattr__ (name , value )
180
+
181
+ ldap_name = self .python_attribute_to_ldap (name )
182
+ self .set_ldap_attribute (ldap_name , value )
183
+
184
+ def __delattr__ (self , name ):
185
+ ldap_name = self .python_attribute_to_ldap (name )
186
+ self .delete_ldap_attribute (ldap_name )
187
+
188
+ def has_ldap_attribute (self , name ):
189
+ return name in self .ldap_object_attributes () and (
190
+ name in self .changes or name in self .state
191
+ )
192
+
193
+ def get_ldap_attribute (self , name ):
170
194
if name in self .changes :
171
- return self .changes [name ][ 0 ] if single_value else self . changes [ name ]
195
+ return self .changes [name ]
172
196
173
197
if not self .state .get (name ):
174
- return None if single_value else []
198
+ return None
175
199
176
200
# Lazy conversion from ldap format to python format
177
201
if any (isinstance (value , bytes ) for value in self .state [name ]):
@@ -180,35 +204,23 @@ def __getattr__(self, name):
180
204
ldap_to_python (value , syntax ) for value in self .state [name ]
181
205
]
182
206
183
- if single_value :
184
- return self .state .get (name )[0 ]
185
- else :
186
- return [value for value in self .state .get (name ) if value is not None ]
207
+ return self .state .get (name )
187
208
188
- def __setattr__ (self , name , value ):
189
- if self .attributes :
190
- name = self . attributes . get ( name , name )
209
+ def set_ldap_attribute (self , name , value ):
210
+ if name not in self .ldap_object_attributes () :
211
+ return
191
212
192
- if name in self .ldap_object_attributes ():
193
- value = listify (value )
194
- self .changes [name ] = value
213
+ value = listify (value )
214
+ self .changes [name ] = value
195
215
196
- else :
197
- super ().__setattr__ (name , value )
198
-
199
- def __delattr__ (self , name ):
200
- name = self .attributes .get (name , name )
216
+ def delete_ldap_attribute (self , name ):
201
217
self .changes [name ] = [None ]
202
218
203
219
@property
204
220
def rdn_value (self ):
205
- value = getattr ( self , self .rdn_attribute )
221
+ value = self . get_ldap_attribute ( self .rdn_attribute )
206
222
return (value [0 ] if isinstance (value , list ) else value ).strip ()
207
223
208
- @property
209
- def dn (self ):
210
- return self .dn_for (self .rdn_value )
211
-
212
224
@classmethod
213
225
def dn_for (cls , rdn ):
214
226
return f"{ cls .rdn_attribute } ={ ldap .dn .escape_dn_chars (rdn )} ,{ cls .base } ,{ cls .root_dn } "
@@ -317,7 +329,7 @@ def query(cls, id=None, filter=None, conn=None, **kwargs):
317
329
arg_filter = ""
318
330
kwargs = python_attrs_to_ldap (
319
331
{
320
- ( cls .attributes or {}). get ( name , name ): values
332
+ cls .python_attribute_to_ldap ( name ): values
321
333
for name , values in kwargs .items ()
322
334
},
323
335
encode = False ,
@@ -350,7 +362,7 @@ def query(cls, id=None, filter=None, conn=None, **kwargs):
350
362
def fuzzy (cls , query , attributes = None , ** kwargs ):
351
363
query = ldap .filter .escape_filter_chars (query )
352
364
attributes = attributes or cls .may () + cls .must ()
353
- attributes = [cls .attributes . get ( name , name ) for name in attributes ]
365
+ attributes = [cls .python_attribute_to_ldap ( name ) for name in attributes ]
354
366
filter = (
355
367
"(|" + "" .join (f"({ attribute } =*{ query } *)" for attribute in attributes ) + ")"
356
368
)
@@ -380,6 +392,15 @@ def update_ldap_attributes(cls):
380
392
cls ._may = list (set (cls ._may ))
381
393
cls ._must = list (set (cls ._must ))
382
394
395
+ @classmethod
396
+ def ldap_attribute_to_python (cls , name ):
397
+ reverse_attribute_map = {v : k for k , v in (cls .attribute_map or {}).items ()}
398
+ return reverse_attribute_map .get (name , name )
399
+
400
+ @classmethod
401
+ def python_attribute_to_ldap (cls , name ):
402
+ return cls .attribute_map .get (name , name ) if cls .attribute_map else None
403
+
383
404
def reload (self , conn = None ):
384
405
conn = conn or Backend .get ().connection
385
406
result = conn .search_s (self .id , ldap .SCOPE_SUBTREE , None , ["+" , "*" ])
@@ -389,7 +410,7 @@ def reload(self, conn=None):
389
410
def save (self , conn = None ):
390
411
conn = conn or Backend .get ().connection
391
412
392
- setattr ( self , "objectClass" , self .ldap_object_class )
413
+ self . set_ldap_attribute ( "objectClass" , self .ldap_object_class )
393
414
394
415
# Object already exists in the LDAP database
395
416
if self .exists :
@@ -429,10 +450,6 @@ def save(self, conn=None):
429
450
self .state = {** self .state , ** self .changes }
430
451
self .changes = {}
431
452
432
- def update (self , ** kwargs ):
433
- for k , v in kwargs .items ():
434
- self .__setattr__ (k , v )
435
-
436
453
def delete (self , conn = None ):
437
454
conn = conn or Backend .get ().connection
438
455
conn .delete_s (self .id )
0 commit comments