import ldap
import logging
import imghdr
-import sshpubkeys
import tornado.web
+import urllib.parse
from .. import countries
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)
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()
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)
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):
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:
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:
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):