]> git.ipfire.org Git - ipfire.org.git/blame - src/backend/accounts.py
people: Hide various things from users that are not staff
[ipfire.org.git] / src / backend / accounts.py
CommitLineData
940227cb 1#!/usr/bin/python
78fdedae 2# encoding: utf-8
940227cb 3
2cd9af74 4import PIL
22153577 5import PIL.ImageOps
3ea97943 6import datetime
11347e46 7import io
940227cb 8import ldap
e96e445b 9import ldap.modlist
27066195 10import logging
e96e445b 11import phonenumbers
f4672785 12import sshpubkeys
eea71144
MT
13import urllib.parse
14import urllib.request
6b582a4f 15import zxcvbn
940227cb 16
e96e445b 17from . import util
917434b8 18from .decorators import *
11347e46 19from .misc import Object
940227cb 20
a6dc0bad 21class Accounts(Object):
9f05796c
MT
22 def __iter__(self):
23 # Only return developers (group with ID 1000)
1bae74c7 24 accounts = self._search("(&(objectClass=posixAccount)(gidNumber=1000))")
9f05796c 25
1bae74c7 26 return iter(sorted(accounts))
9f05796c 27
0ab42c1d 28 @lazy_property
66862195 29 def ldap(self):
0ab42c1d
MT
30 # Connect to LDAP server
31 ldap_uri = self.settings.get("ldap_uri")
32 conn = ldap.initialize(ldap_uri)
940227cb 33
0ab42c1d
MT
34 # Bind with username and password
35 bind_dn = self.settings.get("ldap_bind_dn")
36 if bind_dn:
37 bind_pw = self.settings.get("ldap_bind_pw", "")
38 conn.simple_bind(bind_dn, bind_pw)
66862195 39
0ab42c1d 40 return conn
940227cb 41
1bae74c7 42 def _query(self, query, attrlist=None, limit=0):
66862195 43 logging.debug("Performing LDAP query: %s" % query)
940227cb 44
66862195 45 search_base = self.settings.get("ldap_search_base")
a395634c 46
a69e87a1
MT
47 try:
48 results = self.ldap.search_ext_s(search_base, ldap.SCOPE_SUBTREE,
49 query, attrlist=attrlist, sizelimit=limit)
50 except:
51 # Close current connection
0ab42c1d 52 del self.ldap
a69e87a1
MT
53
54 raise
940227cb 55
66862195 56 return results
940227cb 57
1bae74c7 58 def _search(self, query, attrlist=None, limit=0):
66862195 59 accounts = []
1bae74c7
MT
60
61 for dn, attrs in self._query(query, attrlist=attrlist, limit=limit):
66862195
MT
62 account = Account(self.backend, dn, attrs)
63 accounts.append(account)
64
1bae74c7
MT
65 return accounts
66
67 def search(self, query):
68 # Search for exact matches
73a54cb6 69 accounts = self._search("(&(objectClass=person) \
1bae74c7
MT
70 (|(uid=%s)(mail=%s)(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
71 % (query, query, query, query, query, query))
72
73 # Find accounts by name
74 if not accounts:
73a54cb6 75 for account in self._search("(&(objectClass=person)(|(cn=*%s*)(uid=*%s*)))" % (query, query)):
1bae74c7
MT
76 if not account in accounts:
77 accounts.append(account)
78
66862195
MT
79 return sorted(accounts)
80
1bae74c7
MT
81 def _search_one(self, query):
82 result = self._search(query, limit=1)
66862195
MT
83 assert len(result) <= 1
84
85 if result:
86 return result[0]
87
f32dd17f
MT
88 def uid_exists(self, uid):
89 if self.get_by_uid(uid):
90 return True
91
92 res = self.db.get("SELECT 1 FROM account_activations \
93 WHERE uid = %s AND expires_at > NOW()", uid)
94
95 if res:
96 return True
97
98 # Account with uid does not exist, yet
99 return False
100
66862195 101 def get_by_uid(self, uid):
73a54cb6 102 return self._search_one("(&(objectClass=person)(uid=%s))" % uid)
66862195
MT
103
104 def get_by_mail(self, mail):
73a54cb6 105 return self._search_one("(&(objectClass=inetOrgPerson)(mail=%s))" % mail)
66862195
MT
106
107 find = get_by_uid
108
109 def find_account(self, s):
110 account = self.get_by_uid(s)
111 if account:
112 return account
113
114 return self.get_by_mail(s)
940227cb 115
66862195 116 def get_by_sip_id(self, sip_id):
1bae74c7 117 return self._search_one("(|(&(objectClass=sipUser)(sipAuthenticationUser=%s)) \
66862195 118 (&(objectClass=sipRoutingObject)(sipLocalAddress=%s)))" % (sip_id, sip_id))
940227cb 119
525c01f7 120 def get_by_phone_number(self, number):
73a54cb6 121 return self._search_one("(&(objectClass=inetOrgPerson) \
525c01f7
MT
122 (|(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
123 % (number, number, number, number))
124
f32dd17f
MT
125 # Registration
126
127 def create(self, uid, email, first_name, last_name):
128 # Check if UID is unique
129 if self.get_by_uid(uid):
130 raise ValueError("UID exists: %s" % uid)
131
132 activation_code = util.random_string(24)
133
134 # Account Parameters
135 account = {
136 "objectClass" : [b"top", b"person", b"inetOrgPerson"],
137 "userPassword" : activation_code.encode(),
138 "mail" : email.encode(),
139
140 # Name
141 "cn" : b"%s %s" % (first_name.encode(), last_name.encode()),
142 "sn" : last_name.encode(),
143 "givenName" : first_name.encode(),
144 }
145
146 # Create account on LDAP
147 self.ldap.add_s("uid=%s,ou=People,dc=mcfly,dc=local" % uid, ldap.modlist.addModlist(account))
148
149 # TODO Send email with activation code
150 pass
151
66862195 152 # Session stuff
940227cb 153
66862195
MT
154 def _cleanup_expired_sessions(self):
155 self.db.execute("DELETE FROM sessions WHERE time_expires <= NOW()")
940227cb 156
66862195
MT
157 def create_session(self, account, host):
158 self._cleanup_expired_sessions()
940227cb 159
66862195
MT
160 res = self.db.get("INSERT INTO sessions(host, uid) VALUES(%s, %s) \
161 RETURNING session_id, time_expires", host, account.uid)
162
163 # Session could not be created
164 if not res:
165 return None, None
166
167 logging.info("Created session %s for %s which expires %s" \
168 % (res.session_id, account, res.time_expires))
169 return res.session_id, res.time_expires
170
171 def destroy_session(self, session_id, host):
172 logging.info("Destroying session %s" % session_id)
173
174 self.db.execute("DELETE FROM sessions \
175 WHERE session_id = %s AND host = %s", session_id, host)
176 self._cleanup_expired_sessions()
177
178 def get_by_session(self, session_id, host):
179 logging.debug("Looking up session %s" % session_id)
180
181 res = self.db.get("SELECT uid FROM sessions WHERE session_id = %s \
182 AND host = %s AND NOW() BETWEEN time_created AND time_expires",
183 session_id, host)
184
185 # Session does not exist or has expired
186 if not res:
187 return
188
189 # Update the session expiration time
190 self.db.execute("UPDATE sessions SET time_expires = NOW() + INTERVAL '14 days' \
191 WHERE session_id = %s AND host = %s", session_id, host)
192
193 return self.get_by_uid(res.uid)
194
940227cb 195
a6dc0bad 196class Account(Object):
66862195 197 def __init__(self, backend, dn, attrs=None):
a6dc0bad 198 Object.__init__(self, backend)
940227cb
MT
199 self.dn = dn
200
e96e445b 201 self.attributes = attrs or {}
940227cb 202
917434b8
MT
203 def __str__(self):
204 return self.name
205
940227cb
MT
206 def __repr__(self):
207 return "<%s %s>" % (self.__class__.__name__, self.dn)
208
541c952b
MT
209 def __eq__(self, other):
210 if isinstance(other, self.__class__):
211 return self.dn == other.dn
212
213 def __lt__(self, other):
214 if isinstance(other, self.__class__):
215 return self.name < other.name
940227cb
MT
216
217 @property
66862195
MT
218 def ldap(self):
219 return self.accounts.ldap
940227cb 220
e96e445b
MT
221 def _exists(self, key):
222 try:
223 self.attributes[key]
224 except KeyError:
225 return False
940227cb 226
e96e445b 227 return True
940227cb 228
e96e445b
MT
229 def _get(self, key):
230 for value in self.attributes.get(key, []):
231 yield value
940227cb 232
e96e445b
MT
233 def _get_bytes(self, key, default=None):
234 for value in self._get(key):
235 return value
236
237 return default
238
239 def _get_strings(self, key):
240 for value in self._get(key):
241 yield value.decode()
242
243 def _get_string(self, key, default=None):
244 for value in self._get_strings(key):
245 return value
246
247 return default
248
249 def _get_phone_numbers(self, key):
250 for value in self._get_strings(key):
251 yield phonenumbers.parse(value, None)
252
253 def _modify(self, modlist):
254 logging.debug("Modifying %s: %s" % (self.dn, modlist))
255
256 # Run modify operation
257 self.ldap.modify_s(self.dn, modlist)
258
259 def _set(self, key, values):
260 current = self._get(key)
261
262 # Don't do anything if nothing has changed
263 if list(current) == values:
264 return
265
266 # Remove all old values and add all new ones
267 modlist = []
940227cb 268
e96e445b
MT
269 if self._exists(key):
270 modlist.append((ldap.MOD_DELETE, key, None))
940227cb 271
e96e445b 272 # Add new values
47bb098f
MT
273 if values:
274 modlist.append((ldap.MOD_ADD, key, values))
e96e445b
MT
275
276 # Run modify operation
277 self._modify(modlist)
278
279 # Update cache
280 self.attributes.update({ key : values })
281
282 def _set_bytes(self, key, values):
283 return self._set(key, values)
284
285 def _set_strings(self, key, values):
47bb098f 286 return self._set(key, [e.encode() for e in values if e])
e96e445b
MT
287
288 def _set_string(self, key, value):
289 return self._set_strings(key, [value,])
940227cb 290
0d1fb712
MT
291 def _add(self, key, values):
292 modlist = [
293 (ldap.MOD_ADD, key, values),
294 ]
295
296 self._modify(modlist)
297
298 def _add_strings(self, key, values):
299 return self._add(key, [e.encode() for e in values])
300
301 def _add_string(self, key, value):
302 return self._add_strings(key, [value,])
303
304 def _delete(self, key, values):
305 modlist = [
306 (ldap.MOD_DELETE, key, values),
307 ]
308
309 self._modify(modlist)
310
311 def _delete_strings(self, key, values):
312 return self._delete(key, [e.encode() for e in values])
313
314 def _delete_string(self, key, value):
315 return self._delete_strings(key, [value,])
316
6b582a4f 317 def passwd(self, password):
3ea97943
MT
318 """
319 Sets a new password
320 """
6b582a4f
MT
321 # The new password must have a score of 3 or better
322 quality = self.check_password_quality(password)
323 if quality["score"] < 3:
324 raise ValueError("Password too weak")
325
326 self.ldap.passwd_s(self.dn, None, password)
3ea97943 327
940227cb
MT
328 def check_password(self, password):
329 """
330 Bind to the server with given credentials and return
331 true if password is corrent and false if not.
332
333 Raises exceptions from the server on any other errors.
334 """
0d1fb712
MT
335 if not password:
336 return
337
940227cb 338 logging.debug("Checking credentials for %s" % self.dn)
3ea97943
MT
339
340 # Create a new LDAP connection
341 ldap_uri = self.backend.settings.get("ldap_uri")
342 conn = ldap.initialize(ldap_uri)
343
940227cb 344 try:
3ea97943 345 conn.simple_bind_s(self.dn, password.encode("utf-8"))
940227cb 346 except ldap.INVALID_CREDENTIALS:
3ea97943 347 logging.debug("Account credentials are invalid for %s" % self)
940227cb
MT
348 return False
349
3ea97943
MT
350 logging.info("Successfully authenticated %s" % self)
351
940227cb
MT
352 return True
353
6b582a4f
MT
354 def check_password_quality(self, password):
355 """
356 Passwords are passed through zxcvbn to make sure
357 that they are strong enough.
358 """
359 return zxcvbn.zxcvbn(password, user_inputs=(
360 self.first_name, self.last_name,
361 ))
362
940227cb 363 def is_admin(self):
d82bc8e3 364 return "wheel" in self.groups
66862195 365
71a3109c
MT
366 def is_staff(self):
367 return "staff" in self.groups
368
369 def has_shell(self):
370 return "posixAccount" in self.classes
371
372 def has_mail(self):
373 return "postfixMailUser" in self.classes
374
375 def has_sip(self):
376 return "sipUser" in self.classes or "sipRoutingObject" in self.classes
66862195 377
e96e445b
MT
378 def can_be_managed_by(self, account):
379 """
380 Returns True if account is allowed to manage this account
381 """
382 # Admins can manage all accounts
383 if account.is_admin():
384 return True
385
386 # Users can manage themselves
387 return self == account
388
66862195
MT
389 @property
390 def classes(self):
e96e445b 391 return self._get_strings("objectClass")
66862195
MT
392
393 @property
394 def uid(self):
e96e445b 395 return self._get_string("uid")
940227cb 396
a6dc0bad
MT
397 @property
398 def name(self):
e96e445b 399 return self._get_string("cn")
66862195 400
e96e445b
MT
401 # First Name
402
403 def get_first_name(self):
404 return self._get_string("givenName")
405
406 def set_first_name(self, first_name):
407 self._set_string("givenName", first_name)
408
409 # Update Common Name
410 self._set_string("cn", "%s %s" % (first_name, self.last_name))
411
412 first_name = property(get_first_name, set_first_name)
413
414 # Last Name
415
416 def get_last_name(self):
417 return self._get_string("sn")
418
419 def set_last_name(self, last_name):
420 self._set_string("sn", last_name)
421
422 # Update Common Name
423 self._set_string("cn", "%s %s" % (self.first_name, last_name))
424
425 last_name = property(get_last_name, set_last_name)
66862195 426
1bae74c7 427 @lazy_property
66862195 428 def groups(self):
1bae74c7 429 groups = []
66862195 430
1bae74c7
MT
431 res = self.accounts._query("(&(objectClass=posixGroup) \
432 (memberUid=%s))" % self.uid, ["cn"])
66862195 433
1bae74c7
MT
434 for dn, attrs in res:
435 cns = attrs.get("cn")
436 if cns:
437 groups.append(cns[0].decode())
66862195 438
1bae74c7 439 return groups
66862195 440
e96e445b
MT
441 # Address
442
443 def get_address(self):
444 address = self._get_string("homePostalAddress")
445
446 if address:
447 return (line.strip() for line in address.split(","))
66862195 448
e96e445b
MT
449 return []
450
451 def set_address(self, address):
452 data = ", ".join(address.splitlines())
453
454 self._set_bytes("homePostalAddress", data.encode())
455
456 address = property(get_address, set_address)
a6dc0bad 457
940227cb
MT
458 @property
459 def email(self):
66862195 460 name = self.name.lower()
940227cb 461 name = name.replace(" ", ".")
78fdedae
MT
462 name = name.replace("Ä", "Ae")
463 name = name.replace("Ö", "Oe")
464 name = name.replace("Ü", "Ue")
465 name = name.replace("ä", "ae")
466 name = name.replace("ö", "oe")
467 name = name.replace("ü", "ue")
940227cb 468
66862195 469 for mail in self.attributes.get("mail", []):
7aee4b8d 470 if mail.decode().startswith("%s@ipfire.org" % name):
940227cb
MT
471 return mail
472
2cd9af74
MT
473 # If everything else fails, we will go with the UID
474 return "%s@ipfire.org" % self.uid
940227cb 475
e96e445b
MT
476 # Mail Routing Address
477
478 def get_mail_routing_address(self):
479 return self._get_string("mailRoutingAddress", None)
480
481 def set_mail_routing_address(self, address):
47bb098f 482 self._set_string("mailRoutingAddress", address or None)
e96e445b
MT
483
484 mail_routing_address = property(get_mail_routing_address, set_mail_routing_address)
485
66862195
MT
486 @property
487 def sip_id(self):
488 if "sipUser" in self.classes:
e96e445b 489 return self._get_string("sipAuthenticationUser")
66862195
MT
490
491 if "sipRoutingObject" in self.classes:
e96e445b 492 return self._get_string("sipLocalAddress")
66862195 493
2f51147a
MT
494 @property
495 def sip_password(self):
e96e445b
MT
496 return self._get_string("sipPassword")
497
498 @staticmethod
499 def _generate_sip_password():
500 return util.random_string(8)
2f51147a 501
66862195
MT
502 @property
503 def sip_url(self):
504 return "%s@ipfire.org" % self.sip_id
505
506 def uses_sip_forwarding(self):
e96e445b 507 if self.sip_routing_address:
66862195
MT
508 return True
509
510 return False
511
e96e445b
MT
512 # SIP Routing
513
514 def get_sip_routing_address(self):
66862195 515 if "sipRoutingObject" in self.classes:
e96e445b
MT
516 return self._get_string("sipRoutingAddress")
517
518 def set_sip_routing_address(self, address):
519 if not address:
520 address = None
521
522 # Don't do anything if nothing has changed
523 if self.get_sip_routing_address() == address:
524 return
525
526 if address:
527 modlist = [
528 # This is no longer a SIP user any more
529 (ldap.MOD_DELETE, "objectClass", b"sipUser"),
530 (ldap.MOD_DELETE, "sipAuthenticationUser", None),
531 (ldap.MOD_DELETE, "sipPassword", None),
532
533 (ldap.MOD_ADD, "objectClass", b"sipRoutingObject"),
534 (ldap.MOD_ADD, "sipLocalAddress", self.sip_id.encode()),
535 (ldap.MOD_ADD, "sipRoutingAddress", address.encode()),
536 ]
537 else:
538 modlist = [
539 (ldap.MOD_DELETE, "objectClass", b"sipRoutingObject"),
540 (ldap.MOD_DELETE, "sipLocalAddress", None),
541 (ldap.MOD_DELETE, "sipRoutingAddress", None),
542
543 (ldap.MOD_ADD, "objectClass", b"sipUser"),
544 (ldap.MOD_ADD, "sipAuthenticationUser", self.sip_id.encode()),
545 (ldap.MOD_ADD, "sipPassword", self._generate_sip_password().encode()),
546 ]
547
548 # Run modification
549 self._modify(modlist)
550
551 # XXX Cache is invalid here
552
553 sip_routing_address = property(get_sip_routing_address, set_sip_routing_address)
66862195 554
917434b8
MT
555 @lazy_property
556 def sip_registrations(self):
557 sip_registrations = []
558
559 for reg in self.backend.talk.freeswitch.get_sip_registrations(self.sip_url):
560 reg.account = self
561
562 sip_registrations.append(reg)
563
564 return sip_registrations
565
1f38be5a
MT
566 @lazy_property
567 def sip_channels(self):
568 return self.backend.talk.freeswitch.get_sip_channels(self)
569
bdaf6b46
MT
570 def get_cdr(self, date=None, limit=None):
571 return self.backend.talk.freeswitch.get_cdr_by_account(self, date=date, limit=limit)
525c01f7 572
e96e445b 573 # Phone Numbers
6ff61434 574
d3208ac7
MT
575 @lazy_property
576 def phone_number(self):
577 """
578 Returns the IPFire phone number
579 """
580 if self.sip_id:
581 return phonenumbers.parse("+4923636035%s" % self.sip_id)
582
583 @lazy_property
584 def fax_number(self):
585 if self.sip_id:
586 return phonenumbers.parse("+49236360359%s" % self.sip_id)
587
e96e445b
MT
588 def get_phone_numbers(self):
589 ret = []
6ff61434 590
e96e445b
MT
591 for field in ("telephoneNumber", "homePhone", "mobile"):
592 for number in self._get_phone_numbers(field):
593 ret.append(number)
6ff61434 594
e96e445b
MT
595 return ret
596
597 def set_phone_numbers(self, phone_numbers):
598 # Sort phone numbers by landline and mobile
599 _landline_numbers = []
600 _mobile_numbers = []
601
602 for number in phone_numbers:
603 try:
604 number = phonenumbers.parse(number, None)
605 except phonenumbers.phonenumberutil.NumberParseException:
606 continue
607
608 # Convert to string (in E.164 format)
609 s = phonenumbers.format_number(number, phonenumbers.PhoneNumberFormat.E164)
610
611 # Separate mobile numbers
612 if phonenumbers.number_type(number) == phonenumbers.PhoneNumberType.MOBILE:
613 _mobile_numbers.append(s)
614 else:
615 _landline_numbers.append(s)
616
617 # Save
618 self._set_strings("telephoneNumber", _landline_numbers)
619 self._set_strings("mobile", _mobile_numbers)
620
621 phone_numbers = property(get_phone_numbers, set_phone_numbers)
8476e80f
MT
622
623 @property
624 def _all_telephone_numbers(self):
6ccc8acb
MT
625 ret = [ self.sip_id, ]
626
d3208ac7
MT
627 if self.phone_number:
628 s = phonenumbers.format_number(self.phone_number, phonenumbers.PhoneNumberFormat.E164)
629 ret.append(s)
630
6ccc8acb
MT
631 for number in self.phone_numbers:
632 s = phonenumbers.format_number(number, phonenumbers.PhoneNumberFormat.E164)
633 ret.append(s)
634
635 return ret
66862195 636
2cd9af74
MT
637 def avatar_url(self, size=None):
638 if self.backend.debug:
03706893 639 hostname = "http://people.dev.ipfire.org"
2cd9af74 640 else:
03706893 641 hostname = "https://people.ipfire.org"
2cd9af74 642
03706893 643 url = "%s/users/%s.jpg" % (hostname, self.uid)
2cd9af74
MT
644
645 if size:
646 url += "?size=%s" % size
647
648 return url
649
2cd9af74 650 def get_avatar(self, size=None):
e96e445b 651 avatar = self._get_bytes("jpegPhoto")
2cd9af74
MT
652 if not avatar:
653 return
654
655 if not size:
656 return avatar
657
658 return self._resize_avatar(avatar, size)
659
660 def _resize_avatar(self, image, size):
1a226c83
MT
661 image = PIL.Image.open(io.BytesIO(image))
662
663 # Convert RGBA images into RGB because JPEG doesn't support alpha-channels
664 if image.mode == "RGBA":
665 image = image.convert("RGB")
666
22153577
MT
667 # Resize the image to the desired resolution (and make it square)
668 thumbnail = PIL.ImageOps.fit(image, (size, size), PIL.Image.ANTIALIAS)
1a226c83
MT
669
670 with io.BytesIO() as f:
671 # If writing out the image does not work with optimization,
672 # we try to write it out without any optimization.
673 try:
22153577 674 thumbnail.save(f, "JPEG", optimize=True, quality=98)
1a226c83 675 except:
22153577 676 thumbnail.save(f, "JPEG", quality=98)
1a226c83
MT
677
678 return f.getvalue()
2cd9af74 679
5cc10421
MT
680 def upload_avatar(self, avatar):
681 self._set("jpegPhoto", avatar)
682
f4672785
MT
683 # SSH Keys
684
685 @lazy_property
686 def ssh_keys(self):
687 ret = []
688
689 for key in self._get_strings("sshPublicKey"):
690 s = sshpubkeys.SSHKey()
691
692 try:
693 s.parse(key)
694 except (sshpubkeys.InvalidKeyError, NotImplementedError) as e:
695 logging.warning("Could not parse SSH key %s: %s" % (key, e))
696 continue
697
698 ret.append(s)
699
700 return ret
60024cc8 701
44b75370 702 def get_ssh_key_by_hash_sha256(self, hash_sha256):
55b67ca4 703 for key in self.ssh_keys:
44b75370 704 if not key.hash_sha256() == hash_sha256:
55b67ca4
MT
705 continue
706
707 return key
708
0d1fb712
MT
709 def add_ssh_key(self, key):
710 k = sshpubkeys.SSHKey()
711
712 # Try to parse the key
713 k.parse(key)
714
715 # Check for types and sufficient sizes
716 if k.key_type == b"ssh-rsa":
717 if k.bits < 4096:
718 raise sshpubkeys.TooShortKeyError("RSA keys cannot be smaller than 4096 bits")
719
720 elif k.key_type == b"ssh-dss":
721 raise sshpubkeys.InvalidKeyError("DSA keys are not supported")
722
723 # Ignore any duplicates
724 if key in (k.keydata for k in self.ssh_keys):
725 logging.debug("SSH Key has already been added for %s: %s" % (self, key))
726 return
727
cc27cb63
MT
728 # Prepare transaction
729 modlist = []
730
731 # Add object class if user is not in it, yet
732 if not "ldapPublicKey" in self.classes:
733 modlist.append((ldap.MOD_ADD, "objectClass", b"ldapPublicKey"))
734
735 # Add key
736 modlist.append((ldap.MOD_ADD, "sshPublicKey", key.encode()))
737
0d1fb712 738 # Save key to LDAP
cc27cb63 739 self._modify(modlist)
0d1fb712
MT
740
741 # Append to cache
742 self.ssh_keys.append(k)
743
744 def delete_ssh_key(self, key):
745 if not key in (k.keydata for k in self.ssh_keys):
746 return
747
748 # Delete key from LDAP
cc27cb63
MT
749 if len(self.ssh_keys) > 1:
750 self._delete_string("sshPublicKey", key)
751 else:
752 self._modify([
753 (ldap.MOD_DELETE, "objectClass", b"ldapPublicKey"),
754 (ldap.MOD_DELETE, "sshPublicKey", key.encode()),
755 ])
0d1fb712 756
55b67ca4 757
940227cb
MT
758if __name__ == "__main__":
759 a = Accounts()
760
11347e46 761 print(a.list())