Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 88 additions & 103 deletions apollo-cli.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
#!/usr/bin/env python3
import argparse
import json
import os
import string
import sys
import urllib.error
import urllib.request
from http.cookies import SimpleCookie


API_BASE = os.environ.get("APOLLO_SERVER", "http://127.0.0.1:8080").rstrip("/") + "/api"
DEFAULT_PUZZLE_VALUE = 32
PROG = "apollo-cli.py"


def usage():
print(
"usage: apollo-cli.py <command> [args...]\n"
"\n"
"set_admin_password <initial_password> <password>\n"
"create_puzzle <id> <solution> <password>\n"
"join <username>\n"
"auth_state <username>\n"
"submit <username> <id> <solution>\n"
"logout <username>\n"
"mock_puzzles <from_id_int> <to_id_int> <password>\n"
"mock_join <from_char> <to_char>",
file=sys.stderr,
)
def cli_error(message):
raise SystemExit(f"{PROG}: error: {message}")


def cookie_path(username):
Expand All @@ -43,11 +32,9 @@ def load_user_cookie(username):
with open(path, "r", encoding="utf-8") as f:
sid = f.read().strip()
except FileNotFoundError:
print(f"missing cookie file: {path}", file=sys.stderr)
raise SystemExit(1)
cli_error(f"missing cookie file: {path}")
if not sid:
print(f"empty cookie file: {path}", file=sys.stderr)
raise SystemExit(1)
cli_error(f"empty cookie file: {path}")
return sid


Expand Down Expand Up @@ -91,11 +78,10 @@ def request_json(method, path, payload=None, sid=None):
if text:
print_response_text(text)
else:
print(f"HTTP {err.code}", file=sys.stderr)
cli_error(f"HTTP {err.code}")
raise SystemExit(1)
except urllib.error.URLError as err:
print(f"request failed: {err.reason}", file=sys.stderr)
raise SystemExit(1)
cli_error(f"request failed: {err.reason}")


def extract_sid(set_cookie_headers):
Expand All @@ -104,135 +90,134 @@ def extract_sid(set_cookie_headers):
cookie.load(header)
if "sid" in cookie:
return cookie["sid"].value
print("missing sid cookie in response", file=sys.stderr)
raise SystemExit(1)
cli_error("missing sid cookie in response")


def cmd_set_admin_password(args):
if len(args) != 2:
usage()
raise SystemExit(1)
_, _, text = request_json("POST", "/set_admin_password", {"init_password": args[0], "password": args[1]})
_, _, text = request_json(
"POST",
"/set_admin_password",
{"init_password": args.initial_password, "password": args.password},
)
print_response_text(text)


def cmd_create_puzzle(args):
if len(args) != 3:
usage()
raise SystemExit(1)
puzzle_id, solution, password = args
payload = {
"puzzle_solutions": {
puzzle_id: {
"solution": solution,
args.id: {
"solution": args.solution,
"value": DEFAULT_PUZZLE_VALUE,
}
},
"password": password,
"password": args.password,
}
_, _, text = request_json("POST", "/set_solution", payload)
print_response_text(text)


def cmd_join(args):
if len(args) != 1:
usage()
raise SystemExit(1)
username = args[0]
_, headers, text = request_json("POST", "/join", {"username": username})
_, headers, text = request_json("POST", "/join", {"username": args.username})
sid = extract_sid(headers.get_all("Set-Cookie", []))
save_user_cookie(username, sid)
save_user_cookie(args.username, sid)
print_response_text(text)


def cmd_auth_state(args):
if len(args) != 1:
usage()
raise SystemExit(1)
sid = load_user_cookie(args[0])
sid = load_user_cookie(args.username)
_, _, text = request_json("GET", "/auth_state", sid=sid)
print_response_text(text)


def cmd_submit(args):
if len(args) != 3:
usage()
raise SystemExit(1)
username, puzzle_id, solution = args
sid = load_user_cookie(username)
payload = {"puzzle_id": puzzle_id, "solution": solution}
sid = load_user_cookie(args.username)
payload = {"puzzle_id": args.id, "solution": args.solution}
_, _, text = request_json("POST", "/submit", payload, sid=sid)
print_response_text(text)


def cmd_logout(args):
if len(args) != 1:
usage()
raise SystemExit(1)
username = args[0]
sid = load_user_cookie(username)
sid = load_user_cookie(args.username)
_, _, text = request_json("POST", "/logout", {}, sid=sid)
delete_user_cookie(username)
delete_user_cookie(args.username)
print_response_text(text)


def cmd_mock_puzzles(args):
if len(args) != 3:
usage()
raise SystemExit(1)
start, end, password = args
start_int = int(start)
end_int = int(end)
for current in range(start_int, end_int + 1):
cmd_create_puzzle([str(current), str(current * 10), password])
for current in range(args.from_id_int, args.to_id_int + 1):
cmd_create_puzzle(
argparse.Namespace(
id=str(current), solution=str(current * 10), password=args.password
)
)


def cmd_mock_join(args):
if len(args) != 2:
usage()
raise SystemExit(1)
start_char, end_char = args
if (
len(start_char) != 1
or len(end_char) != 1
or start_char not in string.ascii_lowercase
or end_char not in string.ascii_lowercase
):
print("mock_join expects lowercase ascii characters", file=sys.stderr)
raise SystemExit(1)
if start_char > end_char:
print("mock_join expects <from_char> <= <to_char>", file=sys.stderr)
raise SystemExit(1)
start_idx = string.ascii_lowercase.index(start_char)
end_idx = string.ascii_lowercase.index(end_char)
if args.from_char > args.to_char:
cli_error("mock_join expects <from_char> <= <to_char>")
start_idx = string.ascii_lowercase.index(args.from_char)
end_idx = string.ascii_lowercase.index(args.to_char)
for username in string.ascii_lowercase[start_idx : end_idx + 1]:
cmd_join([f"user-{username}"])
cmd_join(argparse.Namespace(username=f"user-{username}"))


COMMANDS = {
"set_admin_password": cmd_set_admin_password,
"create_puzzle": cmd_create_puzzle,
"join": cmd_join,
"auth_state": cmd_auth_state,
"submit": cmd_submit,
"logout": cmd_logout,
"mock_puzzles": cmd_mock_puzzles,
"mock_join": cmd_mock_join,
}
def lower_char(value):
if len(value) != 1 or value not in string.ascii_lowercase:
raise argparse.ArgumentTypeError("expected a lowercase ascii character")
return value


def main():
if len(sys.argv) < 2:
usage()
raise SystemExit(1)
def build_parser():
parser = argparse.ArgumentParser(prog=PROG)
subparsers = parser.add_subparsers(dest="command", required=True)

command = sys.argv[1]
handler = COMMANDS.get(command)
if handler is None:
usage()
raise SystemExit(1)
p = subparsers.add_parser("set_admin_password")
p.add_argument("initial_password")
p.add_argument("password")
p.set_defaults(func=cmd_set_admin_password)

p = subparsers.add_parser("create_puzzle")
p.add_argument("id")
p.add_argument("solution")
p.add_argument("password")
p.set_defaults(func=cmd_create_puzzle)

p = subparsers.add_parser("join")
p.add_argument("username")
p.set_defaults(func=cmd_join)

p = subparsers.add_parser("auth_state")
p.add_argument("username")
p.set_defaults(func=cmd_auth_state)

handler(sys.argv[2:])
p = subparsers.add_parser("submit")
p.add_argument("username")
p.add_argument("id")
p.add_argument("solution")
p.set_defaults(func=cmd_submit)

p = subparsers.add_parser("logout")
p.add_argument("username")
p.set_defaults(func=cmd_logout)

p = subparsers.add_parser("mock_puzzles")
p.add_argument("from_id_int", type=int)
p.add_argument("to_id_int", type=int)
p.add_argument("password")
p.set_defaults(func=cmd_mock_puzzles)

p = subparsers.add_parser("mock_join")
p.add_argument("from_char", type=lower_char)
p.add_argument("to_char", type=lower_char)
p.set_defaults(func=cmd_mock_join)

return parser


def main():
parser = build_parser()
args = parser.parse_args()
args.func(args)


if __name__ == "__main__":
Expand Down
Loading