]>
Commit | Line | Data |
---|---|---|
2cd9af74 MT |
1 | #!/usr/bin/python |
2 | ||
bdaf6b46 | 3 | import datetime |
e96e445b | 4 | import ldap |
2cd9af74 | 5 | import tornado.web |
2dac7110 | 6 | import urllib.parse |
2cd9af74 | 7 | |
0099c2a7 MT |
8 | from .. import countries |
9 | ||
9b8ff27d | 10 | from . import auth |
124a8404 | 11 | from . import base |
786e9ca8 MT |
12 | from . import ui_modules |
13 | ||
9b8ff27d | 14 | class 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 | 20 | class 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 | 42 | class 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 | 62 | class 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 |
68 | class 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 |
78 | class 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 | 93 | class 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 | ||
102 | class 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 | 118 | class 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 | 132 | class 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 | |
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 | 190 | class 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 | 237 | class 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 |
292 | class 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 |
300 | class AgentModule(ui_modules.UIModule): |
301 | def render(self, account): | |
302 | return self.render_string("people/modules/agent.html", account=account) | |
303 | ||
304 | ||
786e9ca8 | 305 | class 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 | ||
313 | class 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 |
319 | class MOSModule(ui_modules.UIModule): |
320 | def render(self, call): | |
321 | return self.render_string("people/modules/mos.html", call=call) | |
322 | ||
323 | ||
b5e2077f | 324 | class 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 |
335 | class RegistrationsModule(ui_modules.UIModule): |
336 | def render(self, account): | |
337 | return self.render_string("people/modules/registrations.html", account=account) |