diff --git a/container-compose-dev.yml b/container-compose-dev.yml
index 9b48fd88..b0307857 100644
--- a/container-compose-dev.yml
+++ b/container-compose-dev.yml
@@ -17,5 +17,5 @@ services:
volumes:
- type: bind
source: ./kdlp.underground.software
- target: /orbit/docs
+ target: /usr/local/share/orbit/docs
read_only: true
diff --git a/orbit/.dockerignore b/orbit/.dockerignore
deleted file mode 100644
index 1b30444a..00000000
--- a/orbit/.dockerignore
+++ /dev/null
@@ -1 +0,0 @@
-orbit.db
diff --git a/orbit/Containerfile b/orbit/Containerfile
index 856ff967..a31ef9ff 100644
--- a/orbit/Containerfile
+++ b/orbit/Containerfile
@@ -1,24 +1,14 @@
FROM alpine:3.19 AS build
RUN apk update && apk upgrade && apk add \
- python3-dev \
- py3-pip \
- build-base \
- libffi-dev \
+ py3-peewee \
envsubst \
;
-COPY requirements.txt /requirements.txt
-RUN python3 -m venv /radius-venv && \
- source /radius-venv/bin/activate && \
- pip install -r requirements.txt && \
- :
-
-COPY . /orbit
-WORKDIR /orbit
+COPY . /usr/local/share/orbit
+WORKDIR /usr/local/share/orbit
RUN mkdir -p /var/orbit/ && \
- source /radius-venv/bin/activate && \
./db.py \
:
@@ -29,35 +19,31 @@ RUN test -n "$orbit_version_info" || (echo 'version info is not set' && false) &
rm config.py.template \
;
-RUN mkdir \
- /var/git \
- /orbit/docs \
- /etc/cgit \
- ;
-
-COPY --from=orbit_singularity_git_dir . /var/git/singularity
-COPY --from=orbit_docs_source . /orbit/docs
-COPY --from=orbit_repos_source . /etc/cgit
-
FROM alpine:3.19 AS orbit
RUN apk update && apk upgrade && apk add \
- python3 \
+ py3-bcrypt \
+ py3-peewee \
+ py3-markdown \
+ uwsgi-python3 \
+ uwsgi-http \
cgit \
;
-COPY --from=build /orbit /orbit
-COPY --from=build /radius-venv /radius-venv
+WORKDIR /usr/local/share/orbit
+
+COPY --from=build /usr/local/share/orbit /usr/local/share/orbit
+COPY --from=orbit_docs_source . ./docs
COPY --from=build /var/orbit /var/orbit
-COPY --from=build /var/git /var/git
-COPY --from=build /etc/cgit /etc/cgit
+COPY --from=orbit_singularity_git_dir . /var/git/singularity
+COPY --from=orbit_repos_source . /etc/cgit
COPY cgitrc /etc/cgitrc
-RUN chown -R 100:100 /orbit /radius-venv /var/orbit /var/git
+RUN chown -R 100:100 /var/orbit
USER 100:100
EXPOSE 9098
-CMD /bin/sh -c "source /radius-venv/bin/activate && uwsgi /orbit/radius.ini"
+CMD ["uwsgi", "--plugin", "python,http", "./radius.ini"]
diff --git a/orbit/README.md b/orbit/README.md
deleted file mode 100644
index 8ebcc009..00000000
--- a/orbit/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# orbit
-
-For development, setup and enter a python virtualenv for
-(e.g. `python -m venv orbit-dev && source orbit-dev/bin/activate`)
-and install the development dependencies: `pip install -r dev-requirements.txt`
-to enable style checking via `test-style.sh` utilizing flake8.
diff --git a/orbit/config.py b/orbit/config.py
index 67b781d1..07fe92a3 100644
--- a/orbit/config.py
+++ b/orbit/config.py
@@ -1,9 +1,8 @@
version_info = '${orbit_version_info}'
# read these documents from a filesystem path
-orbit_root = '/orbit'
-doc_root = f'{orbit_root}/docs'
-doc_header = f'{orbit_root}/header.html'
+doc_root = './docs'
+doc_header = './header.html'
database = '/var/orbit/orbit.db'
# duration of authentication token validity period
diff --git a/orbit/dev-requirements.txt b/orbit/dev-requirements.txt
deleted file mode 100644
index 2c9e2a97..00000000
--- a/orbit/dev-requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-flake8 ~=7.0.0
-mccabe ~= 0.7.0
-pycodestyle ~= 2.11.1
-pyflakes ~= 3.2.0
diff --git a/orbit/hyperspace.py b/orbit/hyperspace.py
index f8f39260..6c750901 100755
--- a/orbit/hyperspace.py
+++ b/orbit/hyperspace.py
@@ -8,7 +8,6 @@
# internal imports
import config
-from radius import Session
def errx(msg):
@@ -16,77 +15,38 @@ def errx(msg):
exit(1)
-def need(a, u=False, p=False, t=False):
+def need(a, u=False, p=False):
+ needed = []
if u and a.username is None:
- errx("Need username. Bye.")
+ needed.append('username')
if p and a.password is None:
- errx("Need password. Bye.")
- if t and a.token is None:
- errx("Need token. Bye.")
+ needed.append('password')
+ if needed:
+ errx(f"Need {' and '.join(needed)}. Bye.")
def nou(u):
errx(f'no such user "{u}". Bye.')
-def do_query_username(args):
- need(args, u=True)
- if not (user := db.User.get_or_none(db.User.username == args.username)):
- nou(args.username)
- print(f'Username : {user.username}\n'
- f'Hashed Password : {user.pwdhash}\n'
- f'Student ID : {user.student_id}')
-
-
-def do_validate_token(args):
- need(args, t=True)
-
- ses = db.Session.get_or_none(db.Session.token == args.token)
- if ses:
- print(ses.username)
- else:
- print('null')
-
-
def do_drop_session(args):
need(args, u=True)
query = (db.Session
.delete()
- .where(db.Session.username == args.username)
- .returning(db.Session))
-
- if ses := next(iter(query.execute()), None):
- print(ses.username)
- else:
- print('null')
-
-
-def do_create_session(args):
- need(args, u=True)
- ses = Session(username=args.username)
- print(ses.token)
-
+ .where(db.Session.username == args.username))
-def do_validate_creds(args):
- need(args, u=True, p=True)
- if not (user := db.User.get_or_none(db.User.username == args.username)):
- nou(args.username)
- if not bcrypt.checkpw(args.password.encode('utf-8'),
- user.pwdhash.encode('utf-8')):
- print('null')
- return
- print(f'credentials(username: {args.username}, password:{args.password})')
+ if query.execute() < 1:
+ errx('No session belonging to that user found')
def do_change_password(args):
need(args, u=True, p=True)
- new_hash = do_bcrypt_hash(args, get=True)
+ new_hash = do_bcrypt_hash(args)
query = (db.User
.update({db.User.pwdhash: new_hash})
.where(db.User.username == args.username))
if query.execute() < 1:
nou(args.username)
- print(f'credentials(username: {args.username}, password:{args.password})')
def do_delete_user(args):
@@ -96,22 +56,17 @@ def do_delete_user(args):
.where(db.User.username == args.username))
if query.execute() < 1:
nou(args.username)
- print(args.username)
-def do_bcrypt_hash(args, get=False):
+def do_bcrypt_hash(args):
need(args, p=True)
- res = str(bcrypt.hashpw(bytes(args.password, "UTF-8"),
- bcrypt.gensalt()), "UTF-8")
- if get:
- return res
- else:
- print(res)
+ return bcrypt.hashpw(args.password.encode('utf-8'),
+ bcrypt.gensalt()).decode('utf-8')
def do_newuser(args):
need(args, u=True, p=True)
- new_hash = do_bcrypt_hash(args, get=True)
+ new_hash = do_bcrypt_hash(args)
try:
db.User.create(username=args.username, pwdhash=new_hash,
student_id=args.studentid)
@@ -119,7 +74,6 @@ def do_newuser(args):
db.Registration.create(username=args.username,
password=args.password,
student_id=args.studentid)
- do_validate_creds(args)
except db.peewee.IntegrityError as e:
errx(f'cannot create user with duplicate field: "{e}"')
@@ -145,9 +99,6 @@ def hyperspace_main(raw_args):
parser.add_argument('-u', '--username', help='Username to operate with')
parser.add_argument('-p', '--password', help='Password to operate with')
parser.add_argument('-i', '--studentid', help='Student ID to operate with')
- parser.add_argument('-t', '--token', help='Token to operate with')
- parser.add_argument('-e', '--exercise',
- help='Assignment/Exercise to operate with')
actions = parser.add_mutually_exclusive_group()
actions.add_argument('-r', '--roster', action='store_const',
@@ -156,39 +107,24 @@ def hyperspace_main(raw_args):
actions.add_argument('-n', '--newuser', action='store_const',
help='Create a new user from supplied credentials',
dest='do', const=do_newuser)
- actions.add_argument('-s', '--session', action='store_const',
- help='Check valitity of supplied token',
- dest='do', const=do_validate_token)
- actions.add_argument('-d', '--dropsession', action='store_const',
- help='Drop any existing valid session for supplied username', # NOQA: E501
- dest='do', const=do_drop_session)
- actions.add_argument('-c', '--createsession', action='store_const',
- help='Create session for supplied username',
- dest='do', const=do_create_session)
- actions.add_argument('-v', '--validatecreds', action='store_const',
- help='Create session for supplied username',
- dest='do', const=do_validate_creds)
actions.add_argument('-m', '--mutatepassword', action='store_const',
help='Change password for supplied username to supplied password', # NOQA: E501
dest='do', const=do_change_password)
actions.add_argument('-w', '--withdrawuser', action='store_const',
help='Delete ("withdraw") the supplied username',
dest='do', const=do_delete_user)
- actions.add_argument('-b', '--bcrypthash', action='store_const',
- help='Generate bcrypt hash from supplied password',
- dest='do', const=do_bcrypt_hash)
actions.add_argument('-l', '--listsessions', action='store_const',
help='List of all known sessions (some could be invalid)', # NOQA: E501
dest='do', const=do_list_sessions)
- actions.add_argument('-q', '--queryuser', action='store_const',
- help='Get information about supplied username if valid', # NOQA: E501
- dest='do', const=do_query_username)
+ actions.add_argument('-d', '--dropsession', action='store_const',
+ help='Drop any existing valid session for supplied username', # NOQA: E501
+ dest='do', const=do_drop_session)
args = parser.parse_args(raw_args)
if (args.do):
args.do(args)
else:
- print("Nothing to do. Tip: -h")
+ parser.print_help()
if __name__ == "__main__":
diff --git a/orbit/radius.ini b/orbit/radius.ini
index bb303375..5d4696c0 100644
--- a/orbit/radius.ini
+++ b/orbit/radius.ini
@@ -1,6 +1,5 @@
[uwsgi]
http = 0.0.0.0:9098
-chdir = /orbit
wsgi-file = radius.py
disable-logging
diff --git a/orbit/radius.py b/orbit/radius.py
index 5c54cb8f..1de2f79b 100644
--- a/orbit/radius.py
+++ b/orbit/radius.py
@@ -3,13 +3,13 @@
# it's all one things now
import bcrypt
-import hashlib
import html
import markdown
import os
import re
import subprocess
import sys
+import secrets
from http import HTTPStatus, cookies
from datetime import datetime, timedelta
from urllib.parse import parse_qs, urlparse
@@ -27,34 +27,10 @@
# === utilities ===
-def encode(dat): return bytes(dat, "UTF-8")
-def decode(dat): return str(dat, "UTF-8")
-
-
-def mk_table(row_list, indentation_level=0):
- # Create
elements in first row, and | elements afterwards
- first_row = True
- def indenter(adjustment): return '\t' * (indentation_level + adjustment)
-
- output = f'{indenter(0)}'
- for row in row_list:
- output += f'{indenter(1)}'
- for column in row:
- if first_row:
- output += f'{indenter(2)}| {column} | '
- first_row = False
- else:
- output += f'{indenter(2)}{column} | '
- output += f'{indenter(1)} '
- output += f'{indenter(0)} '
-
- return output
-
-
def check_credentials(username, password):
if not (user := db.User.get_or_none(db.User.username == username)):
return False
- return bcrypt.checkpw(encode(password), encode(user.pwdhash))
+ return bcrypt.checkpw(password.encode(), user.pwdhash.encode())
# === user session handling ===
@@ -141,8 +117,7 @@ def valid(self):
return self.token
def mk_hash(self, username):
- hash_input = username + str(datetime.now())
- return hashlib.sha256(encode(hash_input)).hexdigest()
+ return secrets.token_hex()
def expired(self):
if (expiry := self.expiry) is None or datetime.utcnow() > expiry:
@@ -166,12 +141,6 @@ def mk_cookie_header(self):
return [('Set-Cookie', cookie_val)]
- def __repr__(self):
- return f'Session({self.token},{self.username},{self.expiry})'
-
- def __str__(self):
- return repr(self)
-
class Rocket:
"""
@@ -226,16 +195,6 @@ def __init__(self, env, start_response):
self.headers = []
self.body_args = self.read_body_args_wsgi()
- def __repr__(self):
- return (
- f'Rocket({self.method},{self.path_info},{self.queries},'
- f'{str(self.headers)},{self._msg},{str(self.session)},'
- f'{self.body_args})'
- )
-
- def __str__(self):
- return repr(self)
-
def msg(self, msg):
self._msg = msg
@@ -264,19 +223,9 @@ def username(self):
if session := self.session:
return session.username
- @property
- def token(self):
- if session := self.session:
- return session.token
-
- @property
- def expiry(self):
- if session := self.session:
- return session.expiry
-
def body_args_query(self, key):
return html.escape(
- decode(self.body_args.get(encode(key), [b''])[0]))
+ self.body_args.get(key.encode(), [b''])[0].decode())
def queries_query(self, key):
return self.queries.get(key, [''])[0]
@@ -320,31 +269,38 @@ def raw_respond(self, response_code, body=b''):
def respond(self, response_document):
self.headers += [('Content-Type', 'text/html')]
response_document = self.format_html(response_document)
- return self.raw_respond(HTTPStatus.OK, encode(response_document))
+ return self.raw_respond(HTTPStatus.OK, response_document.encode())
-form_welcome_template = """
+def mk_form_welcome(session):
+ return f'''
- {}
+
+ | Cookie Key | Value |
+ | Token | {session.token} |
+ | User | {session.username} |
+ | Expiry | {session.expiry_fmt()} |
+
Welcome!
- {}
-
-""".strip()
+
+ '''
-form_welcome_buttons = """
-
-""".strip() # NOQA: E501
-form_login = """
-
- Need an account? Register here
-""".strip()
-
-
-def cookie_info_table(session):
- return mk_table([
- ('Cookie Key', 'Value'),
- ('Token', session.token),
- ('User', session.username),
- ('Expiry', session.expiry_fmt()),
- ('Remaining Validity', str(session.expiry - datetime.utcnow()))])
-
-
-def mk_form_welcome(session):
- return form_welcome_template.format(cookie_info_table(session),
- form_welcome_buttons)
+ Need an account? Register here '''
def handle_login(rocket):
@@ -384,12 +325,11 @@ def respond(welcome):
rocket.headers += [('Location', target)]
return rocket.raw_respond(HTTPStatus.SEE_OTHER)
elif target:
- return rocket.respond(form_login % ({'target_redir':
- f'?target={target}'}))
+ return rocket.respond(login_form(target_location=target))
elif welcome:
return rocket.respond(mk_form_welcome(rocket.session))
else:
- return rocket.respond(form_login % ({'target_redir': ''}))
+ return rocket.respond(login_form())
if rocket.session:
rocket.msg(f'{rocket.username} authenticated by token')
@@ -442,25 +382,15 @@ def handle_dashboard(rocket):
return handle_stub(rocket, ['dashboard in development, check back later'])
-form_register = """
+def handle_register(rocket):
+ def form_respond():
+ return rocket.respond('''
-""".strip()
-
+ ''')
-register_response = """
-Save these credentials, you will not be able to access them again
-Username: %(username)s
-Password: %(password)s
-""".strip()
-
-
-def handle_register(rocket):
- def form_respond():
- return rocket.respond(form_register)
if rocket.method != 'POST':
return form_respond()
if not (student_id := rocket.body_args_query('student_id')):
@@ -474,10 +404,10 @@ def form_respond():
rocket.msg('no such student')
return form_respond()
rocket.msg('welcome to the classroom')
- return rocket.respond((register_response % {
- 'username': registration.username,
- 'password': registration.password,
- }))
+ return rocket.respond(f'''
+ Save these credentials, you will not be able to access them again
+ Username: {registration.username}
+ Password: {registration.password} ''')
def handle_cgit(rocket):
@@ -497,7 +427,7 @@ def handle_cgit(rocket):
env=cgit_env)
so, se = proc.communicate()
try:
- outstring = str(so, 'UTF-8')
+ outstring = so.decode()
begin = outstring.index('\n\n')
return rocket.respond(outstring[begin+2:])
except (UnicodeDecodeError, ValueError) as ex:
diff --git a/orbit/requirements.txt b/orbit/requirements.txt
deleted file mode 100644
index a1543266..00000000
--- a/orbit/requirements.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-bcrypt ~= 3.1.6
-certbot ~= 2.6.0
-Markdown ~= 3.3.7
-urllib3 ~= 1.26.16
-uWSGI ~= 2.0.21
-uwsgi-tools ~= 1.1.1
-peewee ~= 3.17.1
diff --git a/orbit/warpdrive.sh b/orbit/warpdrive.sh
index ed4ef082..19344730 100755
--- a/orbit/warpdrive.sh
+++ b/orbit/warpdrive.sh
@@ -12,7 +12,4 @@ require "${DOCKER}"
CONTAINER=${CONTAINER:-singularity_orbit_1}
-cat < |