]> git.ipfire.org Git - ipfire.org.git/blob - src/web/people.py
d7a8675d59ae04b76033bff91c5e8a1538185880
[ipfire.org.git] / src / web / people.py
1 #!/usr/bin/python
2
3 import datetime
4 import ldap
5 import logging
6 import imghdr
7 import tornado.web
8 import urllib.parse
9
10 from .. import countries
11
12 from . import auth
13 from . import base
14 from . import ui_modules
15
16 class IndexHandler(auth.CacheMixin, base.BaseHandler):
17 @tornado.web.authenticated
18 def get(self):
19 hints = []
20
21 # Suggest uploading an avatar if this user does not have one
22 if not self.current_user.has_avatar():
23 hints.append("avatar")
24
25 # Suggest adding a description
26 if not self.current_user.description:
27 hints.append("description")
28
29 self.render("people/index.html", hints=hints)
30
31
32 class AvatarHandler(base.BaseHandler):
33 def get(self, uid):
34 # Get the desired size of the avatar file
35 size = self.get_argument("size", None)
36
37 try:
38 size = int(size)
39 except (TypeError, ValueError):
40 size = None
41
42 logging.debug("Querying for avatar of %s" % uid)
43
44 # Fetch user account
45 account = self.backend.accounts.get_by_uid(uid)
46 if not account:
47 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
48
49 # Allow downstream to cache this for 60 minutes
50 self.set_expires(3600)
51
52 # Resize avatar
53 avatar = account.get_avatar(size)
54
55 # If there is no avatar, we serve a default image
56 if not avatar:
57 logging.debug("No avatar uploaded for %s" % account)
58
59 return self.redirect(self.static_url("img/default-avatar.jpg"))
60
61 # Guess content type
62 type = imghdr.what(None, avatar)
63
64 # Set headers about content
65 self.set_header("Content-Disposition", "inline; filename=\"%s.%s\"" % (account.uid, type))
66 self.set_header("Content-Type", "image/%s" % type)
67
68 # Deliver payload
69 self.finish(avatar)
70
71
72 class CallsHandler(auth.CacheMixin, base.BaseHandler):
73 @tornado.web.authenticated
74 def get(self, uid, date=None):
75 account = self.backend.accounts.get_by_uid(uid)
76 if not account:
77 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
78
79 # Check for permissions
80 if not account.can_be_managed_by(self.current_user):
81 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
82
83 if date:
84 try:
85 date = datetime.datetime.strptime(date, "%Y-%m-%d").date()
86 except ValueError:
87 raise tornado.web.HTTPError(400, "Invalid date: %s" % date)
88 else:
89 date = datetime.date.today()
90
91 self.render("people/calls.html", account=account, date=date)
92
93
94 class CallHandler(auth.CacheMixin, base.BaseHandler):
95 @tornado.web.authenticated
96 def get(self, uid, uuid):
97 account = self.backend.accounts.get_by_uid(uid)
98 if not account:
99 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
100
101 # Check for permissions
102 if not account.can_be_managed_by(self.current_user):
103 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
104
105 call = self.backend.talk.freeswitch.get_call_by_uuid(uuid)
106 if not call:
107 raise tornado.web.HTTPError(404, "Could not find call %s" % uuid)
108
109 # XXX limit
110
111 self.render("people/call.html", account=account, call=call)
112
113
114 class ConferencesHandler(auth.CacheMixin, base.BaseHandler):
115 @tornado.web.authenticated
116 def get(self):
117 self.render("people/conferences.html", conferences=self.backend.talk.conferences)
118
119
120 class GroupsHandler(auth.CacheMixin, base.BaseHandler):
121 @tornado.web.authenticated
122 def get(self):
123 # Only staff can see other groups
124 if not self.current_user.is_staff():
125 raise tornado.web.HTTPError(403)
126
127 self.render("people/groups.html")
128
129
130 class GroupHandler(auth.CacheMixin, base.BaseHandler):
131 @tornado.web.authenticated
132 def get(self, gid):
133 # Only staff can see other groups
134 if not self.current_user.is_staff():
135 raise tornado.web.HTTPError(403)
136
137 # Fetch group
138 group = self.backend.groups.get_by_gid(gid)
139 if not group:
140 raise tornado.web.HTTPError(404, "Could not find group %s" % gid)
141
142 self.render("people/group.html", group=group)
143
144
145 class SearchHandler(auth.CacheMixin, base.BaseHandler):
146 @tornado.web.authenticated
147 def get(self):
148 q = self.get_argument("q")
149
150 # Perform the search
151 accounts = self.backend.accounts.search(q)
152
153 # Redirect when only one result was found
154 if len(accounts) == 1:
155 self.redirect("/users/%s" % accounts[0].uid)
156 return
157
158 self.render("people/search.html", q=q, accounts=accounts)
159
160
161 class SIPHandler(auth.CacheMixin, base.BaseHandler):
162 @tornado.web.authenticated
163 def get(self, uid):
164 account = self.backend.accounts.get_by_uid(uid)
165 if not account:
166 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
167
168 # Check for permissions
169 if not account.can_be_managed_by(self.current_user):
170 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
171
172 self.render("people/sip.html", account=account)
173
174
175 class UsersHandler(auth.CacheMixin, base.BaseHandler):
176 @tornado.web.authenticated
177 def get(self):
178 # Only staff can see other users
179 if not self.current_user.is_staff():
180 raise tornado.web.HTTPError(403)
181
182 self.render("people/users.html")
183
184
185 class UserHandler(auth.CacheMixin, base.BaseHandler):
186 @tornado.web.authenticated
187 def get(self, uid):
188 account = self.backend.accounts.get_by_uid(uid)
189 if not account:
190 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
191
192 self.render("people/user.html", account=account)
193
194
195 class UserEditHandler(auth.CacheMixin, base.BaseHandler):
196 @tornado.web.authenticated
197 def get(self, uid):
198 account = self.backend.accounts.get_by_uid(uid)
199 if not account:
200 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
201
202 # Check for permissions
203 if not account.can_be_managed_by(self.current_user):
204 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
205
206 self.render("people/user-edit.html", account=account, countries=countries.get_all())
207
208 @tornado.web.authenticated
209 def post(self, uid):
210 account = self.backend.accounts.get_by_uid(uid)
211 if not account:
212 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
213
214 # Check for permissions
215 if not account.can_be_managed_by(self.current_user):
216 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
217
218 # Unfortunately this cannot be wrapped into a transaction
219 try:
220 account.first_name = self.get_argument("first_name")
221 account.last_name = self.get_argument("last_name")
222 account.nickname = self.get_argument("nickname", None)
223 account.street = self.get_argument("street", None)
224 account.city = self.get_argument("city", None)
225 account.postal_code = self.get_argument("postal_code", None)
226 account.country_code = self.get_argument("country_code", None)
227 account.description = self.get_argument("description", None)
228
229 # Avatar
230 try:
231 filename, data, mimetype = self.get_file("avatar")
232
233 if not mimetype.startswith("image/"):
234 raise tornado.web.HTTPError(400, "Avatar is not an image file: %s" % mimetype)
235
236 account.upload_avatar(data)
237 except TypeError:
238 pass
239
240 # Email
241 account.mail_routing_address = self.get_argument("mail_routing_address", None)
242
243 # Telephone
244 account.phone_numbers = self.get_argument("phone_numbers", "").splitlines()
245 account.sip_routing_address = self.get_argument("sip_routing_address", None)
246 except ldap.STRONG_AUTH_REQUIRED as e:
247 raise tornado.web.HTTPError(403, "%s" % e) from e
248
249 # Redirect back to user page
250 self.redirect("/users/%s" % account.uid)
251
252
253 class UserPasswdHandler(auth.CacheMixin, base.BaseHandler):
254 @tornado.web.authenticated
255 def get(self, uid):
256 account = self.backend.accounts.get_by_uid(uid)
257 if not account:
258 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
259
260 # Check for permissions
261 if not account.can_be_managed_by(self.current_user):
262 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
263
264 self.render("people/passwd.html", account=account)
265
266 @tornado.web.authenticated
267 def post(self, uid):
268 account = self.backend.accounts.get_by_uid(uid)
269 if not account:
270 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
271
272 # Check for permissions
273 if not account.can_be_managed_by(self.current_user):
274 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
275
276 # Get current password
277 password = self.get_argument("password")
278
279 # Get new password
280 password1 = self.get_argument("password1")
281 password2 = self.get_argument("password2")
282
283 # Passwords must match
284 if not password1 == password2:
285 raise tornado.web.HTTPError(400, "Passwords do not match")
286
287 # XXX Check password complexity
288
289 # Check if old password matches
290 if not account.check_password(password):
291 raise tornado.web.HTTPError(403, "Incorrect password for %s" % account)
292
293 # Save new password
294 account.passwd(password1)
295
296 # Redirect back to user's page
297 self.redirect("/users/%s" % account.uid)
298
299
300 class SSODiscourse(auth.CacheMixin, base.BaseHandler):
301 @base.ratelimit(minutes=24*60, requests=100)
302 @tornado.web.authenticated
303 def get(self):
304 # Fetch Discourse's parameters
305 sso = self.get_argument("sso")
306 sig = self.get_argument("sig")
307
308 # Decode payload
309 try:
310 params = self.accounts.decode_discourse_payload(sso, sig)
311
312 # Raise bad request if the signature is invalid
313 except ValueError:
314 raise tornado.web.HTTPError(400)
315
316 # Redirect back if user is already logged in
317 args = {
318 "nonce" : params.get("nonce"),
319 "external_id" : self.current_user.uid,
320
321 # Pass email address
322 "email" : self.current_user.email,
323 "require_activation" : "false",
324
325 # More details about the user
326 "username" : self.current_user.uid,
327 "name" : "%s" % self.current_user,
328 "bio" : self.current_user.description or "",
329
330 # Avatar
331 "avatar_url" : self.current_user.avatar_url(),
332 "avatar_force_update" : "true",
333
334 # Send a welcome message
335 "suppress_welcome_message" : "false",
336
337 # Group memberships
338 "admin" : "true" if self.current_user.is_admin() else "false",
339 "moderator" : "true" if self.current_user.is_moderator() else "false",
340 }
341
342 # Format payload and sign it
343 payload = self.accounts.encode_discourse_payload(**args)
344 signature = self.accounts.sign_discourse_payload(payload)
345
346 qs = urllib.parse.urlencode({
347 "sso" : payload,
348 "sig" : signature,
349 })
350
351 # Redirect user
352 self.redirect("%s?%s" % (params.get("return_sso_url"), qs))
353
354
355 class NewAccountsModule(ui_modules.UIModule):
356 def render(self, days=14):
357 t = datetime.datetime.utcnow() - datetime.timedelta(days=days)
358
359 # Fetch all accounts created after t
360 accounts = self.backend.accounts.get_created_after(t)
361
362 accounts.sort(key=lambda a: a.created_at, reverse=True)
363
364 return self.render_string("people/modules/accounts-new.html",
365 accounts=accounts, t=t)
366
367
368 class AccountsListModule(ui_modules.UIModule):
369 def render(self, accounts=None):
370 if accounts is None:
371 accounts = self.backend.accounts
372
373 return self.render_string("people/modules/accounts-list.html", accounts=accounts)
374
375
376 class CDRModule(ui_modules.UIModule):
377 def render(self, account, date=None, limit=None):
378 cdr = account.get_cdr(date=date, limit=limit)
379
380 return self.render_string("people/modules/cdr.html",
381 account=account, cdr=list(cdr))
382
383
384 class ChannelsModule(ui_modules.UIModule):
385 def render(self, account):
386 return self.render_string("people/modules/channels.html",
387 account=account, channels=account.sip_channels)
388
389
390 class MOSModule(ui_modules.UIModule):
391 def render(self, call):
392 return self.render_string("people/modules/mos.html", call=call)
393
394
395 class PasswordModule(ui_modules.UIModule):
396 def render(self, account=None):
397 return self.render_string("people/modules/password.html", account=account)
398
399 def javascript_files(self):
400 return "js/zxcvbn.js"
401
402 def embedded_javascript(self):
403 return self.render_string("people/modules/password.js")
404
405
406 class RegistrationsModule(ui_modules.UIModule):
407 def render(self, account):
408 return self.render_string("people/modules/registrations.html", account=account)
409
410
411 class SIPStatusModule(ui_modules.UIModule):
412 def render(self, account):
413 return self.render_string("people/modules/sip-status.html", account=account)