]> git.ipfire.org Git - ipfire.org.git/blobdiff - src/web/people.py
people: Encourage people to add a description
[ipfire.org.git] / src / web / people.py
index c429aeb496885a659949f59c3b50a0a65e09e6a2..7f49cdfd6804d6f265f20daaf3c202d4499c6333 100644 (file)
@@ -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):