]>
Commit | Line | Data |
---|---|---|
940227cb | 1 | #!/usr/bin/python |
78fdedae | 2 | # encoding: utf-8 |
940227cb | 3 | |
2cd9af74 MT |
4 | import PIL |
5 | import StringIO | |
940227cb MT |
6 | import hashlib |
7 | import ldap | |
27066195 | 8 | import logging |
940227cb MT |
9 | import urllib |
10 | ||
a6dc0bad | 11 | from misc import Object |
940227cb | 12 | |
a6dc0bad | 13 | class 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 | 131 | class 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 |
361 | if __name__ == "__main__": |
362 | a = Accounts() | |
363 | ||
364 | print a.list() |