11 from .decorators
import *
12 from .misc
import Object
14 class Accounts(Object
):
16 # Only return developers (group with ID 1000)
17 accounts
= self
._search
("(&(objectClass=posixAccount)(gidNumber=1000))")
19 return iter(sorted(accounts
))
23 # Connect to LDAP server
24 ldap_uri
= self
.settings
.get("ldap_uri")
25 conn
= ldap
.initialize(ldap_uri
)
27 # Bind with username and password
28 bind_dn
= self
.settings
.get("ldap_bind_dn")
30 bind_pw
= self
.settings
.get("ldap_bind_pw", "")
31 conn
.simple_bind(bind_dn
, bind_pw
)
35 def _query(self
, query
, attrlist
=None, limit
=0):
36 logging
.debug("Performing LDAP query: %s" % query
)
38 search_base
= self
.settings
.get("ldap_search_base")
41 results
= self
.ldap
.search_ext_s(search_base
, ldap
.SCOPE_SUBTREE
,
42 query
, attrlist
=attrlist
, sizelimit
=limit
)
44 # Close current connection
52 def _search(self
, query
, attrlist
=None, limit
=0):
55 for dn
, attrs
in self
._query
(query
, attrlist
=attrlist
, limit
=limit
):
56 account
= Account(self
.backend
, dn
, attrs
)
57 accounts
.append(account
)
61 def search(self
, query
):
62 # Search for exact matches
63 accounts
= self
._search
("(&(objectClass=posixAccount) \
64 (|(uid=%s)(mail=%s)(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
65 % (query
, query
, query
, query
, query
, query
))
67 # Find accounts by name
69 for account
in self
._search
("(&(objectClass=posixAccount)(cn=*%s*))" % query
):
70 if not account
in accounts
:
71 accounts
.append(account
)
73 return sorted(accounts
)
75 def _search_one(self
, query
):
76 result
= self
._search
(query
, limit
=1)
77 assert len(result
) <= 1
82 def get_by_uid(self
, uid
):
83 return self
._search
_one
("(&(objectClass=posixAccount)(uid=%s))" % uid
)
85 def get_by_mail(self
, mail
):
86 return self
._search
_one
("(&(objectClass=posixAccount)(mail=%s))" % mail
)
90 def find_account(self
, s
):
91 account
= self
.get_by_uid(s
)
95 return self
.get_by_mail(s
)
97 def get_by_sip_id(self
, sip_id
):
98 return self
._search
_one
("(|(&(objectClass=sipUser)(sipAuthenticationUser=%s)) \
99 (&(objectClass=sipRoutingObject)(sipLocalAddress=%s)))" % (sip_id
, sip_id
))
101 def get_by_phone_number(self
, number
):
102 return self
._search
_one
("(&(objectClass=posixAccount) \
103 (|(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
104 % (number
, number
, number
, number
))
108 def _cleanup_expired_sessions(self
):
109 self
.db
.execute("DELETE FROM sessions WHERE time_expires <= NOW()")
111 def create_session(self
, account
, host
):
112 self
._cleanup
_expired
_sessions
()
114 res
= self
.db
.get("INSERT INTO sessions(host, uid) VALUES(%s, %s) \
115 RETURNING session_id, time_expires", host
, account
.uid
)
117 # Session could not be created
121 logging
.info("Created session %s for %s which expires %s" \
122 % (res
.session_id
, account
, res
.time_expires
))
123 return res
.session_id
, res
.time_expires
125 def destroy_session(self
, session_id
, host
):
126 logging
.info("Destroying session %s" % session_id
)
128 self
.db
.execute("DELETE FROM sessions \
129 WHERE session_id = %s AND host = %s", session_id
, host
)
130 self
._cleanup
_expired
_sessions
()
132 def get_by_session(self
, session_id
, host
):
133 logging
.debug("Looking up session %s" % session_id
)
135 res
= self
.db
.get("SELECT uid FROM sessions WHERE session_id = %s \
136 AND host = %s AND NOW() BETWEEN time_created AND time_expires",
139 # Session does not exist or has expired
143 # Update the session expiration time
144 self
.db
.execute("UPDATE sessions SET time_expires = NOW() + INTERVAL '14 days' \
145 WHERE session_id = %s AND host = %s", session_id
, host
)
147 return self
.get_by_uid(res
.uid
)
150 class Account(Object
):
151 def __init__(self
, backend
, dn
, attrs
=None):
152 Object
.__init
__(self
, backend
)
155 self
.__attrs
= attrs
or {}
161 return "<%s %s>" % (self
.__class
__.__name
__, self
.dn
)
163 def __eq__(self
, other
):
164 if isinstance(other
, self
.__class
__):
165 return self
.dn
== other
.dn
167 def __lt__(self
, other
):
168 if isinstance(other
, self
.__class
__):
169 return self
.name
< other
.name
173 return self
.accounts
.ldap
176 def attributes(self
):
179 def _get_first_attribute(self
, attr
, default
=None):
180 if attr
not in self
.attributes
:
183 res
= self
.attributes
.get(attr
, [])
189 attribute
= self
.attributes
[key
]
191 raise AttributeError(key
)
193 if len(attribute
) == 1:
198 def check_password(self
, password
):
200 Bind to the server with given credentials and return
201 true if password is corrent and false if not.
203 Raises exceptions from the server on any other errors.
206 logging
.debug("Checking credentials for %s" % self
.dn
)
208 self
.ldap
.simple_bind_s(self
.dn
, password
.encode("utf-8"))
209 except ldap
.INVALID_CREDENTIALS
:
210 logging
.debug("Account credentials are invalid.")
213 logging
.debug("Successfully authenticated.")
217 return "wheel" in self
.groups
219 def is_talk_enabled(self
):
220 return "sipUser" in self
.classes
or "sipRoutingObject" in self
.classes \
221 or self
.telephone_numbers
or self
.address
225 return (x
.decode() for x
in self
.attributes
.get("objectClass", []))
229 return self
._get
_first
_attribute
("uid").decode()
233 return self
._get
_first
_attribute
("cn").decode()
236 def first_name(self
):
237 return self
._get
_first
_attribute
("givenName").decode()
243 res
= self
.accounts
._query
("(&(objectClass=posixGroup) \
244 (memberUid=%s))" % self
.uid
, ["cn"])
246 for dn
, attrs
in res
:
247 cns
= attrs
.get("cn")
249 groups
.append(cns
[0].decode())
255 address
= self
._get
_first
_attribute
("homePostalAddress", "".encode()).decode()
256 address
= address
.replace(", ", "\n")
262 name
= self
.name
.lower()
263 name
= name
.replace(" ", ".")
264 name
= name
.replace("Ä", "Ae")
265 name
= name
.replace("Ö", "Oe")
266 name
= name
.replace("Ü", "Ue")
267 name
= name
.replace("ä", "ae")
268 name
= name
.replace("ö", "oe")
269 name
= name
.replace("ü", "ue")
271 for mail
in self
.attributes
.get("mail", []):
272 if mail
.decode().startswith("%s@ipfire.org" % name
):
275 # If everything else fails, we will go with the UID
276 return "%s@ipfire.org" % self
.uid
280 if "sipUser" in self
.classes
:
281 return self
._get
_first
_attribute
("sipAuthenticationUser").decode()
283 if "sipRoutingObject" in self
.classes
:
284 return self
._get
_first
_attribute
("sipLocalAddress").decode()
287 def sip_password(self
):
288 return self
._get
_first
_attribute
("sipPassword").decode()
292 return "%s@ipfire.org" % self
.sip_id
294 def uses_sip_forwarding(self
):
295 if self
.sip_routing_url
:
301 def sip_routing_url(self
):
302 if "sipRoutingObject" in self
.classes
:
303 return self
._get
_first
_attribute
("sipRoutingAddress").decode()
306 def sip_registrations(self
):
307 sip_registrations
= []
309 for reg
in self
.backend
.talk
.freeswitch
.get_sip_registrations(self
.sip_url
):
312 sip_registrations
.append(reg
)
314 return sip_registrations
316 def get_cdr(self
, limit
=None):
317 return self
.backend
.talk
.freeswitch
.get_cdr_by_account(self
, limit
=limit
)
320 def telephone_numbers(self
):
321 return self
._telephone
_numbers
+ self
.mobile_telephone_numbers \
322 + self
.home_telephone_numbers
325 def _telephone_numbers(self
):
326 return self
.attributes
.get("telephoneNumber") or []
329 def home_telephone_numbers(self
):
330 return self
.attributes
.get("homePhone") or []
333 def mobile_telephone_numbers(self
):
334 return self
.attributes
.get("mobile") or []
336 def avatar_url(self
, size
=None):
337 if self
.backend
.debug
:
338 hostname
= "http://people.dev.ipfire.org"
340 hostname
= "https://people.ipfire.org"
342 url
= "%s/users/%s.jpg" % (hostname
, self
.uid
)
345 url
+= "?size=%s" % size
349 def get_avatar(self
, size
=None):
350 avatar
= self
._get
_first
_attribute
("jpegPhoto")
357 return self
._resize
_avatar
(avatar
, size
)
359 def _resize_avatar(self
, image
, size
):
360 image
= PIL
.Image
.open(io
.BytesIO(image
))
362 # Convert RGBA images into RGB because JPEG doesn't support alpha-channels
363 if image
.mode
== "RGBA":
364 image
= image
.convert("RGB")
366 # Resize the image to the desired resolution
367 image
.thumbnail((size
, size
), PIL
.Image
.ANTIALIAS
)
369 with io
.BytesIO() as f
:
370 # If writing out the image does not work with optimization,
371 # we try to write it out without any optimization.
373 image
.save(f
, "JPEG", optimize
=True, quality
=98)
375 image
.save(f
, "JPEG", quality
=98)
380 if __name__
== "__main__":