def oauth(): if not request.args["code"]: return jsonify({"Error": "sadcat"}), 500 resp = requests.post( "https://slack.com/api/oauth.v2.access", { "code": request.args["code"], "client_id": get_secret(secret_name="CLIENT_ID"), "client_secret": get_secret(secret_name="CLIENT_SECRET"), }, ) if resp.status_code == 200: data = resp.json() bot_token = data["access_token"] workspace_data = requests.post( "https://slack.com/api/auth.test", headers={"Authorization": "Bearer {}".format(bot_token)}, ).json() workspace_url = workspace_data["url"] workspace = re.match( r"https://([a-zA-Z\-0-9]+)\.slack\.com", workspace_url ).group(1) store_bot_token(get_course(workspace), data["team"]["id"], bot_token) store_user_token( data["authed_user"]["id"], data["authed_user"]["access_token"] ) with connect_db() as db: db( "DELETE FROM silenced_users WHERE user = (%s)", [data["authed_user"]["id"]], ) return redirect(workspace_url) return jsonify({"Error": "sadcat"}), 500
def gen_env_variables(app: App, pr_number: int): if app.config["deploy_type"] == "hosted": database_url = sqlalchemy.engine.url.URL( drivername="mysql", host=DB_IP_ADDRESS, username="******", password=get_secret(secret_name="DATABASE_PW"), database=app.name.replace("-", "_"), ).__to_string__(hide_password=False) elif app.config["deploy_type"] in CLOUD_RUN_DEPLOY_TYPES: database_url = sqlalchemy.engine.url.URL( drivername="mysql+pymysql", username="******", password=get_secret(secret_name="DATABASE_PW"), database=app.name.replace("-", "_"), query={ "unix_socket": "{}/{}".format("/cloudsql", DB_INSTANCE_NAME) }, ).__to_string__(hide_password=False) else: database_url = None return dict( ENV="prod", DATABASE_URL=database_url, INSTANCE_CONNECTION_NAME=DB_INSTANCE_NAME, APP_MASTER_SECRET=gen_master_secret(app, pr_number), **(load_all_secrets( created_app_name=app.name) if pr_number == 0 else {}), )
def webhook(): if not hmac.compare_digest( "sha1=" + hmac.new( get_secret(secret_name="GITHUB_WEBHOOK_SECRET").encode("ascii"), request.get_data(), "sha1", ).hexdigest(), request.headers["X-Hub-Signature"], ): abort(401) payload = request.json g = Github(get_secret(secret_name="GITHUB_ACCESS_TOKEN")) if "pusher" in payload and payload["ref"] == "refs/heads/master": base_repo = g.get_repo(GITHUB_REPO) repo = g.get_repo(payload["repository"]["id"]) sha = payload["after"] land_commit( sha, repo, base_repo, None, [ file for commit in payload["commits"] for file in commit["added"] + commit["modified"] + commit["removed"] ], ) delete_unused_services() if "pull_request" in payload: repo_id = payload["repository"]["id"] repo = g.get_repo(repo_id) pr = repo.get_pull(payload["pull_request"]["number"]) if payload["action"] in ("opened", "synchronize", "reopened"): if repo.full_name != GITHUB_REPO: land_commit(pr.head.sha, repo, g.get_repo(GITHUB_REPO), pr, []) else: for target in determine_targets(repo, pr.get_files()): report_build_status( target, pr.number, pack(repo.clone_url, pr.head.sha), BuildStatus.pushed, None, None, private=True, ) elif payload["action"] == "closed": set_pr_comment("PR closed, shutting down PR builds...", pr) delete_unused_services(pr.number) set_pr_comment("All PR builds shut down.", pr) return ""
def add_domain(app, is_staging, course, domain): 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
def handle_deploy_prod_app_sync(app, is_staging, target_app): if app != "buildserver" or is_staging: abort(401) g = Github(get_secret(secret_name="GITHUB_ACCESS_TOKEN")) repo = g.get_repo(GITHUB_REPO) land_commit( repo.get_branch(repo.default_branch).commit.sha, repo, repo, None, [f"{target_app}/main.py"], )
def handle_trigger_build_sync(app, is_staging, pr_number, target_app=None): if app not in ("slack", "buildserver") or is_staging: raise PermissionError g = Github(get_secret(secret_name="GITHUB_ACCESS_TOKEN")) repo = g.get_repo(GITHUB_REPO) pr = repo.get_pull(pr_number) if DO_NOT_BUILD in [l.name for l in pr.labels]: raise PermissionError land_commit(pr.head.sha, repo, repo, pr, pr.get_files(), target_app=target_app)
def clear_queue(repo: str, pr_number: int): g = Github(get_secret(secret_name="GITHUB_ACCESS_TOKEN")) repo = g.get_repo(repo) pr = repo.get_pull(pr_number) if pr_number else None land_commit( pr.head.sha if pr else repo.get_branch(repo.default_branch).commit.sha, repo, g.get_repo(GITHUB_REPO), pr, [], dequeue_only=True, )
def export(name, gs_code): email = "*****@*****.**".strip() password = get_secret(secret_name="GRADESCOPE_PW").strip() print("Logging in...") gsapi = GradescopeAPIClient() if gsapi.log_in(email, password): gs = GradescopeClient() if gs.log_in(email, password): print("Logged in.\n") else: print("Frontend login failed :(", file=sys.stderr) sys.exit(1) else: print("Backend login failed :(", file=sys.stderr) sys.exit(1) print(f"Looking up {name}...") full_name = gs.get_assignment_name(COURSE_CODE, gs_code) if not full_name: print(f"Assignment for '{name}' not found :(", file=sys.stderr) sys.exit(1) print(f"Assignment '{full_name}' found. Downloading scores...") res = gs.download_scores(COURSE_CODE, gs_code) if res: p = Path(".").expanduser().absolute() if not p.exists(): p.parent.mkdir(parents=True, exist_ok=True) if not p.is_file(): p = p.joinpath(f"data/{name}.csv") with open(p, "wb+") as f: f.write(res) print("Done.\n") else: print(f"Download for '{full_name}' failed :(", file=sys.stderr) sys.exit(1) print("Converting to Okpy upload file...") gs_csv = pd.read_csv(f"data/{name}.csv") ok_csv = gs_csv[["SID", "Email", "Total Score"]] ok_csv["SID"] = ok_csv["SID"].fillna(0).astype(int).astype(str) ok_csv.to_csv(f"data/{name}.csv", index=False) print("Done.") return full_name
def run_pypi_deploy(app: App, pr_number: int): sh("python", "-m", "venv", "env") update_setup_py(app, pr_number) sh("env/bin/pip", "install", "setuptools") sh("env/bin/pip", "install", "wheel") sh("env/bin/python", "setup.py", "sdist", "bdist_wheel") sh( "twine", "upload", *(f"dist/{file}" for file in os.listdir("dist")), env=dict( TWINE_USERNAME="******", TWINE_PASSWORD=get_secret(secret_name="PYPI_PASSWORD"), ), )
def wrapped(*args, **kwargs): data = request.get_data().decode("utf-8") timestamp = request.headers["X-Slack-Request-Timestamp"] slack_signature = request.headers["X-Slack-Signature"] if abs(time.time() - int(timestamp)) > 60 * 5: abort(403) basestring = "v0:" + timestamp + ":" + data my_signature = ("v0=" + hmac.new( get_secret(secret_name="SIGNING_SECRET").encode(), basestring.encode(), hashlib.sha256, ).hexdigest()) if hmac.compare_digest(my_signature.encode(), slack_signature.encode()): return route(*args, **kwargs) else: abort(403)
def load_dependencies(app: App, sha: str, repo: Repository): g = Github(get_secret(secret_name="GITHUB_ACCESS_TOKEN")) def clone_repo(repo_str: str): cloned_repo = g.get_repo(repo_str) cloned_sha = (sha if cloned_repo.full_name == repo.full_name else cloned_repo.get_branch( cloned_repo.default_branch).commit.sha) clone_commit(cloned_repo.clone_url, cloned_sha, in_place=True) with tmp_directory(): for dependency in app.config["dependencies"]: folder_name = dependency["repo"].replace("/", "-") if not isdir(folder_name): # dependency is not already loaded mkdir(folder_name) chdir(folder_name) clone_repo(dependency["repo"]) chdir("..") try: copytree( os.path.join(folder_name, dependency["src"]), os.path.join(app.name, dependency["dest"]), symlinks=False, dirs_exist_ok=True, ) except NotADirectoryError: copyfile( os.path.join(folder_name, dependency["src"]), os.path.join(app.name, dependency["dest"]), ) if app.config["repo"]: working_dir = gen_working_dir(app) mkdir(working_dir) chdir(working_dir) clone_repo(app.config["repo"]) chdir("..")
def trigger_build(): if not is_staff("cs61a"): return login() email = get_user()["email"] if not is_admin(course="cs61a", email=email): abort(401) if "app" in request.args: target = request.args["app"] else: target = None pr_number = int(request.args["pr_number"]) g = Github(get_secret(secret_name="GITHUB_ACCESS_TOKEN")) repo = g.get_repo(GITHUB_REPO) pr = repo.get_pull(pr_number) if DO_NOT_BUILD in [l.name for l in pr.labels]: return html( f"PR <code>{pr_number}</code> has a DO NOT BUILD label on it, so it cannot be built. Remove this label to build the PR." ) trigger_build_sync(pr_number=pr_number, target_app=target, noreply=True) return html(f"Building PR <code>{pr_number}</code>!")
def create_oauth_client(app): oauth = OAuth(app) if os.getenv("ENV") == "prod": consumer_key = CONSUMER_KEY app.secret_key = get_secret(secret_name="OKPY_OAUTH_SECRET") else: consumer_key = "local-dev-all" app.secret_key = "kmSPJYPzKJglOOOmr7q0irMfBVMRFXN" if not app.debug: app.config.update( SESSION_COOKIE_SECURE=True, SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE="Lax", ) remote = oauth.remote_app( "ok-server", # Server Name consumer_key=consumer_key, consumer_secret=app.secret_key, request_token_params={ "scope": "all", "state": lambda: security.gen_salt(10) }, base_url="https://okpy.org/api/v3/", request_token_url=None, access_token_method="POST", access_token_url="https://okpy.org/oauth/token", authorize_url="https://okpy.org/oauth/authorize", ) def kill_popup(): response = app.make_response("<script> window.close(); </script>") response.delete_cookie(COOKIE_IS_POPUP) return response def check_req(uri, headers, body): """ Add access_token to the URL Request. """ if "access_token" not in uri and session.get("dev_token"): params = {"access_token": session.get("dev_token")[0]} url_parts = list(urllib.parse.urlparse(uri)) query = dict(urllib.parse.parse_qsl(url_parts[4])) query.update(params) url_parts[4] = urllib.parse.urlencode(query) uri = urllib.parse.urlunparse(url_parts) return uri, headers, body remote.pre_request = check_req @app.route("/oauth/popup_login") def popup_login(): response = remote.authorize( callback=url_for("authorized", _external=True)) response.set_cookie(COOKIE_IS_POPUP, value="") return response @app.route("/oauth/popup_logout") def popup_logout(): session.pop("dev_token", None) return kill_popup() @app.route("/oauth/login") def login(): response = remote.authorize( callback=url_for("authorized", _external=True)) response.delete_cookie(COOKIE_IS_POPUP) return response @app.route("/oauth/authorized") def authorized(): resp = remote.authorized_response() if resp is None: return "Access denied: error=%s" % (request.args["error"]) if isinstance(resp, dict) and "access_token" in resp: session["dev_token"] = (resp["access_token"], "") if COOKIE_IS_POPUP in request.cookies: return kill_popup() if COOKIE_SHORTLINK_REDIRECT in request.cookies: return app.load_file(request.cookies[COOKIE_SHORTLINK_REDIRECT]) else: return redirect("/") @app.route("/api/user", methods=["POST"]) def client_method(): if "dev_token" not in session: abort(401) token = session["dev_token"][0] r = requests.get( "https://okpy.org/api/v3/user/?access_token={}".format(token)) if not r.ok: abort(401) return jsonify(r.json()) @remote.tokengetter def get_oauth_token(): return session.get("dev_token") app.remote = remote
def create_oauth_client( app: flask.Flask, consumer_key, secret_key=None, success_callback=None, return_response=None, ): oauth = OAuth(app) if os.getenv("ENV") == "prod": if secret_key is None: app.secret_key = get_secret(secret_name="OKPY_OAUTH_SECRET") else: app.secret_key = secret_key else: consumer_key = "local-dev-all" app.secret_key = "kmSPJYPzKJglOOOmr7q0irMfBVMRFXN" if not app.debug: app.config.update( SESSION_COOKIE_SECURE=True, SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE="Lax", ) remote = oauth.remote_app( "ok-server", # Server Name consumer_key=consumer_key, consumer_secret=app.secret_key, request_token_params={ "scope": "all", "state": lambda: security.gen_salt(10) }, base_url="https://okpy.org/api/v3/", request_token_url=None, access_token_method="POST", access_token_url="https://okpy.org/oauth/token", authorize_url="https://okpy.org/oauth/authorize", ) def check_req(uri, headers, body): """ Add access_token to the URL Request. """ if "access_token" not in uri and session.get("access_token"): params = {"access_token": session.get("access_token")[0]} url_parts = list(urllib.parse.urlparse(uri)) query = dict(urllib.parse.parse_qsl(url_parts[4])) query.update(params) url_parts[4] = urllib.parse.urlencode(query) uri = urllib.parse.urlunparse(url_parts) return uri, headers, body remote.pre_request = check_req @app.route("/oauth/login") def login(): if app.debug: response = remote.authorize( callback=url_for("authorized", _external=True)) else: response = remote.authorize( url_for("authorized", _external=True, _scheme="https")) return response @app.route("/oauth/authorized") def authorized(): resp = remote.authorized_response() if resp is None: return "Access denied: error=%s" % (request.args["error"]) if isinstance(resp, dict) and "access_token" in resp: session["access_token"] = (resp["access_token"], "") if return_response: return_response(resp) if success_callback: success_callback() target = session.get(REDIRECT_KEY) if target: session.pop(REDIRECT_KEY) return redirect(target) return redirect(url_for("index")) @app.route("/api/user", methods=["POST"]) def client_method(): if "access_token" not in session: abort(401) token = session["access_token"][0] r = requests.get( "https://okpy.org/api/v3/user/?access_token={}".format(token)) if not r.ok: abort(401) return jsonify(r.json()) @remote.tokengetter def get_oauth_token(): return session.get("access_token") app.remote = remote
import json from flask import request, url_for, abort from werkzeug.utils import redirect from common.rpc.auth import get_endpoint, list_courses, slack_workspace_name from common.rpc.secrets import get_secret from common.db import connect_db from security import logged_in, get_staff_endpoints REJECTED = object() UNABLE = object() CLIENT_ID = get_secret(secret_name="CLIENT_ID") def get_add_to_slack_link(domain): return f"https://{domain}.slack.com/oauth/v2/authorize?client_id={CLIENT_ID}&scope=channels:join,channels:read,chat:write,users:read,users:read.email,groups:read&user_scope=channels:history,chat:write,groups:history,im:history,mpim:history,users:read" with open("config.json") as f: CONFIG = json.load(f) def init_db(): with connect_db() as db: db("""CREATE TABLE IF NOT EXISTS tokens ( user varchar(128), token text, PRIMARY KEY (`user`) );""")
# this is used to trigger the worker via Cloud Build import sys from github import Github from app_config import App from build import clone_commit from common.rpc.secrets import get_secret from conf import GITHUB_REPO from worker import land_app_worker if __name__ == "__main__": g = Github(get_secret(secret_name="GITHUB_ACCESS_TOKEN")) _, app_name, pr_number, sha, repo_id = sys.argv base_repo = g.get_repo(GITHUB_REPO) clone_commit( base_repo.clone_url, sha if repo_id == base_repo.full_name else base_repo.get_branch( base_repo.default_branch).commit.sha, ) land_app_worker(App(app_name), int(pr_number), sha, g.get_repo(repo_id))
def wrapped(**kwargs): secret = get_secret(secret_name="AUTH_SECRET") return func(**kwargs, secret=secret)
from common.db import database_url from common.rpc.secrets import get_secret basedir = os.path.abspath(os.path.dirname(__file__)) ENV = os.getenv("ENV", "dev") if ENV == "DEV_ON_PROD": ENV = "dev" if ENV == "dev": DEBUG = True SECRET_KEY = "dev" else: DEBUG = False SECRET_KEY = get_secret(secret_name="SESSION_COOKIE_SECRET") SQLALCHEMY_DATABASE_URI = database_url SQLALCHEMY_TRACK_MODIFICATIONS = False DATABASE_CONNECT_OPTIONS = {} LOCAL_TIMEZONE = os.getenv("TIMEZONE", "US/Pacific") AUTH_KEY = "oh-queue-staging" if os.getenv("IN_STAGING") else "oh-queue" if ENV == "dev": AUTH_SECRET = "" OK_KEY = "local-dev-email" OK_SECRET = "KH0mvknMUWT5w3U7zvz6wsUQZoy6UmQ" else:
def update_status(packed_ref: str, pr_number: int): g = Github(get_secret(secret_name="GITHUB_ACCESS_TOKEN")) repo_url, sha = unpack(packed_ref) repo_name = urlparse(repo_url).path.split(".")[0][ 1: ] # This is awful ... but it works repo = g.get_repo(repo_name) # First we will update the commit-specific status indicator with connect_db() as db: statuses = db( "SELECT app, status FROM builds WHERE packed_ref=%s", [packed_ref] ).fetchall() statuses = [(app, BuildStatus(status)) for app, status in statuses] if all(status == BuildStatus.success for _, status in statuses): repo.get_commit(sha).create_status( "success", "https://logs.cs61a.org/service/buildserver", "All modified services built!", "Pusher", ) elif any(status == BuildStatus.failure for _, status in statuses): repo.get_commit(sha).create_status( "failure", "https://logs.cs61a.org/service/buildserver", "Pusher failed to build a modified service", "Pusher", ) elif all( status in (BuildStatus.building, BuildStatus.queued) for _, status in statuses ): repo.get_commit(sha).create_status( "pending", "https://logs.cs61a.org/service/buildserver", "Pusher is building all modified services", "Pusher", ) else: # There are no failures, but not everything is building / built repo.get_commit(sha).create_status( "pending", "https://logs.cs61a.org/service/buildserver", "You must build all modified apps before merging", "Pusher", ) if pr_number == 0: return pr = repo.get_pull(pr_number) # Now we will update the PR comment, looking at builds for all packed_refs in the PR apps = determine_targets(repo, pr.get_files()) success = [] failure = [] running = [] queued = [] triggerable = [] with connect_db() as db: for app in apps: successful_build = db( "SELECT url, log_url, unix, packed_ref FROM builds WHERE app=%s AND pr_number=%s AND status='success' ORDER BY unix DESC LIMIT 1", [app, pr_number], ).fetchone() if successful_build: url, log_url, success_unix, packed_ref = successful_build _, sha = unpack(packed_ref) if url: for link in url.split(","): success.append((app, link, sha, log_url)) else: success.append((app, None, sha, log_url)) failed_build = db( "SELECT unix, log_url, packed_ref FROM builds WHERE app=%s AND pr_number=%s AND status='failure' ORDER BY unix DESC LIMIT 1", [app, pr_number], ).fetchone() if failed_build: unix, log_url, packed_ref = failed_build if not successful_build or success_unix < unix: _, sha = unpack(packed_ref) failure.append((app, sha, log_url)) running_build = db( "SELECT packed_ref FROM builds WHERE app=%s AND pr_number=%s AND status='building'", [app, pr_number], ).fetchone() if running_build: [packed_ref] = running_build _, sha = unpack(packed_ref) running.append((app, sha)) queued_build = db( "SELECT packed_ref FROM builds WHERE app=%s AND pr_number=%s AND status='queued'", [app, pr_number], ).fetchone() if queued_build: [packed_ref] = queued_build _, sha = unpack(packed_ref) queued.append((app, sha)) latest_commit_build = db( "SELECT * FROM builds WHERE app=%s AND pr_number=%s AND packed_ref=%s AND status!='pushed'", [app, pr_number, pack(repo_url, pr.head.sha)], ).fetchone() if not latest_commit_build: triggerable.append(app) if repo.name == "berkeley-cs61a": message = f"## Build Status ([pr/{pr_number}]({pr.html_url}))\n\n" elif repo.name == "cs61a-apps": message = f"## Build Status ([apps/{pr_number}]({pr.html_url}))\n\n" else: message = f"## Build Status (#{pr_number})\n\n" if success: message += ( "**Successful Builds**\n" + "\n".join( f" - [{host}](https://{host}) ({sha}) [[logs]({log_url})]" if host else f" - `{app}` ({sha}) [[logs]({log_url})]" for app, host, sha, log_url in success ) + "\n\n" ) if failure: message += ( "**Failed Builds**\n" + "\n".join( f" - `{app}` ({sha}) [[logs]({log_url})]" for app, sha, log_url in failure ) + "\n\n" ) if running: message += ( "**Running Builds**\n" + "\n".join(f" - `{app}` ({sha})" for app, sha in running) + "\n\n" ) if queued: message += ( "**Queued Builds**\n" + "\n".join(f" - `{app}` ({sha})" for app, sha in queued) + "\n\n" ) if (success or failure or running or queued) and triggerable: message += "-----\n" if triggerable: message += ( f"**[Click here]({url_for('trigger_build', pr_number=pr.number)})** to trigger all builds " f"for the most recent commit ({pr.head.sha})\n\n" "Or trigger builds individually:\n" ) + "\n".join( f" - [Click here]({url_for('trigger_build', pr_number=pr.number, app=app)}) " f"to build `{app}` at the most recent commit ({pr.head.sha})" for app in triggerable ) set_pr_comment(message, pr)
"Are you sure? You are about to use the PROD database on a DEV build (y/N) " ) .strip() .lower() == "y" ) if "cloud_sql_proxy" not in os.listdir(curr): print( "Follow the instructions at https://cloud.google.com/sql/docs/mysql/sql-proxy to install the proxy " "in the root directory of the repository, then try again." ) exit(0) database_url = sqlalchemy.engine.url.URL( drivername="mysql+pymysql", username="******", password=get_secret(secret_name="DATABASE_PW"), host="127.0.0.1", port=3307, database=app, ).__to_string__(hide_password=False) else: database_url = getenv("DATABASE_URL") engine = sqlalchemy.create_engine( database_url, **( {} if use_devdb else dict(pool_size=5, max_overflow=2, pool_timeout=30, pool_recycle=1800) ) )
from common.rpc.hosted import add_domain from common.rpc.secrets import get_secret from common.shell_utils import sh from common.html import html from common.url_for import get_host, url_for from common.db import connect_db NGINX_PORT = os.environ.get("PORT", "8001") DEFAULT_USER = "******" SK_RETURN_TO = "start_kill_return_to" app = Flask(__name__) create_oauth_client(app, "61a-ide", secret_key=get_secret(secret_name="OKPY_IDE_SECRET")) with connect_db() as db: db("""CREATE TABLE IF NOT EXISTS ide ( username varchar(128), initialized boolean, -- this is unused in the ide context, for now locked boolean );""") VSCODE_ASSOC = """ { "files.associations": { "BUILD": "python", "WORKSPACE": "python" }, "files.trimTrailingWhitespace": true
def create_oauth_client( app: flask.Flask, consumer_key, secret_key=None, success_callback=None, return_response=None, ): """Add Okpy OAuth for ``consumer_key`` to the current ``app``. Specifically, adds an endpoint ``/oauth/login`` that redirects to the Okpy login process, ``/oauth/authorized`` that receives the successful result of authentication, ``/api/user`` that acts as a test endpoint, and a :meth:`~flask_oauthlib.client.OAuthRemoteApp.tokengetter`. :param app: the app to add OAuth endpoints to :type app: ~flask.Flask :param consumer_key: the OAuth client consumer key :type consumer_key: str :param secret_key: the OAuth client secret, inferred using :func:`~common.rpc.secrets.get_secret` if omitted :type secret_key: str :param success_callback: an optional function to call upon login :type success_callback: func :param return_response: an optional function to send the OAuth response to :type return_response: func """ oauth = OAuth(app) if os.getenv("ENV") == "prod": if secret_key is None: app.secret_key = get_secret(secret_name="OKPY_OAUTH_SECRET") else: app.secret_key = secret_key else: consumer_key = "local-dev-all" app.secret_key = "kmSPJYPzKJglOOOmr7q0irMfBVMRFXN" if not app.debug: app.config.update( SESSION_COOKIE_SECURE=True, SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE="Lax", ) remote = oauth.remote_app( "ok-server", # Server Name consumer_key=consumer_key, consumer_secret=app.secret_key, request_token_params={ "scope": "all", "state": lambda: security.gen_salt(10) }, base_url="https://okpy.org/api/v3/", request_token_url=None, access_token_method="POST", access_token_url="https://okpy.org/oauth/token", authorize_url="https://okpy.org/oauth/authorize", ) def check_req(uri, headers, body): """ Add access_token to the URL Request. """ if "access_token" not in uri and session.get("access_token"): params = {"access_token": session.get("access_token")[0]} url_parts = list(urllib.parse.urlparse(uri)) query = dict(urllib.parse.parse_qsl(url_parts[4])) query.update(params) url_parts[4] = urllib.parse.urlencode(query) uri = urllib.parse.urlunparse(url_parts) return uri, headers, body remote.pre_request = check_req @app.route("/oauth/login") def login(): if app.debug: response = remote.authorize( callback=url_for("authorized", _external=True)) else: response = remote.authorize( url_for("authorized", _external=True, _scheme="https")) return response @app.route("/oauth/authorized") def authorized(): resp = remote.authorized_response() if resp is None: return "Access denied: error=%s" % (request.args["error"]) if isinstance(resp, dict) and "access_token" in resp: session["access_token"] = (resp["access_token"], "") if return_response: return_response(resp) if success_callback: success_callback() target = session.get(REDIRECT_KEY) if target: session.pop(REDIRECT_KEY) return redirect(target) return redirect(url_for("index")) @app.route("/api/user", methods=["POST"]) def client_method(): if "access_token" not in session: abort(401) token = session["access_token"][0] r = requests.get( "https://okpy.org/api/v3/user/?access_token={}".format(token)) if not r.ok: abort(401) return jsonify(r.json()) @remote.tokengetter def get_oauth_token(): return session.get("access_token") app.remote = remote
def _process(self): for trigger_word, event in TRIGGER_WORDS.items(): if trigger_word in self._message.lower(): break else: return match = re.search(REGEX_TEMPLATE, self._message) if not match: return try: repo = match.group("repo") pr = int(match.group("path")) except ValueError: return if COURSE_ORGS[repo.split("/")[0]] != self._course: return users = requests.get("https://slack.com/api/users.list", params={ "token": self._bot_token }).json() g = Github(get_secret(secret_name="GITHUB_ACCESS_TOKEN")) repo = g.get_repo(repo) pr = repo.get_pull(pr) if pr.user.login in STAFF_GITHUB: github_email = STAFF_GITHUB[pr.user.login] else: github_email = pr.user.email if not github_email: return for member in users["members"]: if member["profile"].get("email") == github_email: break else: return for member in users["members"]: if member["id"] == self._event["user"]: sender_email = member["profile"].get("email") break else: return if sender_email == github_email: return action = "approved" if event == "APPROVE" else "requested changes on" pr.create_review( body="{} {} this PR via the Slackbot!".format( member["profile"].get("real_name_normalized"), action), event=event, ) if trigger_word == "lgtm": self.reply = ":white_check_mark: Automatically approved on GitHub!" elif trigger_word == "sucks": self.reply = ":poop: Wow, this really needs changes..." else: self.reply = ":x: Changes requested on GitHub!"
import requests, sys, time import logging, ssl from flask import redirect from common.oauth_client import is_staff from common.rpc.secrets import get_secret from common.url_for import url_for from common.db import connect_db log = logging.getLogger(__name__) CLIENT_ID = "grade-display-exports" CLIENT_SECRET = get_secret(secret_name="OKPY_OAUTH_SECRET") OAUTH_SCOPE = "all" TIMEOUT = 10 TOKEN_ENDPOINT = "/oauth/token" # --------------------- with connect_db() as db: db("""CREATE TABLE IF NOT EXISTS tokens ( access_token VARCHAR(128), expires_at INTEGER, refresh_token VARCHAR(128) ) """)