]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/accounts.py
17 from .decorators
import *
18 from .misc
import Object
20 class Accounts(Object
):
22 # Only return developers (group with ID 1000)
23 accounts
= self
._search
("(&(objectClass=posixAccount)(gidNumber=1000))")
25 return iter(sorted(accounts
))
29 # Connect to LDAP server
30 ldap_uri
= self
.settings
.get("ldap_uri")
31 conn
= ldap
.initialize(ldap_uri
)
33 # Bind with username and password
34 bind_dn
= self
.settings
.get("ldap_bind_dn")
36 bind_pw
= self
.settings
.get("ldap_bind_pw", "")
37 conn
.simple_bind(bind_dn
, bind_pw
)
41 def _query(self
, query
, attrlist
=None, limit
=0):
42 logging
.debug("Performing LDAP query: %s" % query
)
44 search_base
= self
.settings
.get("ldap_search_base")
47 results
= self
.ldap
.search_ext_s(search_base
, ldap
.SCOPE_SUBTREE
,
48 query
, attrlist
=attrlist
, sizelimit
=limit
)
50 # Close current connection
58 def _search(self
, query
, attrlist
=None, limit
=0):
61 for dn
, attrs
in self
._query
(query
, attrlist
=attrlist
, limit
=limit
):
62 account
= Account(self
.backend
, dn
, attrs
)
63 accounts
.append(account
)
67 def search(self
, query
):
68 # Search for exact matches
69 accounts
= self
._search
("(&(objectClass=posixAccount) \
70 (|(uid=%s)(mail=%s)(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
71 % (query
, query
, query
, query
, query
, query
))
73 # Find accounts by name
75 for account
in self
._search
("(&(objectClass=posixAccount)(|(cn=*%s*)(uid=*%s*)))" % (query
, query
)):
76 if not account
in accounts
:
77 accounts
.append(account
)
79 return sorted(accounts
)
81 def _search_one(self
, query
):
82 result
= self
._search
(query
, limit
=1)
83 assert len(result
) <= 1
88 def get_by_uid(self
, uid
):
89 return self
._search
_one
("(&(objectClass=posixAccount)(uid=%s))" % uid
)
91 def get_by_mail(self
, mail
):
92 return self
._search
_one
("(&(objectClass=posixAccount)(mail=%s))" % mail
)
96 def find_account(self
, s
):
97 account
= self
.get_by_uid(s
)
101 return self
.get_by_mail(s
)
103 def get_by_sip_id(self
, sip_id
):
104 return self
._search
_one
("(|(&(objectClass=sipUser)(sipAuthenticationUser=%s)) \
105 (&(objectClass=sipRoutingObject)(sipLocalAddress=%s)))" % (sip_id
, sip_id
))
107 def get_by_phone_number(self
, number
):
108 return self
._search
_one
("(&(objectClass=posixAccount) \
109 (|(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
110 % (number
, number
, number
, number
))
114 def _cleanup_expired_sessions(self
):
115 self
.db
.execute("DELETE FROM sessions WHERE time_expires <= NOW()")
117 def create_session(self
, account
, host
):
118 self
._cleanup
_expired
_sessions
()
120 res
= self
.db
.get("INSERT INTO sessions(host, uid) VALUES(%s, %s) \
121 RETURNING session_id, time_expires", host
, account
.uid
)
123 # Session could not be created
127 logging
.info("Created session %s for %s which expires %s" \
128 % (res
.session_id
, account
, res
.time_expires
))
129 return res
.session_id
, res
.time_expires
131 def destroy_session(self
, session_id
, host
):
132 logging
.info("Destroying session %s" % session_id
)
134 self
.db
.execute("DELETE FROM sessions \
135 WHERE session_id = %s AND host = %s", session_id
, host
)
136 self
._cleanup
_expired
_sessions
()
138 def get_by_session(self
, session_id
, host
):
139 logging
.debug("Looking up session %s" % session_id
)
141 res
= self
.db
.get("SELECT uid FROM sessions WHERE session_id = %s \
142 AND host = %s AND NOW() BETWEEN time_created AND time_expires",
145 # Session does not exist or has expired
149 # Update the session expiration time
150 self
.db
.execute("UPDATE sessions SET time_expires = NOW() + INTERVAL '14 days' \
151 WHERE session_id = %s AND host = %s", session_id
, host
)
153 return self
.get_by_uid(res
.uid
)
156 class Account(Object
):
157 def __init__(self
, backend
, dn
, attrs
=None):
158 Object
.__init
__(self
, backend
)
161 self
.attributes
= attrs
or {}
167 return "<%s %s>" % (self
.__class
__.__name
__, self
.dn
)
169 def __eq__(self
, other
):
170 if isinstance(other
, self
.__class
__):
171 return self
.dn
== other
.dn
173 def __lt__(self
, other
):
174 if isinstance(other
, self
.__class
__):
175 return self
.name
< other
.name
179 return self
.accounts
.ldap
181 def _exists(self
, key
):
190 for value
in self
.attributes
.get(key
, []):
193 def _get_bytes(self
, key
, default
=None):
194 for value
in self
._get
(key
):
199 def _get_strings(self
, key
):
200 for value
in self
._get
(key
):
203 def _get_string(self
, key
, default
=None):
204 for value
in self
._get
_strings
(key
):
209 def _get_phone_numbers(self
, key
):
210 for value
in self
._get
_strings
(key
):
211 yield phonenumbers
.parse(value
, None)
213 def _modify(self
, modlist
):
214 logging
.debug("Modifying %s: %s" % (self
.dn
, modlist
))
216 # Run modify operation
217 self
.ldap
.modify_s(self
.dn
, modlist
)
219 def _set(self
, key
, values
):
220 current
= self
._get
(key
)
222 # Don't do anything if nothing has changed
223 if list(current
) == values
:
226 # Remove all old values and add all new ones
229 if self
._exists
(key
):
230 modlist
.append((ldap
.MOD_DELETE
, key
, None))
233 modlist
.append((ldap
.MOD_ADD
, key
, values
))
235 # Run modify operation
236 self
._modify
(modlist
)
239 self
.attributes
.update({ key
: values
})
241 def _set_bytes(self
, key
, values
):
242 return self
._set
(key
, values
)
244 def _set_strings(self
, key
, values
):
245 return self
._set
(key
, [e
.encode() for e
in values
])
247 def _set_string(self
, key
, value
):
248 return self
._set
_strings
(key
, [value
,])
250 def passwd(self
, new_password
):
254 self
.ldap
.passwd_s(self
.dn
, None, new_password
)
256 def check_password(self
, password
):
258 Bind to the server with given credentials and return
259 true if password is corrent and false if not.
261 Raises exceptions from the server on any other errors.
263 logging
.debug("Checking credentials for %s" % self
.dn
)
265 # Create a new LDAP connection
266 ldap_uri
= self
.backend
.settings
.get("ldap_uri")
267 conn
= ldap
.initialize(ldap_uri
)
270 conn
.simple_bind_s(self
.dn
, password
.encode("utf-8"))
271 except ldap
.INVALID_CREDENTIALS
:
272 logging
.debug("Account credentials are invalid for %s" % self
)
275 logging
.info("Successfully authenticated %s" % self
)
280 return "wheel" in self
.groups
282 def is_talk_enabled(self
):
283 return "sipUser" in self
.classes
or "sipRoutingObject" in self
.classes \
284 or self
.telephone_numbers
or self
.address
286 def can_be_managed_by(self
, account
):
288 Returns True if account is allowed to manage this account
290 # Admins can manage all accounts
291 if account
.is_admin():
294 # Users can manage themselves
295 return self
== account
299 return self
._get
_strings
("objectClass")
303 return self
._get
_string
("uid")
307 return self
._get
_string
("cn")
311 def get_first_name(self
):
312 return self
._get
_string
("givenName")
314 def set_first_name(self
, first_name
):
315 self
._set
_string
("givenName", first_name
)
318 self
._set
_string
("cn", "%s %s" % (first_name
, self
.last_name
))
320 first_name
= property(get_first_name
, set_first_name
)
324 def get_last_name(self
):
325 return self
._get
_string
("sn")
327 def set_last_name(self
, last_name
):
328 self
._set
_string
("sn", last_name
)
331 self
._set
_string
("cn", "%s %s" % (self
.first_name
, last_name
))
333 last_name
= property(get_last_name
, set_last_name
)
339 res
= self
.accounts
._query
("(&(objectClass=posixGroup) \
340 (memberUid=%s))" % self
.uid
, ["cn"])
342 for dn
, attrs
in res
:
343 cns
= attrs
.get("cn")
345 groups
.append(cns
[0].decode())
351 def get_address(self
):
352 address
= self
._get
_string
("homePostalAddress")
355 return (line
.strip() for line
in address
.split(","))
359 def set_address(self
, address
):
360 data
= ", ".join(address
.splitlines())
362 self
._set
_bytes
("homePostalAddress", data
.encode())
364 address
= property(get_address
, set_address
)
368 name
= self
.name
.lower()
369 name
= name
.replace(" ", ".")
370 name
= name
.replace("Ä", "Ae")
371 name
= name
.replace("Ö", "Oe")
372 name
= name
.replace("Ü", "Ue")
373 name
= name
.replace("ä", "ae")
374 name
= name
.replace("ö", "oe")
375 name
= name
.replace("ü", "ue")
377 for mail
in self
.attributes
.get("mail", []):
378 if mail
.decode().startswith("%s@ipfire.org" % name
):
381 # If everything else fails, we will go with the UID
382 return "%s@ipfire.org" % self
.uid
384 # Mail Routing Address
386 def get_mail_routing_address(self
):
387 return self
._get
_string
("mailRoutingAddress", None)
389 def set_mail_routing_address(self
, address
):
390 self
._set
_string
("mailRoutingAddress", address
)
392 mail_routing_address
= property(get_mail_routing_address
, set_mail_routing_address
)
396 if "sipUser" in self
.classes
:
397 return self
._get
_string
("sipAuthenticationUser")
399 if "sipRoutingObject" in self
.classes
:
400 return self
._get
_string
("sipLocalAddress")
403 def sip_password(self
):
404 return self
._get
_string
("sipPassword")
407 def _generate_sip_password():
408 return util
.random_string(8)
412 return "%s@ipfire.org" % self
.sip_id
414 def uses_sip_forwarding(self
):
415 if self
.sip_routing_address
:
422 def get_sip_routing_address(self
):
423 if "sipRoutingObject" in self
.classes
:
424 return self
._get
_string
("sipRoutingAddress")
426 def set_sip_routing_address(self
, address
):
430 # Don't do anything if nothing has changed
431 if self
.get_sip_routing_address() == address
:
436 # This is no longer a SIP user any more
437 (ldap
.MOD_DELETE
, "objectClass", b
"sipUser"),
438 (ldap
.MOD_DELETE
, "sipAuthenticationUser", None),
439 (ldap
.MOD_DELETE
, "sipPassword", None),
441 (ldap
.MOD_ADD
, "objectClass", b
"sipRoutingObject"),
442 (ldap
.MOD_ADD
, "sipLocalAddress", self
.sip_id
.encode()),
443 (ldap
.MOD_ADD
, "sipRoutingAddress", address
.encode()),
447 (ldap
.MOD_DELETE
, "objectClass", b
"sipRoutingObject"),
448 (ldap
.MOD_DELETE
, "sipLocalAddress", None),
449 (ldap
.MOD_DELETE
, "sipRoutingAddress", None),
451 (ldap
.MOD_ADD
, "objectClass", b
"sipUser"),
452 (ldap
.MOD_ADD
, "sipAuthenticationUser", self
.sip_id
.encode()),
453 (ldap
.MOD_ADD
, "sipPassword", self
._generate
_sip
_password
().encode()),
457 self
._modify
(modlist
)
459 # XXX Cache is invalid here
461 sip_routing_address
= property(get_sip_routing_address
, set_sip_routing_address
)
464 def sip_registrations(self
):
465 sip_registrations
= []
467 for reg
in self
.backend
.talk
.freeswitch
.get_sip_registrations(self
.sip_url
):
470 sip_registrations
.append(reg
)
472 return sip_registrations
475 def sip_channels(self
):
476 return self
.backend
.talk
.freeswitch
.get_sip_channels(self
)
478 def get_cdr(self
, date
=None, limit
=None):
479 return self
.backend
.talk
.freeswitch
.get_cdr_by_account(self
, date
=date
, limit
=limit
)
483 def get_phone_numbers(self
):
486 for field
in ("telephoneNumber", "homePhone", "mobile"):
487 for number
in self
._get
_phone
_numbers
(field
):
492 def set_phone_numbers(self
, phone_numbers
):
493 # Sort phone numbers by landline and mobile
494 _landline_numbers
= []
497 for number
in phone_numbers
:
499 number
= phonenumbers
.parse(number
, None)
500 except phonenumbers
.phonenumberutil
.NumberParseException
:
503 # Convert to string (in E.164 format)
504 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
506 # Separate mobile numbers
507 if phonenumbers
.number_type(number
) == phonenumbers
.PhoneNumberType
.MOBILE
:
508 _mobile_numbers
.append(s
)
510 _landline_numbers
.append(s
)
513 self
._set
_strings
("telephoneNumber", _landline_numbers
)
514 self
._set
_strings
("mobile", _mobile_numbers
)
516 phone_numbers
= property(get_phone_numbers
, set_phone_numbers
)
519 def _all_telephone_numbers(self
):
520 ret
= [ self
.sip_id
, ]
522 for number
in self
.phone_numbers
:
523 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
528 def avatar_url(self
, size
=None):
529 if self
.backend
.debug
:
530 hostname
= "http://people.dev.ipfire.org"
532 hostname
= "https://people.ipfire.org"
534 url
= "%s/users/%s.jpg" % (hostname
, self
.uid
)
537 url
+= "?size=%s" % size
541 def get_avatar(self
, size
=None):
542 avatar
= self
._get
_bytes
("jpegPhoto")
549 return self
._resize
_avatar
(avatar
, size
)
551 def _resize_avatar(self
, image
, size
):
552 image
= PIL
.Image
.open(io
.BytesIO(image
))
554 # Convert RGBA images into RGB because JPEG doesn't support alpha-channels
555 if image
.mode
== "RGBA":
556 image
= image
.convert("RGB")
558 # Resize the image to the desired resolution (and make it square)
559 thumbnail
= PIL
.ImageOps
.fit(image
, (size
, size
), PIL
.Image
.ANTIALIAS
)
561 with io
.BytesIO() as f
:
562 # If writing out the image does not work with optimization,
563 # we try to write it out without any optimization.
565 thumbnail
.save(f
, "JPEG", optimize
=True, quality
=98)
567 thumbnail
.save(f
, "JPEG", quality
=98)
571 def upload_avatar(self
, avatar
):
572 self
._set
("jpegPhoto", avatar
)
580 for key
in self
._get
_strings
("sshPublicKey"):
581 s
= sshpubkeys
.SSHKey()
585 except (sshpubkeys
.InvalidKeyError
, NotImplementedError) as e
:
586 logging
.warning("Could not parse SSH key %s: %s" % (key
, e
))
593 if __name__
== "__main__":