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