]> git.ipfire.org Git - ipfire.org.git/blame - src/backend/accounts.py
blog: Remove unused CSS
[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
541c952b
MT
141 def __eq__(self, other):
142 if isinstance(other, self.__class__):
143 return self.dn == other.dn
144
145 def __lt__(self, other):
146 if isinstance(other, self.__class__):
147 return self.name < other.name
940227cb
MT
148
149 @property
66862195
MT
150 def ldap(self):
151 return self.accounts.ldap
940227cb
MT
152
153 @property
154 def attributes(self):
66862195 155 return self.__attrs
940227cb 156
66862195
MT
157 def _get_first_attribute(self, attr, default=None):
158 if not self.attributes.has_key(attr):
159 return default
940227cb 160
66862195
MT
161 res = self.attributes.get(attr, [])
162 if res:
163 return res[0]
940227cb
MT
164
165 def get(self, key):
166 try:
167 attribute = self.attributes[key]
168 except KeyError:
169 raise AttributeError(key)
170
171 if len(attribute) == 1:
172 return attribute[0]
173
174 return attribute
175
940227cb
MT
176 def check_password(self, password):
177 """
178 Bind to the server with given credentials and return
179 true if password is corrent and false if not.
180
181 Raises exceptions from the server on any other errors.
182 """
183
184 logging.debug("Checking credentials for %s" % self.dn)
185 try:
66862195 186 self.ldap.simple_bind_s(self.dn, password.encode("utf-8"))
940227cb 187 except ldap.INVALID_CREDENTIALS:
60024cc8 188 logging.debug("Account credentials are invalid.")
940227cb
MT
189 return False
190
60024cc8 191 logging.debug("Successfully authenticated.")
940227cb
MT
192 return True
193
940227cb 194 def is_admin(self):
d82bc8e3 195 return "wheel" in self.groups
66862195
MT
196
197 def is_talk_enabled(self):
06c1d39c
MT
198 return "sipUser" in self.classes or "sipRoutingObject" in self.classes \
199 or self.telephone_numbers or self.address
66862195
MT
200
201 @property
202 def classes(self):
203 return self.attributes.get("objectClass", [])
204
205 @property
206 def uid(self):
207 return self._get_first_attribute("uid")
940227cb 208
a6dc0bad
MT
209 @property
210 def name(self):
66862195
MT
211 return self._get_first_attribute("cn")
212
213 @property
214 def first_name(self):
215 return self._get_first_attribute("givenName")
216
217 @property
218 def groups(self):
219 if not hasattr(self, "_groups"):
220 self._groups = []
221
222 res = self.accounts._search("(&(objectClass=posixGroup) \
223 (memberUid=%s))" % self.uid, ["cn"])
224
225 for dn, attrs in res:
226 cns = attrs.get("cn")
227 if cns:
228 self._groups.append(cns[0])
229
230 return self._groups
231
232 @property
233 def address(self):
234 address = self._get_first_attribute("homePostalAddress", "")
235 address = address.replace(", ", "\n")
236
237 return address
a6dc0bad 238
940227cb
MT
239 @property
240 def email(self):
66862195 241 name = self.name.lower()
940227cb 242 name = name.replace(" ", ".")
78fdedae
MT
243 name = name.replace("Ä", "Ae")
244 name = name.replace("Ö", "Oe")
245 name = name.replace("Ü", "Ue")
246 name = name.replace("ä", "ae")
247 name = name.replace("ö", "oe")
248 name = name.replace("ü", "ue")
940227cb 249
66862195 250 for mail in self.attributes.get("mail", []):
cc3b928d 251 if mail.startswith("%s@ipfire.org" % name):
940227cb
MT
252 return mail
253
2cd9af74
MT
254 # If everything else fails, we will go with the UID
255 return "%s@ipfire.org" % self.uid
940227cb 256
66862195
MT
257 @property
258 def sip_id(self):
259 if "sipUser" in self.classes:
260 return self._get_first_attribute("sipAuthenticationUser")
261
262 if "sipRoutingObject" in self.classes:
263 return self._get_first_attribute("sipLocalAddress")
264
2f51147a
MT
265 @property
266 def sip_password(self):
267 return self._get_first_attribute("sipPassword")
268
66862195
MT
269 @property
270 def sip_url(self):
271 return "%s@ipfire.org" % self.sip_id
272
273 def uses_sip_forwarding(self):
274 if self.sip_routing_url:
275 return True
276
277 return False
278
279 @property
280 def sip_routing_url(self):
281 if "sipRoutingObject" in self.classes:
282 return self._get_first_attribute("sipRoutingAddress")
283
284 def sip_is_online(self):
285 assert self.sip_id
286
287 if not hasattr(self, "_is_online"):
288 self._is_online = self.backend.talk.user_is_online(self.sip_id)
289
290 return self._is_online
291
292 @property
293 def telephone_numbers(self):
6ff61434
MT
294 return self._telephone_numbers + self.mobile_telephone_numbers \
295 + self.home_telephone_numbers
296
297 @property
298 def _telephone_numbers(self):
299 return self.attributes.get("telephoneNumber") or []
300
301 @property
302 def home_telephone_numbers(self):
303 return self.attributes.get("homePhone") or []
304
305 @property
306 def mobile_telephone_numbers(self):
307 return self.attributes.get("mobile") or []
66862195 308
2cd9af74
MT
309 def avatar_url(self, size=None):
310 if self.backend.debug:
311 hostname = "accounts.dev.ipfire.org"
312 else:
313 hostname = "accounts.ipfire.org"
314
4da2cb99 315 url = "https://%s/avatar/%s.jpg" % (hostname, self.uid)
2cd9af74
MT
316
317 if size:
318 url += "?size=%s" % size
319
320 return url
321
322 gravatar_icon = avatar_url
323
324 def get_avatar(self, size=None):
325 avatar = self._get_first_attribute("jpegPhoto")
326 if not avatar:
327 return
328
329 if not size:
330 return avatar
331
332 return self._resize_avatar(avatar, size)
333
334 def _resize_avatar(self, image, size):
335 image = StringIO.StringIO(image)
336 image = PIL.Image.open(image)
337
338 # Resize the image to the desired resolution
339 image.thumbnail((size, size), PIL.Image.ANTIALIAS)
340
341 f = StringIO.StringIO()
342
343 # If writing out the image does not work with optimization,
344 # we try to write it out without any optimization.
345 try:
346 image.save(f, "JPEG", optimize=True)
347 except:
348 image.save(f, "JPEG")
349
350 return f.getvalue()
351
352 def get_gravatar_url(self, size=128):
353 try:
354 gravatar_email = self.email.lower()
355 except:
356 gravatar_email = "nobody@ipfire.org"
357
358 # construct the url
4da2cb99 359 gravatar_url = "https://www.gravatar.com/avatar/" + \
2cd9af74
MT
360 hashlib.md5(gravatar_email).hexdigest() + "?"
361 gravatar_url += urllib.urlencode({'d': "mm", 's': str(size)})
362
363 return gravatar_url
364
60024cc8 365
940227cb
MT
366if __name__ == "__main__":
367 a = Accounts()
368
369 print a.list()