]> git.ipfire.org Git - ipfire.org.git/blame - src/web/people.py
users: Merge stats from people
[ipfire.org.git] / src / web / people.py
CommitLineData
2cd9af74
MT
1#!/usr/bin/python
2
bdaf6b46 3import datetime
e96e445b 4import ldap
2cd9af74 5import tornado.web
2dac7110 6import urllib.parse
2cd9af74 7
0099c2a7
MT
8from .. import countries
9
9b8ff27d 10from . import auth
124a8404 11from . import base
786e9ca8
MT
12from . import ui_modules
13
9b8ff27d 14class IndexHandler(auth.CacheMixin, base.BaseHandler):
786e9ca8
MT
15 @tornado.web.authenticated
16 def get(self):
15bb44ee 17 self.render("people/index.html")
786e9ca8 18
2cd9af74 19
9b8ff27d 20class CallsHandler(auth.CacheMixin, base.BaseHandler):
bdaf6b46
MT
21 @tornado.web.authenticated
22 def get(self, uid, date=None):
23 account = self.backend.accounts.get_by_uid(uid)
24 if not account:
25 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
26
d561d931
MT
27 # Check for permissions
28 if not account.can_be_managed_by(self.current_user):
29 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
30
bdaf6b46 31 if date:
44b4640b
MT
32 try:
33 date = datetime.datetime.strptime(date, "%Y-%m-%d").date()
34 except ValueError:
35 raise tornado.web.HTTPError(400, "Invalid date: %s" % date)
bdaf6b46
MT
36 else:
37 date = datetime.date.today()
38
39 self.render("people/calls.html", account=account, date=date)
40
41
9b8ff27d 42class CallHandler(auth.CacheMixin, base.BaseHandler):
68ece434
MT
43 @tornado.web.authenticated
44 def get(self, uid, uuid):
d09d554b
MT
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
d561d931
MT
49 # Check for permissions
50 if not account.can_be_managed_by(self.current_user):
51 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
52
68ece434
MT
53 call = self.backend.talk.freeswitch.get_call_by_uuid(uuid)
54 if not call:
55 raise tornado.web.HTTPError(404, "Could not find call %s" % uuid)
56
57 # XXX limit
58
d09d554b 59 self.render("people/call.html", account=account, call=call)
68ece434
MT
60
61
9b8ff27d 62class ConferencesHandler(auth.CacheMixin, base.BaseHandler):
30aeccdb
MT
63 @tornado.web.authenticated
64 def get(self):
65 self.render("people/conferences.html", conferences=self.backend.talk.conferences)
66
67
18b13823
MT
68class GroupsHandler(auth.CacheMixin, base.BaseHandler):
69 @tornado.web.authenticated
70 def get(self):
71 # Only staff can see other groups
72 if not self.current_user.is_staff():
73 raise tornado.web.HTTPError(403)
74
75 self.render("people/groups.html")
76
77
bef47ee8
MT
78class GroupHandler(auth.CacheMixin, base.BaseHandler):
79 @tornado.web.authenticated
80 def get(self, gid):
81 # Only staff can see other groups
82 if not self.current_user.is_staff():
83 raise tornado.web.HTTPError(403)
84
85 # Fetch group
86 group = self.backend.groups.get_by_gid(gid)
87 if not group:
88 raise tornado.web.HTTPError(404, "Could not find group %s" % gid)
89
90 self.render("people/group.html", group=group)
91
92
92c4b559 93class SubscribeHandler(auth.CacheMixin, base.BaseHandler):
92c4b559
MT
94 @tornado.web.authenticated
95 def post(self):
96 # Give consent
97 self.current_user.consents_to_promotional_emails = True
98
99 self.render("people/subscribed.html")
100
101
102class UnsubscribeHandler(auth.CacheMixin, base.BaseHandler):
103 @tornado.web.authenticated
104 def get(self):
105 if self.current_user.consents_to_promotional_emails:
106 return self.render("people/unsubscribe.html")
107
108 self.render("people/unsubscribed.html")
109
110 @tornado.web.authenticated
111 def post(self):
112 # Withdraw consent
113 self.current_user.consents_to_promotional_emails = False
114
115 self.render("people/unsubscribed.html")
116
117
9b8ff27d 118class SIPHandler(auth.CacheMixin, base.BaseHandler):
e0daee8f
MT
119 @tornado.web.authenticated
120 def get(self, uid):
121 account = self.backend.accounts.get_by_uid(uid)
122 if not account:
123 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
124
125 # Check for permissions
126 if not account.can_be_managed_by(self.current_user):
127 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
128
129 self.render("people/sip.html", account=account)
130
131
9b8ff27d 132class UserEditHandler(auth.CacheMixin, base.BaseHandler):
e96e445b
MT
133 @tornado.web.authenticated
134 def get(self, uid):
135 account = self.backend.accounts.get_by_uid(uid)
136 if not account:
137 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
138
139 # Check for permissions
140 if not account.can_be_managed_by(self.current_user):
141 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
142
0099c2a7 143 self.render("people/user-edit.html", account=account, countries=countries.get_all())
e96e445b
MT
144
145 @tornado.web.authenticated
146 def post(self, uid):
147 account = self.backend.accounts.get_by_uid(uid)
148 if not account:
149 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
150
151 # Check for permissions
152 if not account.can_be_managed_by(self.current_user):
153 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
154
155 # Unfortunately this cannot be wrapped into a transaction
156 try:
0099c2a7
MT
157 account.first_name = self.get_argument("first_name")
158 account.last_name = self.get_argument("last_name")
d6e57f73 159 account.nickname = self.get_argument("nickname", None)
0099c2a7
MT
160 account.street = self.get_argument("street", None)
161 account.city = self.get_argument("city", None)
162 account.postal_code = self.get_argument("postal_code", None)
163 account.country_code = self.get_argument("country_code", None)
1c4522dc 164 account.description = self.get_argument("description", None)
e96e445b 165
5cc10421
MT
166 # Avatar
167 try:
168 filename, data, mimetype = self.get_file("avatar")
169
170 if not mimetype.startswith("image/"):
171 raise tornado.web.HTTPError(400, "Avatar is not an image file: %s" % mimetype)
172
173 account.upload_avatar(data)
9bbf48b8 174 except TypeError:
5cc10421
MT
175 pass
176
e96e445b
MT
177 # Email
178 account.mail_routing_address = self.get_argument("mail_routing_address", None)
179
180 # Telephone
181 account.phone_numbers = self.get_argument("phone_numbers", "").splitlines()
182 account.sip_routing_address = self.get_argument("sip_routing_address", None)
183 except ldap.STRONG_AUTH_REQUIRED as e:
184 raise tornado.web.HTTPError(403, "%s" % e) from e
185
186 # Redirect back to user page
187 self.redirect("/users/%s" % account.uid)
188
189
9b8ff27d 190class UserPasswdHandler(auth.CacheMixin, base.BaseHandler):
3ea97943
MT
191 @tornado.web.authenticated
192 def get(self, uid):
193 account = self.backend.accounts.get_by_uid(uid)
194 if not account:
195 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
196
197 # Check for permissions
198 if not account.can_be_managed_by(self.current_user):
199 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
200
201 self.render("people/passwd.html", account=account)
202
203 @tornado.web.authenticated
204 def post(self, uid):
205 account = self.backend.accounts.get_by_uid(uid)
206 if not account:
207 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
208
209 # Check for permissions
210 if not account.can_be_managed_by(self.current_user):
211 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
212
213 # Get current password
214 password = self.get_argument("password")
215
216 # Get new password
217 password1 = self.get_argument("password1")
218 password2 = self.get_argument("password2")
219
220 # Passwords must match
221 if not password1 == password2:
222 raise tornado.web.HTTPError(400, "Passwords do not match")
223
224 # XXX Check password complexity
225
226 # Check if old password matches
227 if not account.check_password(password):
228 raise tornado.web.HTTPError(403, "Incorrect password for %s" % account)
229
230 # Save new password
231 account.passwd(password1)
232
233 # Redirect back to user's page
234 self.redirect("/users/%s" % account.uid)
235
236
2dac7110 237class SSODiscourse(auth.CacheMixin, base.BaseHandler):
880fd132
MT
238 @base.ratelimit(minutes=24*60, requests=100)
239 @tornado.web.authenticated
240 def get(self):
2dac7110
MT
241 # Fetch Discourse's parameters
242 sso = self.get_argument("sso")
243 sig = self.get_argument("sig")
244
245 # Decode payload
246 try:
880fd132 247 params = self.accounts.decode_discourse_payload(sso, sig)
2dac7110
MT
248
249 # Raise bad request if the signature is invalid
250 except ValueError:
251 raise tornado.web.HTTPError(400)
252
880fd132 253 # Redirect back if user is already logged in
2dac7110 254 args = {
880fd132
MT
255 "nonce" : params.get("nonce"),
256 "external_id" : self.current_user.uid,
2dac7110
MT
257
258 # Pass email address
880fd132 259 "email" : self.current_user.email,
2dac7110
MT
260 "require_activation" : "false",
261
262 # More details about the user
880fd132
MT
263 "username" : self.current_user.uid,
264 "name" : "%s" % self.current_user,
265 "bio" : self.current_user.description or "",
2dac7110
MT
266
267 # Avatar
0da30c28 268 "avatar_url" : self.current_user.avatar_url(absolute=True),
2dac7110
MT
269 "avatar_force_update" : "true",
270
271 # Send a welcome message
272 "suppress_welcome_message" : "false",
273
274 # Group memberships
880fd132
MT
275 "admin" : "true" if self.current_user.is_admin() else "false",
276 "moderator" : "true" if self.current_user.is_moderator() else "false",
2dac7110
MT
277 }
278
279 # Format payload and sign it
280 payload = self.accounts.encode_discourse_payload(**args)
281 signature = self.accounts.sign_discourse_payload(payload)
282
283 qs = urllib.parse.urlencode({
284 "sso" : payload,
285 "sig" : signature,
286 })
287
288 # Redirect user
880fd132 289 self.redirect("%s?%s" % (params.get("return_sso_url"), qs))
2dac7110
MT
290
291
786e9ca8
MT
292class AccountsListModule(ui_modules.UIModule):
293 def render(self, accounts=None):
294 if accounts is None:
51907e45 295 accounts = self.backend.accounts
786e9ca8
MT
296
297 return self.render_string("people/modules/accounts-list.html", accounts=accounts)
298
299
c66f2152
MT
300class AgentModule(ui_modules.UIModule):
301 def render(self, account):
302 return self.render_string("people/modules/agent.html", account=account)
303
304
786e9ca8 305class CDRModule(ui_modules.UIModule):
bdaf6b46
MT
306 def render(self, account, date=None, limit=None):
307 cdr = account.get_cdr(date=date, limit=limit)
786e9ca8 308
89e47299
MT
309 return self.render_string("people/modules/cdr.html",
310 account=account, cdr=list(cdr))
786e9ca8
MT
311
312
313class ChannelsModule(ui_modules.UIModule):
314 def render(self, account):
1f38be5a
MT
315 return self.render_string("people/modules/channels.html",
316 account=account, channels=account.sip_channels)
786e9ca8
MT
317
318
68ece434
MT
319class MOSModule(ui_modules.UIModule):
320 def render(self, call):
321 return self.render_string("people/modules/mos.html", call=call)
322
323
b5e2077f 324class PasswordModule(ui_modules.UIModule):
56894a8b 325 def render(self, account=None):
b5e2077f
MT
326 return self.render_string("people/modules/password.html", account=account)
327
328 def javascript_files(self):
329 return "js/zxcvbn.js"
330
331 def embedded_javascript(self):
332 return self.render_string("people/modules/password.js")
333
334
786e9ca8
MT
335class RegistrationsModule(ui_modules.UIModule):
336 def render(self, account):
337 return self.render_string("people/modules/registrations.html", account=account)