]> git.ipfire.org Git - ipfire.org.git/blame - src/backend/accounts.py
Introduce autotools
[ipfire.org.git] / src / backend / accounts.py
CommitLineData
940227cb 1#!/usr/bin/python
78fdedae 2# encoding: utf-8
940227cb 3
2cd9af74
MT
4import PIL
5import StringIO
940227cb
MT
6import hashlib
7import ldap
27066195 8import logging
940227cb
MT
9import urllib
10
a6dc0bad 11from misc import Object
940227cb 12
a6dc0bad 13class Accounts(Object):
940227cb 14 @property
66862195
MT
15 def ldap(self):
16 if not hasattr(self, "_ldap"):
17 # Connect to LDAP server
27066195 18 ldap_uri = self.settings.get("ldap_uri")
66862195 19 self._ldap = ldap.initialize(ldap_uri)
940227cb 20
66862195 21 # Bind with username and password
27066195 22 bind_dn = self.settings.get("ldap_bind_dn")
940227cb 23 if bind_dn:
9068dba1 24 bind_pw = self.settings.get("ldap_bind_pw", "")
66862195
MT
25 self._ldap.simple_bind(bind_dn, bind_pw)
26
27 return self._ldap
940227cb 28
66862195
MT
29 def _search(self, query, attrlist=None, limit=0):
30 logging.debug("Performing LDAP query: %s" % query)
940227cb 31
66862195 32 search_base = self.settings.get("ldap_search_base")
a395634c
MT
33
34 try:
35 results = self.ldap.search_ext_s(search_base, ldap.SCOPE_SUBTREE,
36 query, attrlist=attrlist, sizelimit=limit)
37 except:
38 # Close current connection
39 del self._ldap
40
41 raise
940227cb 42
66862195 43 return results
940227cb 44
66862195
MT
45 def search(self, query, limit=0):
46 results = self._search(query, limit=limit)
940227cb 47
66862195 48 accounts = []
940227cb 49 for dn, attrs in results:
66862195
MT
50 account = Account(self.backend, dn, attrs)
51 accounts.append(account)
52
53 return sorted(accounts)
54
55 def search_one(self, query):
56 result = self.search(query, limit=1)
57 assert len(result) <= 1
58
59 if result:
60 return result[0]
61
62 def get_all(self):
63 # Only return developers (group with ID 500)
64 return self.search("(&(objectClass=posixAccount)(gidNumber=500))")
65
66 list = get_all
67
68 def get_by_uid(self, uid):
69 return self.search_one("(&(objectClass=posixAccount)(uid=%s))" % uid)
70
71 def get_by_mail(self, mail):
72 return self.search_one("(&(objectClass=posixAccount)(mail=%s))" % mail)
73
74 find = get_by_uid
75
76 def find_account(self, s):
77 account = self.get_by_uid(s)
78 if account:
79 return account
80
81 return self.get_by_mail(s)
940227cb 82
66862195
MT
83 def get_by_sip_id(self, sip_id):
84 return self.search_one("(|(&(objectClass=sipUser)(sipAuthenticationUser=%s)) \
85 (&(objectClass=sipRoutingObject)(sipLocalAddress=%s)))" % (sip_id, sip_id))
940227cb 86
66862195 87 # Session stuff
940227cb 88
66862195
MT
89 def _cleanup_expired_sessions(self):
90 self.db.execute("DELETE FROM sessions WHERE time_expires <= NOW()")
940227cb 91
66862195
MT
92 def create_session(self, account, host):
93 self._cleanup_expired_sessions()
940227cb 94
66862195
MT
95 res = self.db.get("INSERT INTO sessions(host, uid) VALUES(%s, %s) \
96 RETURNING session_id, time_expires", host, account.uid)
97
98 # Session could not be created
99 if not res:
100 return None, None
101
102 logging.info("Created session %s for %s which expires %s" \
103 % (res.session_id, account, res.time_expires))
104 return res.session_id, res.time_expires
105
106 def destroy_session(self, session_id, host):
107 logging.info("Destroying session %s" % session_id)
108
109 self.db.execute("DELETE FROM sessions \
110 WHERE session_id = %s AND host = %s", session_id, host)
111 self._cleanup_expired_sessions()
112
113 def get_by_session(self, session_id, host):
114 logging.debug("Looking up session %s" % session_id)
115
116 res = self.db.get("SELECT uid FROM sessions WHERE session_id = %s \
117 AND host = %s AND NOW() BETWEEN time_created AND time_expires",
118 session_id, host)
119
120 # Session does not exist or has expired
121 if not res:
122 return
123
124 # Update the session expiration time
125 self.db.execute("UPDATE sessions SET time_expires = NOW() + INTERVAL '14 days' \
126 WHERE session_id = %s AND host = %s", session_id, host)
127
128 return self.get_by_uid(res.uid)
129
940227cb 130
a6dc0bad 131class Account(Object):
66862195 132 def __init__(self, backend, dn, attrs=None):
a6dc0bad 133 Object.__init__(self, backend)
940227cb
MT
134 self.dn = dn
135
66862195 136 self.__attrs = attrs or {}
940227cb
MT
137
138 def __repr__(self):
139 return "<%s %s>" % (self.__class__.__name__, self.dn)
140
141 def __cmp__(self, other):
66862195 142 return cmp(self.name, other.name)
940227cb
MT
143
144 @property
66862195
MT
145 def ldap(self):
146 return self.accounts.ldap
940227cb
MT
147
148 @property
149 def attributes(self):
66862195 150 return self.__attrs
940227cb 151
66862195
MT
152 def _get_first_attribute(self, attr, default=None):
153 if not self.attributes.has_key(attr):
154 return default
940227cb 155
66862195
MT
156 res = self.attributes.get(attr, [])
157 if res:
158 return res[0]
940227cb
MT
159
160 def get(self, key):
161 try:
162 attribute = self.attributes[key]
163 except KeyError:
164 raise AttributeError(key)
165
166 if len(attribute) == 1:
167 return attribute[0]
168
169 return attribute
170
940227cb
MT
171 def check_password(self, password):
172 """
173 Bind to the server with given credentials and return
174 true if password is corrent and false if not.
175
176 Raises exceptions from the server on any other errors.
177 """
178
179 logging.debug("Checking credentials for %s" % self.dn)
180 try:
66862195 181 self.ldap.simple_bind_s(self.dn, password.encode("utf-8"))
940227cb 182 except ldap.INVALID_CREDENTIALS:
60024cc8 183 logging.debug("Account credentials are invalid.")
940227cb
MT
184 return False
185
60024cc8 186 logging.debug("Successfully authenticated.")
940227cb
MT
187 return True
188
940227cb 189 def is_admin(self):
d82bc8e3 190 return "wheel" in self.groups
66862195
MT
191
192 def is_talk_enabled(self):
06c1d39c
MT
193 return "sipUser" in self.classes or "sipRoutingObject" in self.classes \
194 or self.telephone_numbers or self.address
66862195
MT
195
196 @property
197 def classes(self):
198 return self.attributes.get("objectClass", [])
199
200 @property
201 def uid(self):
202 return self._get_first_attribute("uid")
940227cb 203
a6dc0bad
MT
204 @property
205 def name(self):
66862195
MT
206 return self._get_first_attribute("cn")
207
208 @property
209 def first_name(self):
210 return self._get_first_attribute("givenName")
211
212 @property
213 def groups(self):
214 if not hasattr(self, "_groups"):
215 self._groups = []
216
217 res = self.accounts._search("(&(objectClass=posixGroup) \
218 (memberUid=%s))" % self.uid, ["cn"])
219
220 for dn, attrs in res:
221 cns = attrs.get("cn")
222 if cns:
223 self._groups.append(cns[0])
224
225 return self._groups
226
227 @property
228 def address(self):
229 address = self._get_first_attribute("homePostalAddress", "")
230 address = address.replace(", ", "\n")
231
232 return address
a6dc0bad 233
940227cb
MT
234 @property
235 def email(self):
66862195 236 name = self.name.lower()
940227cb 237 name = name.replace(" ", ".")
78fdedae
MT
238 name = name.replace("Ä", "Ae")
239 name = name.replace("Ö", "Oe")
240 name = name.replace("Ü", "Ue")
241 name = name.replace("ä", "ae")
242 name = name.replace("ö", "oe")
243 name = name.replace("ü", "ue")
940227cb 244
66862195 245 for mail in self.attributes.get("mail", []):
cc3b928d 246 if mail.startswith("%s@ipfire.org" % name):
940227cb
MT
247 return mail
248
2cd9af74
MT
249 # If everything else fails, we will go with the UID
250 return "%s@ipfire.org" % self.uid
940227cb 251
66862195
MT
252 @property
253 def sip_id(self):
254 if "sipUser" in self.classes:
255 return self._get_first_attribute("sipAuthenticationUser")
256
257 if "sipRoutingObject" in self.classes:
258 return self._get_first_attribute("sipLocalAddress")
259
2f51147a
MT
260 @property
261 def sip_password(self):
262 return self._get_first_attribute("sipPassword")
263
66862195
MT
264 @property
265 def sip_url(self):
266 return "%s@ipfire.org" % self.sip_id
267
268 def uses_sip_forwarding(self):
269 if self.sip_routing_url:
270 return True
271
272 return False
273
274 @property
275 def sip_routing_url(self):
276 if "sipRoutingObject" in self.classes:
277 return self._get_first_attribute("sipRoutingAddress")
278
279 def sip_is_online(self):
280 assert self.sip_id
281
282 if not hasattr(self, "_is_online"):
283 self._is_online = self.backend.talk.user_is_online(self.sip_id)
284
285 return self._is_online
286
287 @property
288 def telephone_numbers(self):
6ff61434
MT
289 return self._telephone_numbers + self.mobile_telephone_numbers \
290 + self.home_telephone_numbers
291
292 @property
293 def _telephone_numbers(self):
294 return self.attributes.get("telephoneNumber") or []
295
296 @property
297 def home_telephone_numbers(self):
298 return self.attributes.get("homePhone") or []
299
300 @property
301 def mobile_telephone_numbers(self):
302 return self.attributes.get("mobile") or []
66862195 303
2cd9af74
MT
304 def avatar_url(self, size=None):
305 if self.backend.debug:
306 hostname = "accounts.dev.ipfire.org"
307 else:
308 hostname = "accounts.ipfire.org"
309
4da2cb99 310 url = "https://%s/avatar/%s.jpg" % (hostname, self.uid)
2cd9af74
MT
311
312 if size:
313 url += "?size=%s" % size
314
315 return url
316
317 gravatar_icon = avatar_url
318
319 def get_avatar(self, size=None):
320 avatar = self._get_first_attribute("jpegPhoto")
321 if not avatar:
322 return
323
324 if not size:
325 return avatar
326
327 return self._resize_avatar(avatar, size)
328
329 def _resize_avatar(self, image, size):
330 image = StringIO.StringIO(image)
331 image = PIL.Image.open(image)
332
333 # Resize the image to the desired resolution
334 image.thumbnail((size, size), PIL.Image.ANTIALIAS)
335
336 f = StringIO.StringIO()
337
338 # If writing out the image does not work with optimization,
339 # we try to write it out without any optimization.
340 try:
341 image.save(f, "JPEG", optimize=True)
342 except:
343 image.save(f, "JPEG")
344
345 return f.getvalue()
346
347 def get_gravatar_url(self, size=128):
348 try:
349 gravatar_email = self.email.lower()
350 except:
351 gravatar_email = "nobody@ipfire.org"
352
353 # construct the url
4da2cb99 354 gravatar_url = "https://www.gravatar.com/avatar/" + \
2cd9af74
MT
355 hashlib.md5(gravatar_email).hexdigest() + "?"
356 gravatar_url += urllib.urlencode({'d': "mm", 's': str(size)})
357
358 return gravatar_url
359
60024cc8 360
940227cb
MT
361if __name__ == "__main__":
362 a = Accounts()
363
364 print a.list()