From e95e07be18cbfab4c7547c0a8e4b5884476becb2 Mon Sep 17 00:00:00 2001 From: charliemirabile <46761267+charliemirabile@users.noreply.github.com> Date: Tue, 4 Jun 2024 00:38:47 -0400 Subject: [PATCH 1/6] nginx_snippets/orbit: change login redirect to use HTTP 403 401 should be used to request the client to authenitcate (e.g. using http basic auth) and not just to indicate that they are not allowed access to a resource. The HTTP 403 Forbidden status code is better. --- nginx_snippets/server_https/00-orbit-paths.conf | 2 +- nginx_snippets/server_https/01-error-pages.conf | 1 - orbit/radius.py | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/nginx_snippets/server_https/00-orbit-paths.conf b/nginx_snippets/server_https/00-orbit-paths.conf index 84021ad2..129a0a76 100644 --- a/nginx_snippets/server_https/00-orbit-paths.conf +++ b/nginx_snippets/server_https/00-orbit-paths.conf @@ -1,4 +1,4 @@ -error_page 401 @login; +error_page 403 @login; location @login { absolute_redirect off; diff --git a/nginx_snippets/server_https/01-error-pages.conf b/nginx_snippets/server_https/01-error-pages.conf index 1619ef93..cfae3b8a 100644 --- a/nginx_snippets/server_https/01-error-pages.conf +++ b/nginx_snippets/server_https/01-error-pages.conf @@ -1,6 +1,5 @@ error_page 400 - 403 404 405 500 diff --git a/orbit/radius.py b/orbit/radius.py index 20914f6d..0b5f7f5e 100644 --- a/orbit/radius.py +++ b/orbit/radius.py @@ -380,7 +380,7 @@ def handle_stub(rocket, more=[]): def handle_dashboard(rocket): if not rocket.session: - return rocket.raw_respond(HTTPStatus.UNAUTHORIZED) + return rocket.raw_respond(HTTPStatus.FORBIDDEN) submissions = (mailman.db.Submission.select() .where(mailman.db.Submission.user == rocket.session.username) # NOQA: E501 @@ -445,7 +445,7 @@ def form_respond(): def handle_cgit(rocket): if not rocket.session: - return rocket.raw_respond(HTTPStatus.UNAUTHORIZED) + return rocket.raw_respond(HTTPStatus.FORBIDDEN) cgit_env = os.environ.copy() cgit_env['PATH_INFO'] = rocket.path_info.removeprefix('/cgit') cgit_env['QUERY_STRING'] = rocket.env.get('QUERY_STRING', '') From 48887b9759e98e9684be133a417c25619f70baa9 Mon Sep 17 00:00:00 2001 From: charliemirabile <46761267+charliemirabile@users.noreply.github.com> Date: Tue, 4 Jun 2024 01:19:18 -0400 Subject: [PATCH 2/6] orbit: cgit: handle cgi response more correctly Without the embedded option in the cgitrc cgit generates a full html page with its own html and body tags that we were embedding within our existing body creating ill-formed html documents. We can also use noheader to suppress the header bar and avoid the need to hide the logo with custom css rules. By parsing the http headers returned from cgit we can also surface information like content type to the client and suppress wrapping the output in our html if it is supposed to be binary data which allows us to bring back the plain view button. --- orbit/cgitrc | 20 +++++--------------- orbit/radius.py | 41 ++++++++++++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/orbit/cgitrc b/orbit/cgitrc index 030b37cc..f3888393 100644 --- a/orbit/cgitrc +++ b/orbit/cgitrc @@ -1,24 +1,14 @@ -# Specify the css url -css=/style.css - # Allow http transport git clone enable-http-clone=0 -# Enable caching of up to 1000 output entries -cache-size=0 - -# Use a custom logo -logo=/images/kdlp_logo.png - -# Set the title and heading of the repository index page -root-title=Kernel Development Learning Pipeline Git Repositories - -# Set a subheading for the repository index page -root-desc=Explore the class git repositories here! - # Allow cgit to use git config to set repo-specific settings enable-git-config=1 + +# Create output suitable for embedding within other pages +embedded=1 +noheader=1 + ## ## List of common mimetypes ## diff --git a/orbit/radius.py b/orbit/radius.py index 0b5f7f5e..a7fe1771 100644 --- a/orbit/radius.py +++ b/orbit/radius.py @@ -450,9 +450,11 @@ def handle_cgit(rocket): cgit_env['PATH_INFO'] = rocket.path_info.removeprefix('/cgit') cgit_env['QUERY_STRING'] = rocket.env.get('QUERY_STRING', '') - path_array = cgit_env['PATH_INFO'].split('/') - if len(path_array) > 2 and path_array[2] == 'plain': - return rocket.raw_respond(HTTPStatus.NOT_FOUND) + def cgit_internal_server_error(msg): + print(f'cgit: Error {msg} at path_info "{cgit_env["PATH_INFO"]}"' + f' and query string "{cgit_env["QUERY_STRING"]}"', + file=sys.stderr) + return rocket.raw_respond(HTTPStatus.INTERNAL_SERVER_ERROR) proc = subprocess.Popen(['/usr/share/webapps/cgit/cgit'], stdout=subprocess.PIPE, @@ -460,14 +462,31 @@ def handle_cgit(rocket): env=cgit_env) so, se = proc.communicate() try: - outstring = so.decode() - begin = outstring.index('\n\n') - return rocket.respond(outstring[begin+2:]) - except (UnicodeDecodeError, ValueError) as ex: - print(f'cgit: Error {type(ex)} at path_info "{cgit_env["PATH_INFO"]}"' - f' and query string "{cgit_env["QUERY_STRING"]}"', - file=sys.stderr) - return rocket.raw_respond(HTTPStatus.INTERNAL_SERVER_ERROR) + raw_headers, raw_body = so.split(b'\n\n', maxsplit=1) + headers_text = raw_headers.decode() + headers = [tuple(line.split(': ', maxsplit=1)) + for line in headers_text.split('\n')] + raw_return = False + status = HTTPStatus.OK + if headers[0][0] == 'Status': + status_str = headers[0][1] + status = HTTPStatus(int(status_str.split(' ')[0])) + if status == HTTPStatus.OK: + return cgit_internal_server_error('Unexpected 200 status') + raw_return = True + raw_body = b'' + del headers[0] + if headers[0][0] != 'Content-Type': + return cgit_internal_server_error('missing Content-Type') + if headers[0][1] != 'text/html; charset=UTF-8': + raw_return = True + rocket.headers += headers + if raw_return: + return rocket.raw_respond(status, raw_body) + outstring = raw_body.decode() + return rocket.respond(outstring) + except (UnicodeDecodeError, ValueError, IndexError) as ex: + return cgit_internal_server_error(type(ex)) def handle_error(rocket): From dd89bf0e66e44dfaef307a2e72f75f8bee62720b Mon Sep 17 00:00:00 2001 From: charliemirabile <46761267+charliemirabile@users.noreply.github.com> Date: Wed, 4 Sep 2024 18:00:01 -0400 Subject: [PATCH 3/6] orbit: cgit: add support for http clone with authentication We can wire up cgit to request credentials from a git client during a clone operation via the basic authentication scheme that is part of HTTP and verify them so that course materials can be hosted entirely in cgit. --- orbit/cgitrc | 3 --- orbit/radius.py | 20 +++++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/orbit/cgitrc b/orbit/cgitrc index f3888393..af0b29ec 100644 --- a/orbit/cgitrc +++ b/orbit/cgitrc @@ -1,6 +1,3 @@ -# Allow http transport git clone -enable-http-clone=0 - # Allow cgit to use git config to set repo-specific settings enable-git-config=1 diff --git a/orbit/radius.py b/orbit/radius.py index a7fe1771..ebb50494 100644 --- a/orbit/radius.py +++ b/orbit/radius.py @@ -2,6 +2,7 @@ # # it's all one things now +import base64 import bcrypt import html import markdown @@ -443,9 +444,26 @@ def form_respond():