lyadmin/app.py

230 lines
6.2 KiB
Python
Raw Normal View History

2020-11-26 18:30:35 -05:00
import glob
2020-11-26 18:56:41 -05:00
import json
import re
import sshpubkeys
2020-11-21 19:41:37 -05:00
from flask import Flask, redirect, url_for, render_template, request
2020-11-26 19:13:45 -05:00
# lyadmin
# scripts and web form for a tilde / PAUS instance
#
# gashapwn
# Nov 2020
#
# https://git.lain.church/gashapwn/lyadmin
# gashapwn@protonmail.com
# or
# gasahwpn on irc.lainchan.org
2020-11-26 18:56:41 -05:00
app=Flask(__name__)
2020-11-26 19:13:45 -05:00
# Paths for conf file,
# user list,
# directory containing
# account request files...
2020-11-26 18:30:35 -05:00
WORKING_DIR = "/home/gashapwn/lyadmin/";
2020-11-27 01:07:33 -05:00
ACCOUNT_DIR = "req/";
2020-11-26 18:30:35 -05:00
FULL_PATH = str(WORKING_DIR) + str(ACCOUNT_DIR)
2020-11-26 18:56:41 -05:00
CONF_PATH = str(WORKING_DIR) + "lyadmin.conf.json"
# validation stuff
MAX_PUB_KEY_LEN = 5000
EMAIL_REGEX = "^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,10}$"
KEY_REGEX = "^[ -~]+$"
2020-11-27 20:46:10 -05:00
2020-11-26 18:30:35 -05:00
# Account requests are given ID numbers
# the first request will have the below
# id number
INIT_REQ_ID = "00000"
2020-11-26 19:13:45 -05:00
# Slurp the conf file
2020-11-26 18:56:41 -05:00
with open(CONF_PATH) as c: conf_json_str = c.read()
conf_obj = json.loads(conf_json_str)
2020-11-21 19:41:37 -05:00
2020-11-27 20:58:23 -05:00
# A list of all the shell enums
conf_obj["shell_tup_list"] = list(map(
2020-11-27 20:58:23 -05:00
lambda k : (
k, conf_obj["shell"][k]
),
list(conf_obj["shell"].keys())
))
2020-11-26 19:13:45 -05:00
# The main home page
2020-11-21 19:41:37 -05:00
@app.route("/")
def home():
app.route('/')
2020-11-26 01:53:20 -05:00
2020-11-26 19:13:45 -05:00
# Load the list of tilde users
# to generate links for
2020-11-26 01:53:20 -05:00
u_list = [];
with open("user_list.txt") as u_file:
2020-11-26 17:15:40 -05:00
for line in u_file.readlines():
u_list.append(line.strip());
2020-11-26 01:53:20 -05:00
return render_template("index.html", u_list=u_list, page_name="home")
# Generates the page with rule. No logic needed.
def rules():
return render_template("rules.html")
2020-11-21 19:41:37 -05:00
2020-11-26 19:13:45 -05:00
# Generate HTML for a form widget
2020-11-22 02:42:56 -05:00
def widg_fun(widg):
if(widg.w_type == "input"):
return "input id=id_%s name=%s type=text></input"%(
widg.w_name, widg.w_name
)
2020-11-22 02:42:56 -05:00
elif(widg.w_type == "textarea"):
return "textarea cols=40 id=id_%s name=%s rows=10 required=\"\""%(
widg.w_name, widg.w_name
)
2020-11-22 02:42:56 -05:00
elif(widg.w_type == "check"):
return "input id=id_%s name=%s type=checkbox required=\"\""%(
widg.w_name, widg.w_name)
2020-11-22 02:42:56 -05:00
return widg.w_type;
2020-11-26 19:13:45 -05:00
# Generate HTML for request form
# probably a strange way to do this...
2020-11-22 01:24:41 -05:00
def req():
2020-11-26 17:06:39 -05:00
app.route('/req')
2020-11-22 02:42:56 -05:00
class Widg:
def __init__(self, w_name, w_type, w_opt):
self.w_name = w_name
self.w_type = w_type
2020-11-26 19:47:31 -05:00
self.w_opt = w_opt # only for choice type widg
2020-11-26 19:13:45 -05:00
# Configuration for our request form
2020-11-22 01:24:41 -05:00
rt = {
2020-11-27 20:46:10 -05:00
"username": Widg(
"username",
"input",
None
),
"email for account lockout / registration confirmation (optional)": Widg(
"email",
"input",
None
),
"SSH public key": Widg(
"pub_key",
"textarea",
None
),
"shell of choice": Widg(
"shell",
"choice",
conf_obj["shell_tup_list"]
2020-11-27 20:46:10 -05:00
),
"have you read the rules?": Widg(
"rule_read", "check", None
)
2020-11-22 01:24:41 -05:00
};
return render_template(
"req.html",
req_tab = rt,
widg_fun = widg_fun,
page_name="req"
)
def handle_invalid_data(req):
# print(str(e))
return render_template("signup.html", is_email_user = False)
# Process input from user creation POST request
def signup():
2020-11-26 17:06:39 -05:00
app.route('/req/signup')
2020-11-26 18:30:35 -05:00
2020-11-26 19:13:45 -05:00
# Get all the params from the POST
# request
username = request.form["username"].strip()
email = request.form["email"].strip()
pub_key = request.form["pub_key"].strip()
shell = request.form["shell"].strip()
rule_read = request.form["rule_read"].strip()
2020-11-26 17:06:39 -05:00
is_email_user = False;
2020-11-26 19:13:45 -05:00
# If a user didnt read the rules
# send them back
if(rule_read != "on"):
2020-11-26 17:06:39 -05:00
return redirect(url_for('req'))
2020-11-26 19:13:45 -05:00
# Set placeholder if user didnt send an email
2020-11-26 17:06:39 -05:00
if(len(email) > 1):
is_email_user = True
2020-11-26 18:30:35 -05:00
else:
email = "NO_EMAIL"
2020-11-27 20:58:23 -05:00
# Validate shell
if(not shell in conf_obj["shell"]):
print("failed shell validation")
return handle_invalid_data(req)
# Validate email
if( is_email_user and not re.search(EMAIL_REGEX, email)):
print("failed email validation")
return handle_invalid_data(req)
# Validate the SSH pub key
# Most software only handles up to 4096 bit keys
if(len(pub_key) > MAX_PUB_KEY_LEN):
print("key failed len check")
return handle_invalid_data(req)
# Only printable ascii characters in
# a valid key
# if(not re.search("^[ -~]+$", pub_key)):
if(not re.search(KEY_REGEX, pub_key)):
print("key failed regex")
return handle_invalid_data(req)
# Check the key against a library
key = sshpubkeys.SSHKey(
pub_key,
strict_mode=False,
skip_option_parsing=True
)
try:
key.parse()
except Exception as e:
print("key failed lib validation")
return handle_invalid_data(request)
2020-11-26 19:13:45 -05:00
# All users requests have a sequential ID
# The below picks the next ID based on
# how many requests we already have saved
# to disk
2020-11-27 01:07:33 -05:00
if(len(glob.glob(ACCOUNT_DIR + str("[0-9]*ident*"))) == 0):
2020-11-26 18:30:35 -05:00
new_id = int(INIT_REQ_ID)
new_id_str = INIT_REQ_ID
else:
2020-11-27 21:01:20 -05:00
max_id = max(
list(map(
lambda path : path.split("/")[-1].split(".")[0],
glob.glob(str(ACCOUNT_DIR) + "[0-9]*ident*")))
)
2020-11-26 18:30:35 -05:00
zpad = len(max_id)
new_id = int(max_id)+1
new_id_str = str(new_id).zfill(zpad)
2020-11-26 17:06:39 -05:00
2020-11-26 19:13:45 -05:00
# write the request to disk
2020-11-26 18:30:35 -05:00
fn1 = str(FULL_PATH) + str(new_id_str) + ".ident"
with open(fn1, "w") as ident_file:
ident_file.write(str(username) + "\n")
ident_file.write(str(email) + "\n")
ident_file.write(str(shell) + "\n")
ident_file.write(str(pub_key) + "\n")
2020-11-26 18:30:35 -05:00
2020-11-26 17:06:39 -05:00
return render_template("signup.html", is_email_user = is_email_user)
2020-11-21 19:41:37 -05:00
2020-11-26 20:01:11 -05:00
@app.context_processor
def get_site_name():
return {"site_name": conf_obj["site_name"]}
2020-11-21 19:41:37 -05:00
if __name__=="__main__":
app.add_url_rule('/rules', 'rules', rules)
2020-11-22 01:24:41 -05:00
app.add_url_rule('/req', 'req', req, methods = ['POST', 'GET'])
2020-11-26 17:06:39 -05:00
app.add_url_rule('/req/signup', 'signup', signup, methods = ['POST'])
2020-11-26 19:05:48 -05:00
app.run(host=conf_obj["listen_ip"],debug=True)