]> git.ipfire.org Git - pbs.git/blobdiff - src/web/base.py
auth: Revert back to authentication using a web form
[pbs.git] / src / web / base.py
index 5b59d1c10c614082b21be4d8998465ed35f864f9..15dd79a76d5d0841ea1df7973642fc348afa8fa8 100644 (file)
@@ -1,6 +1,8 @@
 #!/usr/bin/python
 
+import asyncio
 import base64
+import functools
 import http.client
 import json
 import kerberos
@@ -50,6 +52,7 @@ class KerberosAuthMixin(object):
                # Set status to 401
                self.set_status(401)
 
+       @functools.cache
        def get_authenticated_user(self):
                auth_header = self.request.headers.get("Authorization", None)
 
@@ -58,7 +61,7 @@ class KerberosAuthMixin(object):
                        return
 
                # Perform GSS API Negotiation
-               if auth_header.startswith("Negotiate"):
+               if auth_header.startswith("Negotiate "):
                        return self._auth_negotiate(auth_header)
 
                # Perform Basic Authentication
@@ -125,6 +128,10 @@ class KerberosAuthMixin(object):
                except:
                        raise tornado.web.HTTPError(400, "Authorization data was malformed")
 
+               # Authenticate against Kerberos
+               return self._auth_with_credentials(username, password)
+
+       def _auth_with_credentials(self, username, password):
                # Check the credentials against the Kerberos database
                try:
                        kerberos.checkPassword(username, password,
@@ -200,6 +207,7 @@ class BaseHandler(tornado.web.RequestHandler):
                        "format_date"     : self.format_date,
                        "format_size"     : misc.format_size,
                        "version"         : __version__,
+                       "xsrf_token"      : self.xsrf_token,
                        "year"            : time.strftime("%Y"),
                })
 
@@ -227,7 +235,10 @@ class BaseHandler(tornado.web.RequestHandler):
        def get_argument_bool(self, name):
                arg = self.get_argument(name, default=None)
 
-               return arg == "on"
+               if arg:
+                       return arg.lower() in ("on", "true", "yes", "1")
+
+               return False
 
        def get_argument_int(self, *args, **kwargs):
                arg = self.get_argument(*args, **kwargs)
@@ -253,6 +264,17 @@ class BaseHandler(tornado.web.RequestHandler):
                if slug:
                        return self.backend.distros.get_by_slug(slug)
 
+       # Uploads
+
+       def _get_upload(self, uuid):
+               upload = self.backend.uploads.get_by_uuid(uuid)
+
+               # Check permissions
+               if upload and not upload.has_perm(self.current_user):
+                       raise tornado.web.HTTPError(403, "%s has no permissions for upload %s" % (self.current_user, upload))
+
+               return upload
+
        def get_argument_upload(self, *args, **kwargs):
                """
                        Returns an upload
@@ -260,7 +282,7 @@ class BaseHandler(tornado.web.RequestHandler):
                uuid = self.get_argument(*args, **kwargs)
 
                if uuid:
-                       return self.backend.uploads.get_by_uuid(uuid)
+                       return self._get_upload(uuid)
 
        def get_argument_uploads(self, *args, **kwargs):
                """
@@ -269,7 +291,7 @@ class BaseHandler(tornado.web.RequestHandler):
                uuids = self.get_arguments(*args, **kwargs)
 
                # Return all uploads
-               return [self.backend.uploads.get_by_uuid(uuid) for uuid in uuids]
+               return [self._get_upload(uuid) for uuid in uuids]
 
        def get_argument_user(self, *args, **kwargs):
                name = self.get_argument(*args, **kwargs)
@@ -358,3 +380,36 @@ class APIMixin(KerberosAuthMixin, BackendMixin):
                log.debug("%s" % json.dumps(message, indent=4))
 
                return message
+
+
+class ratelimit(object):
+       """
+               A decorator class which limits how often a function can be called
+       """
+       def __init__(self, *, minutes, requests):
+               self.minutes  = minutes
+               self.requests = requests
+
+       def __call__(self, method):
+               @functools.wraps(method)
+               async def wrapper(handler, *args, **kwargs):
+                       # Pass the request to the rate limiter and get a request object
+                       req = handler.backend.ratelimiter.handle_request(handler.request,
+                               handler, minutes=self.minutes, limit=self.requests)
+
+                       # If the rate limit has been reached, we won't allow
+                       # processing the request and therefore send HTTP error code 429.
+                       if await req.is_ratelimited():
+                               raise tornado.web.HTTPError(429, "Rate limit exceeded")
+
+                       # Call the wrapped method
+                       result = method(handler, *args, **kwargs)
+
+                       # Await it if it is a coroutine
+                       if asyncio.iscoroutine(result):
+                               return await result
+
+                       # Return the result
+                       return result
+
+               return wrapper