X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=src%2Fweb%2Fpeople.py;h=7f49cdfd6804d6f265f20daaf3c202d4499c6333;hb=67fc00bccaded924e574bdb5df190818c4b1a354;hp=c429aeb496885a659949f59c3b50a0a65e09e6a2;hpb=020de88397d6c8bd56f91961a6dc39493c5500a9;p=ipfire.org.git diff --git a/src/web/people.py b/src/web/people.py index c429aeb4..7f49cdfd 100644 --- a/src/web/people.py +++ b/src/web/people.py @@ -4,8 +4,8 @@ import datetime import ldap import logging import imghdr -import sshpubkeys import tornado.web +import urllib.parse from .. import countries @@ -16,13 +16,23 @@ from . import ui_modules class IndexHandler(auth.CacheMixin, base.BaseHandler): @tornado.web.authenticated def get(self): - self.render("people/index.html") + hints = [] + + # Suggest uploading an avatar if this user does not have one + if not self.current_user.has_avatar(): + hints.append("avatar") + + # Suggest adding a description + if not self.current_user.description: + hints.append("description") + + self.render("people/index.html", hints=hints) class AvatarHandler(base.BaseHandler): def get(self, uid): # Get the desired size of the avatar file - size = self.get_argument("size", 0) + size = self.get_argument("size", None) try: size = int(size) @@ -66,6 +76,10 @@ class CallsHandler(auth.CacheMixin, base.BaseHandler): if not account: raise tornado.web.HTTPError(404, "Could not find account %s" % uid) + # Check for permissions + if not account.can_be_managed_by(self.current_user): + raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account)) + if date: try: date = datetime.datetime.strptime(date, "%Y-%m-%d").date() @@ -84,6 +98,10 @@ class CallHandler(auth.CacheMixin, base.BaseHandler): if not account: raise tornado.web.HTTPError(404, "Could not find account %s" % uid) + # Check for permissions + if not account.can_be_managed_by(self.current_user): + raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account)) + call = self.backend.talk.freeswitch.get_call_by_uuid(uuid) if not call: raise tornado.web.HTTPError(404, "Could not find call %s" % uuid) @@ -99,125 +117,45 @@ class ConferencesHandler(auth.CacheMixin, base.BaseHandler): self.render("people/conferences.html", conferences=self.backend.talk.conferences) -class SearchHandler(auth.CacheMixin, base.BaseHandler): +class GroupsHandler(auth.CacheMixin, base.BaseHandler): @tornado.web.authenticated def get(self): - q = self.get_argument("q") - - # Perform the search - accounts = self.backend.accounts.search(q) - - # Redirect when only one result was found - if len(accounts) == 1: - self.redirect("/users/%s" % accounts[0].uid) - return - - self.render("people/search.html", q=q, accounts=accounts) - - -class SSHKeysIndexHandler(auth.CacheMixin, base.BaseHandler): - @tornado.web.authenticated - def get(self, uid): - account = self.backend.accounts.get_by_uid(uid) - if not account: - raise tornado.web.HTTPError(404, "Could not find account %s" % uid) + # Only staff can see other groups + if not self.current_user.is_staff(): + raise tornado.web.HTTPError(403) - self.render("people/ssh-keys/index.html", account=account) + self.render("people/groups.html") -class SSHKeysDownloadHandler(auth.CacheMixin, base.BaseHandler): +class GroupHandler(auth.CacheMixin, base.BaseHandler): @tornado.web.authenticated - def get(self, uid, hash_sha256): - account = self.backend.accounts.get_by_uid(uid) - if not account: - raise tornado.web.HTTPError(404, "Could not find account %s" % uid) - - # Get SSH key - key = account.get_ssh_key_by_hash_sha256(hash_sha256) - if not key: - raise tornado.web.HTTPError(404, "Could not find key: %s" % hash_sha256) - - # Set HTTP Headers - self.add_header("Content-Type", "text/plain") - - self.finish(key.keydata) - + def get(self, gid): + # Only staff can see other groups + if not self.current_user.is_staff(): + raise tornado.web.HTTPError(403) -class SSHKeysUploadHandler(auth.CacheMixin, base.BaseHandler): - @tornado.web.authenticated - def get(self, uid): - account = self.backend.accounts.get_by_uid(uid) - if not account: - raise tornado.web.HTTPError(404, "Could not find account %s" % uid) + # Fetch group + group = self.backend.groups.get_by_gid(gid) + if not group: + raise tornado.web.HTTPError(404, "Could not find group %s" % gid) - # Check for permissions - if not account.can_be_managed_by(self.current_user): - raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account)) + self.render("people/group.html", group=group) - self.render("people/ssh-keys/upload.html", account=account) +class SearchHandler(auth.CacheMixin, base.BaseHandler): @tornado.web.authenticated - def post(self, uid): - account = self.backend.accounts.get_by_uid(uid) - if not account: - raise tornado.web.HTTPError(404, "Could not find account %s" % uid) - - # Check for permissions - if not account.can_be_managed_by(self.current_user): - raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account)) - - key = self.get_argument("key") - - # Verify password - password = self.get_argument("password") - if not account.check_password(password): - raise tornado.web.HTTPError(403, "Incorrect password for %s" % account) + def get(self): + q = self.get_argument("q") - # Try to add new SSH key - try: - account.add_ssh_key(key) + # Perform the search + accounts = self.backend.accounts.search(q) - except sshpubkeys.InvalidKeyException as e: - self.render("people/ssh-keys/error-invalid-key.html", account=account, exception=e) + # Redirect when only one result was found + if len(accounts) == 1: + self.redirect("/users/%s" % accounts[0].uid) return - self.redirect("/users/%s/ssh-keys" % account.uid) - - -class SSHKeysDeleteHandler(auth.CacheMixin, base.BaseHandler): - @tornado.web.authenticated - def get(self, uid, hash_sha256): - account = self.backend.accounts.get_by_uid(uid) - if not account: - raise tornado.web.HTTPError(404, "Could not find account %s" % uid) - - # Get SSH key - key = account.get_ssh_key_by_hash_sha256(hash_sha256) - if not key: - raise tornado.web.HTTPError(404, "Could not find key: %s" % hash_sha256) - - self.render("people/ssh-keys/delete.html", account=account, key=key) - - @tornado.web.authenticated - def post(self, uid, hash_sha256): - account = self.backend.accounts.get_by_uid(uid) - if not account: - raise tornado.web.HTTPError(404, "Could not find account %s" % uid) - - # Get SSH key - key = account.get_ssh_key_by_hash_sha256(hash_sha256) - if not key: - raise tornado.web.HTTPError(404, "Could not find key: %s" % hash_sha256) - - # Verify password - password = self.get_argument("password") - if not account.check_password(password): - raise tornado.web.HTTPError(403, "Incorrect password for %s" % account) - - # Delete the key - account.delete_ssh_key(key.keydata) - - self.redirect("/users/%s/ssh-keys" % account.uid) + self.render("people/search.html", q=q, accounts=accounts) class SIPHandler(auth.CacheMixin, base.BaseHandler): @@ -281,10 +219,12 @@ class UserEditHandler(auth.CacheMixin, base.BaseHandler): try: account.first_name = self.get_argument("first_name") account.last_name = self.get_argument("last_name") + account.nickname = self.get_argument("nickname", None) account.street = self.get_argument("street", None) account.city = self.get_argument("city", None) account.postal_code = self.get_argument("postal_code", None) account.country_code = self.get_argument("country_code", None) + account.description = self.get_argument("description", None) # Avatar try: @@ -357,6 +297,103 @@ class UserPasswdHandler(auth.CacheMixin, base.BaseHandler): self.redirect("/users/%s" % account.uid) +class SSODiscourse(auth.CacheMixin, base.BaseHandler): + def _get_discourse_params(self): + # Fetch Discourse's parameters + sso = self.get_argument("sso") + sig = self.get_argument("sig") + + # Decode payload + try: + return self.accounts.decode_discourse_payload(sso, sig) + + # Raise bad request if the signature is invalid + except ValueError: + raise tornado.web.HTTPError(400) + + def _redirect_user_to_discourse(self, account, nonce, return_sso_url): + """ + Redirects the user back to Discourse passing some + attributes of the user account to Discourse + """ + args = { + "nonce" : nonce, + "external_id" : account.uid, + + # Pass email address + "email" : account.email, + "require_activation" : "false", + + # More details about the user + "username" : account.uid, + "name" : "%s" % account, + "bio" : account.description or "", + + # Avatar + "avatar_url" : account.avatar_url(), + "avatar_force_update" : "true", + + # Send a welcome message + "suppress_welcome_message" : "false", + + # Group memberships + "admin" : "true" if account.is_admin() else "false", + "moderator" : "true" if account.is_moderator() else "false", + } + + # Format payload and sign it + payload = self.accounts.encode_discourse_payload(**args) + signature = self.accounts.sign_discourse_payload(payload) + + qs = urllib.parse.urlencode({ + "sso" : payload, + "sig" : signature, + }) + + # Redirect user + self.redirect("%s?%s" % (return_sso_url, qs)) + + @base.ratelimit(minutes=24*60, requests=100) + def get(self): + params = self._get_discourse_params() + + # Redirect back if user is already logged in + if self.current_user: + return self._redirect_user_to_discourse(self.current_user, **params) + + # Otherwise the user needs to authenticate + self.render("auth/login.html", next=None) + + @base.ratelimit(minutes=24*60, requests=100) + def post(self): + params = self._get_discourse_params() + + # Get credentials + username = self.get_argument("username") + password = self.get_argument("password") + + # Check credentials + account = self.accounts.auth(username, password) + if not account: + raise tornado.web.HTTPError(401, "Unknown user or invalid password: %s" % username) + + # If the user has been authenticated, we will redirect to Discourse + self._redirect_user_to_discourse(account, **params) + + +class NewAccountsModule(ui_modules.UIModule): + def render(self, days=14): + t = datetime.datetime.utcnow() - datetime.timedelta(days=days) + + # Fetch all accounts created after t + accounts = self.backend.accounts.get_created_after(t) + + accounts.sort(key=lambda a: a.created_at, reverse=True) + + return self.render_string("people/modules/accounts-new.html", + accounts=accounts, t=t) + + class AccountsListModule(ui_modules.UIModule): def render(self, accounts=None): if accounts is None: @@ -385,7 +422,7 @@ class MOSModule(ui_modules.UIModule): class PasswordModule(ui_modules.UIModule): - def render(self, account): + def render(self, account=None): return self.render_string("people/modules/password.html", account=account) def javascript_files(self):