]> git.ipfire.org Git - ipfire.org.git/blame - src/web/people.py
people: Commit accidentially commented code
[ipfire.org.git] / src / web / people.py
CommitLineData
2cd9af74
MT
1#!/usr/bin/python
2
bdaf6b46 3import datetime
e96e445b 4import ldap
2cd9af74 5import logging
020de883 6import imghdr
0d1fb712 7import sshpubkeys
2cd9af74 8import tornado.web
2dac7110 9import urllib.parse
2cd9af74 10
0099c2a7
MT
11from .. import countries
12
9b8ff27d 13from . import auth
124a8404 14from . import base
786e9ca8
MT
15from . import ui_modules
16
9b8ff27d 17class IndexHandler(auth.CacheMixin, base.BaseHandler):
786e9ca8
MT
18 @tornado.web.authenticated
19 def get(self):
20 self.render("people/index.html")
21
2cd9af74 22
c4f1a618 23class AvatarHandler(base.BaseHandler):
0cddf220 24 def get(self, uid):
2cd9af74
MT
25 # Get the desired size of the avatar file
26 size = self.get_argument("size", 0)
27
28 try:
29 size = int(size)
30 except (TypeError, ValueError):
31 size = None
32
0cddf220 33 logging.debug("Querying for avatar of %s" % uid)
2cd9af74 34
f6ed3d4d 35 # Fetch user account
0cddf220 36 account = self.backend.accounts.get_by_uid(uid)
f6ed3d4d 37 if not account:
0cddf220 38 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
2cd9af74 39
f6ed3d4d
MT
40 # Allow downstream to cache this for 60 minutes
41 self.set_expires(3600)
2cd9af74 42
f6ed3d4d
MT
43 # Resize avatar
44 avatar = account.get_avatar(size)
2cd9af74 45
f6ed3d4d
MT
46 # If there is no avatar, we serve a default image
47 if not avatar:
0cddf220
MT
48 logging.debug("No avatar uploaded for %s" % account)
49
3f9f12f0 50 return self.redirect(self.static_url("img/default-avatar.jpg"))
2cd9af74 51
020de883
MT
52 # Guess content type
53 type = imghdr.what(None, avatar)
54
f6ed3d4d 55 # Set headers about content
020de883
MT
56 self.set_header("Content-Disposition", "inline; filename=\"%s.%s\"" % (account.uid, type))
57 self.set_header("Content-Type", "image/%s" % type)
2cd9af74 58
f6ed3d4d 59 # Deliver payload
2cd9af74 60 self.finish(avatar)
786e9ca8
MT
61
62
9b8ff27d 63class CallsHandler(auth.CacheMixin, base.BaseHandler):
bdaf6b46
MT
64 @tornado.web.authenticated
65 def get(self, uid, date=None):
66 account = self.backend.accounts.get_by_uid(uid)
67 if not account:
68 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
69
d561d931
MT
70 # Check for permissions
71 if not account.can_be_managed_by(self.current_user):
72 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
73
bdaf6b46 74 if date:
44b4640b
MT
75 try:
76 date = datetime.datetime.strptime(date, "%Y-%m-%d").date()
77 except ValueError:
78 raise tornado.web.HTTPError(400, "Invalid date: %s" % date)
bdaf6b46
MT
79 else:
80 date = datetime.date.today()
81
82 self.render("people/calls.html", account=account, date=date)
83
84
9b8ff27d 85class CallHandler(auth.CacheMixin, base.BaseHandler):
68ece434
MT
86 @tornado.web.authenticated
87 def get(self, uid, uuid):
d09d554b
MT
88 account = self.backend.accounts.get_by_uid(uid)
89 if not account:
90 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
91
d561d931
MT
92 # Check for permissions
93 if not account.can_be_managed_by(self.current_user):
94 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
95
68ece434
MT
96 call = self.backend.talk.freeswitch.get_call_by_uuid(uuid)
97 if not call:
98 raise tornado.web.HTTPError(404, "Could not find call %s" % uuid)
99
100 # XXX limit
101
d09d554b 102 self.render("people/call.html", account=account, call=call)
68ece434
MT
103
104
9b8ff27d 105class ConferencesHandler(auth.CacheMixin, base.BaseHandler):
30aeccdb
MT
106 @tornado.web.authenticated
107 def get(self):
108 self.render("people/conferences.html", conferences=self.backend.talk.conferences)
109
110
9b8ff27d 111class SearchHandler(auth.CacheMixin, base.BaseHandler):
786e9ca8
MT
112 @tornado.web.authenticated
113 def get(self):
114 q = self.get_argument("q")
115
116 # Perform the search
51907e45 117 accounts = self.backend.accounts.search(q)
786e9ca8
MT
118
119 # Redirect when only one result was found
120 if len(accounts) == 1:
121 self.redirect("/users/%s" % accounts[0].uid)
122 return
123
124 self.render("people/search.html", q=q, accounts=accounts)
125
f4672785 126
9b8ff27d 127class SSHKeysIndexHandler(auth.CacheMixin, base.BaseHandler):
f4672785
MT
128 @tornado.web.authenticated
129 def get(self, uid):
130 account = self.backend.accounts.get_by_uid(uid)
131 if not account:
132 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
133
134 self.render("people/ssh-keys/index.html", account=account)
135
55b67ca4 136
9b8ff27d 137class SSHKeysDownloadHandler(auth.CacheMixin, base.BaseHandler):
55b67ca4 138 @tornado.web.authenticated
44b75370 139 def get(self, uid, hash_sha256):
55b67ca4
MT
140 account = self.backend.accounts.get_by_uid(uid)
141 if not account:
142 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
143
144 # Get SSH key
44b75370 145 key = account.get_ssh_key_by_hash_sha256(hash_sha256)
55b67ca4 146 if not key:
44b75370 147 raise tornado.web.HTTPError(404, "Could not find key: %s" % hash_sha256)
55b67ca4
MT
148
149 # Set HTTP Headers
150 self.add_header("Content-Type", "text/plain")
151
152 self.finish(key.keydata)
153
0d1fb712 154
9b8ff27d 155class SSHKeysUploadHandler(auth.CacheMixin, base.BaseHandler):
0d1fb712
MT
156 @tornado.web.authenticated
157 def get(self, uid):
158 account = self.backend.accounts.get_by_uid(uid)
159 if not account:
160 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
161
162 # Check for permissions
163 if not account.can_be_managed_by(self.current_user):
164 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
165
166 self.render("people/ssh-keys/upload.html", account=account)
167
168 @tornado.web.authenticated
169 def post(self, uid):
170 account = self.backend.accounts.get_by_uid(uid)
171 if not account:
172 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
173
174 # Check for permissions
175 if not account.can_be_managed_by(self.current_user):
176 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
177
178 key = self.get_argument("key")
179
180 # Verify password
181 password = self.get_argument("password")
182 if not account.check_password(password):
183 raise tornado.web.HTTPError(403, "Incorrect password for %s" % account)
184
185 # Try to add new SSH key
186 try:
187 account.add_ssh_key(key)
188
189 except sshpubkeys.InvalidKeyException as e:
190 self.render("people/ssh-keys/error-invalid-key.html", account=account, exception=e)
191 return
192
193 self.redirect("/users/%s/ssh-keys" % account.uid)
194
195
9b8ff27d 196class SSHKeysDeleteHandler(auth.CacheMixin, base.BaseHandler):
0d1fb712 197 @tornado.web.authenticated
44b75370 198 def get(self, uid, hash_sha256):
0d1fb712
MT
199 account = self.backend.accounts.get_by_uid(uid)
200 if not account:
201 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
202
203 # Get SSH key
44b75370 204 key = account.get_ssh_key_by_hash_sha256(hash_sha256)
0d1fb712 205 if not key:
44b75370 206 raise tornado.web.HTTPError(404, "Could not find key: %s" % hash_sha256)
0d1fb712
MT
207
208 self.render("people/ssh-keys/delete.html", account=account, key=key)
209
210 @tornado.web.authenticated
44b75370 211 def post(self, uid, hash_sha256):
0d1fb712
MT
212 account = self.backend.accounts.get_by_uid(uid)
213 if not account:
214 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
215
216 # Get SSH key
44b75370 217 key = account.get_ssh_key_by_hash_sha256(hash_sha256)
0d1fb712 218 if not key:
44b75370 219 raise tornado.web.HTTPError(404, "Could not find key: %s" % hash_sha256)
0d1fb712
MT
220
221 # Verify password
222 password = self.get_argument("password")
223 if not account.check_password(password):
224 raise tornado.web.HTTPError(403, "Incorrect password for %s" % account)
225
226 # Delete the key
227 account.delete_ssh_key(key.keydata)
228
229 self.redirect("/users/%s/ssh-keys" % account.uid)
230
786e9ca8 231
9b8ff27d 232class SIPHandler(auth.CacheMixin, base.BaseHandler):
e0daee8f
MT
233 @tornado.web.authenticated
234 def get(self, uid):
235 account = self.backend.accounts.get_by_uid(uid)
236 if not account:
237 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
238
239 # Check for permissions
240 if not account.can_be_managed_by(self.current_user):
241 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
242
243 self.render("people/sip.html", account=account)
244
245
9b8ff27d 246class UsersHandler(auth.CacheMixin, base.BaseHandler):
786e9ca8
MT
247 @tornado.web.authenticated
248 def get(self):
71a3109c
MT
249 # Only staff can see other users
250 if not self.current_user.is_staff():
251 raise tornado.web.HTTPError(403)
252
786e9ca8
MT
253 self.render("people/users.html")
254
255
9b8ff27d 256class UserHandler(auth.CacheMixin, base.BaseHandler):
786e9ca8
MT
257 @tornado.web.authenticated
258 def get(self, uid):
259 account = self.backend.accounts.get_by_uid(uid)
260 if not account:
261 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
262
263 self.render("people/user.html", account=account)
264
265
9b8ff27d 266class UserEditHandler(auth.CacheMixin, base.BaseHandler):
e96e445b
MT
267 @tornado.web.authenticated
268 def get(self, uid):
269 account = self.backend.accounts.get_by_uid(uid)
270 if not account:
271 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
272
273 # Check for permissions
274 if not account.can_be_managed_by(self.current_user):
275 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
276
0099c2a7 277 self.render("people/user-edit.html", account=account, countries=countries.get_all())
e96e445b
MT
278
279 @tornado.web.authenticated
280 def post(self, uid):
281 account = self.backend.accounts.get_by_uid(uid)
282 if not account:
283 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
284
285 # Check for permissions
286 if not account.can_be_managed_by(self.current_user):
287 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
288
289 # Unfortunately this cannot be wrapped into a transaction
290 try:
0099c2a7
MT
291 account.first_name = self.get_argument("first_name")
292 account.last_name = self.get_argument("last_name")
d6e57f73 293 account.nickname = self.get_argument("nickname", None)
0099c2a7
MT
294 account.street = self.get_argument("street", None)
295 account.city = self.get_argument("city", None)
296 account.postal_code = self.get_argument("postal_code", None)
297 account.country_code = self.get_argument("country_code", None)
e96e445b 298
5cc10421
MT
299 # Avatar
300 try:
301 filename, data, mimetype = self.get_file("avatar")
302
303 if not mimetype.startswith("image/"):
304 raise tornado.web.HTTPError(400, "Avatar is not an image file: %s" % mimetype)
305
306 account.upload_avatar(data)
9bbf48b8 307 except TypeError:
5cc10421
MT
308 pass
309
e96e445b
MT
310 # Email
311 account.mail_routing_address = self.get_argument("mail_routing_address", None)
312
313 # Telephone
314 account.phone_numbers = self.get_argument("phone_numbers", "").splitlines()
315 account.sip_routing_address = self.get_argument("sip_routing_address", None)
316 except ldap.STRONG_AUTH_REQUIRED as e:
317 raise tornado.web.HTTPError(403, "%s" % e) from e
318
319 # Redirect back to user page
320 self.redirect("/users/%s" % account.uid)
321
322
9b8ff27d 323class UserPasswdHandler(auth.CacheMixin, base.BaseHandler):
3ea97943
MT
324 @tornado.web.authenticated
325 def get(self, uid):
326 account = self.backend.accounts.get_by_uid(uid)
327 if not account:
328 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
329
330 # Check for permissions
331 if not account.can_be_managed_by(self.current_user):
332 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
333
334 self.render("people/passwd.html", account=account)
335
336 @tornado.web.authenticated
337 def post(self, uid):
338 account = self.backend.accounts.get_by_uid(uid)
339 if not account:
340 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
341
342 # Check for permissions
343 if not account.can_be_managed_by(self.current_user):
344 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
345
346 # Get current password
347 password = self.get_argument("password")
348
349 # Get new password
350 password1 = self.get_argument("password1")
351 password2 = self.get_argument("password2")
352
353 # Passwords must match
354 if not password1 == password2:
355 raise tornado.web.HTTPError(400, "Passwords do not match")
356
357 # XXX Check password complexity
358
359 # Check if old password matches
360 if not account.check_password(password):
361 raise tornado.web.HTTPError(403, "Incorrect password for %s" % account)
362
363 # Save new password
364 account.passwd(password1)
365
366 # Redirect back to user's page
367 self.redirect("/users/%s" % account.uid)
368
369
2dac7110
MT
370class SSODiscourse(auth.CacheMixin, base.BaseHandler):
371 def _get_discourse_params(self):
372 # Fetch Discourse's parameters
373 sso = self.get_argument("sso")
374 sig = self.get_argument("sig")
375
376 # Decode payload
377 try:
378 return self.accounts.decode_discourse_payload(sso, sig)
379
380 # Raise bad request if the signature is invalid
381 except ValueError:
382 raise tornado.web.HTTPError(400)
383
384 def _redirect_user_to_discourse(self, account, nonce, return_sso_url):
385 """
386 Redirects the user back to Discourse passing some
387 attributes of the user account to Discourse
388 """
389 args = {
390 "nonce" : nonce,
391 "external_id" : account.uid,
392
393 # Pass email address
394 "email" : account.email,
395 "require_activation" : "false",
396
397 # More details about the user
398 "username" : account.uid,
399 "name" : "%s" % account,
400
401 # Avatar
402 "avatar_url" : account.avatar_url(),
403 "avatar_force_update" : "true",
404
405 # Send a welcome message
406 "suppress_welcome_message" : "false",
407
408 # Group memberships
409 "admin" : "true" if account.is_admin() else "false",
410 "moderator" : "true" if account.is_staff() else "false",
411 }
412
413 # Format payload and sign it
414 payload = self.accounts.encode_discourse_payload(**args)
415 signature = self.accounts.sign_discourse_payload(payload)
416
417 qs = urllib.parse.urlencode({
418 "sso" : payload,
419 "sig" : signature,
420 })
421
422 # Redirect user
423 self.redirect("%s?%s" % (return_sso_url, qs))
424
425 @base.ratelimit(minutes=24*60, requests=100)
426 def get(self):
427 params = self._get_discourse_params()
428
429 # Redirect back if user is already logged in
a2de2e1f
MT
430 if self.current_user:
431 return self._redirect_user_to_discourse(self.current_user, **params)
2dac7110
MT
432
433 # Otherwise the user needs to authenticate
328a7710
MT
434 self.render("auth/login.html", next=None)
435
436 @base.ratelimit(minutes=24*60, requests=100)
437 def post(self):
438 params = self._get_discourse_params()
439
440 # Get credentials
441 username = self.get_argument("username")
442 password = self.get_argument("password")
443
444 # Check credentials
445 account = self.accounts.auth(username, password)
446 if not account:
447 raise tornado.web.HTTPError(401, "Unknown user or invalid password: %s" % username)
448
449 # If the user has been authenticated, we will redirect to Discourse
450 self._redirect_user_to_discourse(account, **params)
2dac7110
MT
451
452
9150881e
MT
453class NewAccountsModule(ui_modules.UIModule):
454 def render(self, days=14):
455 t = datetime.datetime.utcnow() - datetime.timedelta(days=days)
456
457 # Fetch all accounts created after t
458 accounts = self.backend.accounts.get_created_after(t)
459
460 accounts.sort(key=lambda a: a.created_at, reverse=True)
461
462 return self.render_string("people/modules/accounts-new.html",
463 accounts=accounts, t=t)
464
465
786e9ca8
MT
466class AccountsListModule(ui_modules.UIModule):
467 def render(self, accounts=None):
468 if accounts is None:
51907e45 469 accounts = self.backend.accounts
786e9ca8
MT
470
471 return self.render_string("people/modules/accounts-list.html", accounts=accounts)
472
473
474class CDRModule(ui_modules.UIModule):
bdaf6b46
MT
475 def render(self, account, date=None, limit=None):
476 cdr = account.get_cdr(date=date, limit=limit)
786e9ca8 477
89e47299
MT
478 return self.render_string("people/modules/cdr.html",
479 account=account, cdr=list(cdr))
786e9ca8
MT
480
481
482class ChannelsModule(ui_modules.UIModule):
483 def render(self, account):
1f38be5a
MT
484 return self.render_string("people/modules/channels.html",
485 account=account, channels=account.sip_channels)
786e9ca8
MT
486
487
68ece434
MT
488class MOSModule(ui_modules.UIModule):
489 def render(self, call):
490 return self.render_string("people/modules/mos.html", call=call)
491
492
b5e2077f 493class PasswordModule(ui_modules.UIModule):
56894a8b 494 def render(self, account=None):
b5e2077f
MT
495 return self.render_string("people/modules/password.html", account=account)
496
497 def javascript_files(self):
498 return "js/zxcvbn.js"
499
500 def embedded_javascript(self):
501 return self.render_string("people/modules/password.js")
502
503
786e9ca8
MT
504class RegistrationsModule(ui_modules.UIModule):
505 def render(self, account):
506 return self.render_string("people/modules/registrations.html", account=account)
7afd64bb
MT
507
508
509class SIPStatusModule(ui_modules.UIModule):
510 def render(self, account):
511 return self.render_string("people/modules/sip-status.html", account=account)