]> git.ipfire.org Git - ipfire.org.git/blob - src/backend/accounts.py
Introduce autotools
[ipfire.org.git] / src / backend / accounts.py
1 #!/usr/bin/python
2 # encoding: utf-8
3
4 import PIL
5 import StringIO
6 import hashlib
7 import ldap
8 import logging
9 import urllib
10
11 from misc import Object
12
13 class Accounts(Object):
14 @property
15 def ldap(self):
16 if not hasattr(self, "_ldap"):
17 # Connect to LDAP server
18 ldap_uri = self.settings.get("ldap_uri")
19 self._ldap = ldap.initialize(ldap_uri)
20
21 # Bind with username and password
22 bind_dn = self.settings.get("ldap_bind_dn")
23 if bind_dn:
24 bind_pw = self.settings.get("ldap_bind_pw", "")
25 self._ldap.simple_bind(bind_dn, bind_pw)
26
27 return self._ldap
28
29 def _search(self, query, attrlist=None, limit=0):
30 logging.debug("Performing LDAP query: %s" % query)
31
32 search_base = self.settings.get("ldap_search_base")
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
42
43 return results
44
45 def search(self, query, limit=0):
46 results = self._search(query, limit=limit)
47
48 accounts = []
49 for dn, attrs in results:
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)
82
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))
86
87 # Session stuff
88
89 def _cleanup_expired_sessions(self):
90 self.db.execute("DELETE FROM sessions WHERE time_expires <= NOW()")
91
92 def create_session(self, account, host):
93 self._cleanup_expired_sessions()
94
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
130
131 class Account(Object):
132 def __init__(self, backend, dn, attrs=None):
133 Object.__init__(self, backend)
134 self.dn = dn
135
136 self.__attrs = attrs or {}
137
138 def __repr__(self):
139 return "<%s %s>" % (self.__class__.__name__, self.dn)
140
141 def __cmp__(self, other):
142 return cmp(self.name, other.name)
143
144 @property
145 def ldap(self):
146 return self.accounts.ldap
147
148 @property
149 def attributes(self):
150 return self.__attrs
151
152 def _get_first_attribute(self, attr, default=None):
153 if not self.attributes.has_key(attr):
154 return default
155
156 res = self.attributes.get(attr, [])
157 if res:
158 return res[0]
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
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:
181 self.ldap.simple_bind_s(self.dn, password.encode("utf-8"))
182 except ldap.INVALID_CREDENTIALS:
183 logging.debug("Account credentials are invalid.")
184 return False
185
186 logging.debug("Successfully authenticated.")
187 return True
188
189 def is_admin(self):
190 return "wheel" in self.groups
191
192 def is_talk_enabled(self):
193 return "sipUser" in self.classes or "sipRoutingObject" in self.classes \
194 or self.telephone_numbers or self.address
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")
203
204 @property
205 def name(self):
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
233
234 @property
235 def email(self):
236 name = self.name.lower()
237 name = name.replace(" ", ".")
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")
244
245 for mail in self.attributes.get("mail", []):
246 if mail.startswith("%s@ipfire.org" % name):
247 return mail
248
249 # If everything else fails, we will go with the UID
250 return "%s@ipfire.org" % self.uid
251
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
260 @property
261 def sip_password(self):
262 return self._get_first_attribute("sipPassword")
263
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):
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 []
303
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
310 url = "https://%s/avatar/%s.jpg" % (hostname, self.uid)
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
354 gravatar_url = "https://www.gravatar.com/avatar/" + \
355 hashlib.md5(gravatar_email).hexdigest() + "?"
356 gravatar_url += urllib.urlencode({'d': "mm", 's': str(size)})
357
358 return gravatar_url
359
360
361 if __name__ == "__main__":
362 a = Accounts()
363
364 print a.list()