From c2513d379de2954e705ca5a4cc3523c5006c42b2 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Fri, 23 Feb 2024 19:19:23 +0000 Subject: [PATCH] nopaste: Require authentication on the cURL interface Signed-off-by: Michael Tremer --- src/web/base.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++ src/web/nopaste.py | 12 ++++++++--- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/web/base.py b/src/web/base.py index 956dca2f..efc47d7f 100644 --- a/src/web/base.py +++ b/src/web/base.py @@ -1,6 +1,7 @@ #!/usr/bin/python import asyncio +import base64 import datetime import dateutil.parser import functools @@ -169,6 +170,57 @@ class BaseHandler(tornado.web.RequestHandler): def referrer(self): return self.request.headers.get("Referer", None) + def _request_basic_authentication(self): + """ + Called to ask the client to perform HTTP Basic authentication + """ + # Ask for authentication + self.set_status(401) + + # Say that we support Basic + self.set_header("WWW-Authenticate", "Basic realm=Restricted") + + self.finish() + + def perform_basic_authentication(self): + """ + This handles HTTP Basic authentication. + """ + # Fetch credentials + cred = self.request.headers.get("Authorization", None) + if not cred: + return self._request_basic_authentication() + + # No basic auth? We cannot handle that + if not cred.startswith("Basic "): + return self._request_basic_authentication() + + # Decode the credentials + try: + # Convert into bytes() + cred = cred[6:].encode() + + # Decode base64 + cred = base64.b64decode(cred).decode() + + username, password = cred.split(":", 1) + + # Fail if any of those steps failed + except: + raise e + raise tornado.web.HTTPError(400, "Authorization data was malformed") + + # Find the user in the database + return self.backend.accounts.auth(username, password) + + # Log something + if account: + log.info("%s authenticated successfully using HTTP Basic authentication" % account.uid) + else: + log.warning("Could not authenticate %s" % username) + + return account + def get_argument_int(self, *args, **kwargs): arg = self.get_argument(*args, **kwargs) diff --git a/src/web/nopaste.py b/src/web/nopaste.py index f0508d83..6424f26e 100644 --- a/src/web/nopaste.py +++ b/src/web/nopaste.py @@ -28,6 +28,13 @@ class CreateHandler(base.AnalyticsMixin, base.BaseHandler): # cURL Interface + def get_current_user(self): + if self.request.method == "PUT": + return self.perform_basic_authentication() + + # Perform the usual authentication + return super().get_current_user() + def check_xsrf_cookie(self): # Skip the check on PUT if self.request.method == "PUT": @@ -36,13 +43,12 @@ class CreateHandler(base.AnalyticsMixin, base.BaseHandler): # Perform the check as usual super().check_xsrf_cookie() - # XXX implement HTTP Basic authentication - + @tornado.web.authenticated @base.ratelimit(minutes=15, requests=5) def put(self): with self.db.transaction(): paste = self.backend.nopaste.create( - self.request.body, address=self.get_remote_ip()) + self.request.body, account=account, address=self.get_remote_ip()) # Send a message to the client self.write("https://%s/view/%s\n" % (self.request.host, paste.uuid)) -- 2.47.2