]> 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 32cbf3ce445b9b9f565eff7aa3ad73eb804bb883..15dd79a76d5d0841ea1df7973642fc348afa8fa8 100644 (file)
@@ -1,6 +1,8 @@
 #!/usr/bin/python
 
+import asyncio
 import base64
+import functools
 import http.client
 import json
 import kerberos
@@ -13,12 +15,13 @@ import tornado.websocket
 import traceback
 
 from .. import __version__
+from .. import builders
 from .. import misc
 from .. import users
 from ..decorators import *
 
 # Setup logging
-log = logging.getLogger("pakfire.buildservice.web.base")
+log = logging.getLogger("pbs.web.base")
 
 class KerberosAuthMixin(object):
        """
@@ -49,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)
 
@@ -57,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
@@ -124,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,
@@ -199,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"),
                })
 
@@ -226,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)
@@ -240,12 +252,29 @@ class BaseHandler(tornado.web.RequestHandler):
                except (TypeError, ValueError):
                        raise tornado.web.HTTPError(400, "%s is not an integer" % arg)
 
+       def get_argument_builder(self, *args, **kwargs):
+               name = self.get_argument(*args, **kwargs)
+
+               if name:
+                       return self.backend.builders.get_by_name(name)
+
        def get_argument_distro(self, *args, **kwargs):
                slug = self.get_argument(*args, **kwargs)
 
                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
@@ -253,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):
                """
@@ -262,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)
@@ -313,6 +342,23 @@ class APIMixin(KerberosAuthMixin, BackendMixin):
        def get_user_locale(self):
                return self.get_browser_locale()
 
+       @property
+       def builder(self):
+               """
+                       This is a convenience handler to access a builder by a better name
+               """
+               if isinstance(self.current_user, builders.Builder):
+                       return self.current_user
+
+               raise AttributeError
+
+       def get_compression_options(self):
+               # Enable maximum compression
+               return {
+                       "compression_level" : 9,
+                       "mem_level" : 9,
+               }
+
        def write_error(self, code, **kwargs):
                # Send a JSON-encoded error message
                self.finish({
@@ -334,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