Source code for domains.main

import socket
from enum import Enum
from time import sleep

import requests
from flask import Flask, abort, redirect, request

from common.course_config import format_coursecode, is_admin
from common.db import connect_db
from common.html import html
from common.oauth_client import create_oauth_client, get_user, is_logged_in, login
from common.rpc.buildserver import get_base_hostname
from common.rpc.secrets import get_secret, validates_master_secret
from common.url_for import url_for
from common.rpc.domains import add_domain

APP_LOOKUP = {
    "oh": "oh",
    "hwparty": "oh",
    "joinme": "oh",
    "lab": "oh",
    "howamidoing": "howamidoing",
    "status": "howamidoing",
    "seating": "seating",
    "links": "shortlinks",
    "go": "shortlinks",
    "eecs": "shortlinks",
    # legacy prefixes
    "me100": "oh",
    "csenrolltest": "oh",
    "cs169-oh": "oh",
    "cs188-oh": "oh",
    "cs186-oh": "oh",
    "stat140-oh": "oh",
}


[docs]class Status(Enum): VALIDATING = "VALIDATING" DNS_INVALID = "DNS_INVALID" PROVISIONING = "PROVISIONING" PROVISIONING_FAILED = "PROVISIONING_FAILED" UPDATING_OAUTH = "UPDATING_OAUTH" INTERNAL_ERROR = "INTERNAL_ERROR" SUCCESS = "SUCCESS"
app = Flask(__name__, static_folder="", static_url_path="") if __name__ == "__main__": app.debug = True create_oauth_client(app, "61a-domains") with connect_db() as db: db( """CREATE TABLE IF NOT EXISTS hosted_apps ( domain VARCHAR(128), course VARCHAR(128), app VARCHAR(128), status VARCHAR(128) ) """ ) @app.route("/") def index(): if not is_logged_in(): return login() return html( """ Select course: <form method="post" action="/view_course"> <input placeholder="cs61a" name="course"> <input type="submit" value="Login"> </form>""" ) @app.route("/view_course", methods=["POST"]) @app.route("/view_course/<course>", methods=["GET"], endpoint="canonical_view_course") def view_course(course=None): if not course: course = request.form["course"] return redirect(url_for("canonical_view_course", course=course)) if not is_logged_in(): return login() email = get_user()["email"] if not is_admin(email, course): abort(403) with connect_db() as db: apps = db( "SELECT domain, app, status FROM hosted_apps WHERE course=(%s)", [course] ).fetchall() return html( f""" <h2>Hosted Apps for {format_coursecode(course)}</h2> {"<p>".join(f"<code>{domain}</code> ({app}) - {status}" for domain, app, status in apps)} """ )
[docs]def set_status(domain: str, status: Status): """Updates the status for a domain name :param domain: domain name :type domain: str :param status: new status of domain :type status: Status :return: None """ with connect_db() as db: db( "UPDATE hosted_apps SET status=(%s) WHERE domain=(%s)", [status.value, domain], )
[docs]@add_domain.bind(app) @validates_master_secret def add_domain(app, is_staging, course, domain): """Adds domain to hosted_apps table and hosts new domain :param app: "auth" :type app: str :param is_staging: whether app is staging or not :type is_staging: boolean :param course: course name :type course: str :param domain: domain name :type domain: str :return: None or Error """ try: if app != "auth": abort(401) app = APP_LOOKUP[domain.split(".")[0]] with connect_db() as db: status = db( "SELECT status FROM hosted_apps WHERE domain=(%s)", [domain] ).fetchone() if status is not None and status[0] == Status.SUCCESS: return "" # domain already provisioned db("DELETE FROM hosted_apps WHERE domain=(%s)", [domain]) with connect_db() as db: db( "INSERT INTO hosted_apps (domain, course, app, status) VALUES (%s, %s, %s, %s)", [domain, course, app, Status.VALIDATING.value], ) try: ip = socket.gethostbyname(domain) except socket.gaierror: ip = None if ip != socket.gethostbyname("proxy.cs61a.org"): set_status(domain, Status.DNS_INVALID) return set_status(domain, Status.PROVISIONING) try: requests.post( "https://proxy.cs61a.org/create_domain", json=dict( app=app, domain=domain, target=get_base_hostname(target_app=app), secret=get_secret(secret_name="DOMAIN_WEBHOOK_SECRET"), ), ).raise_for_status() except requests.exceptions.ConnectionError: pass # nginx restarts so the connection crashes sleep(5) if not requests.get(f"https://{domain}/").ok: set_status(domain, Status.PROVISIONING_FAILED) return set_status(domain, Status.UPDATING_OAUTH) # TODO set_status(domain, Status.SUCCESS) return except: set_status(domain, Status.INTERNAL_ERROR) raise
if __name__ == "__main__": app.run(debug=True)