server/app/endpoints/oauth.py (46 lines of code) (raw):

#!/usr/bin/env python3 # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. """Selfserve Portal for the Apache Software Foundation""" """Handler for oauth operations""" if not __debug__: raise RuntimeError("This code requires assert statements to be enabled") from ..lib import middleware import quart import aiohttp import uuid import urllib.parse import time OAUTH_URL_INIT = "https://oauth.apache.org/auth?state=%s&redirect_uri=%s" OAUTH_URL_CALLBACK = "https://oauth.apache.org/token?code=%s" async def process(form_data): if quart.request.method == "GET": code = form_data.get("code") state = form_data.get("state") if not code or not state: # Presumably first step in OAuth state = str(uuid.uuid4()) callback_url = urllib.parse.urljoin( quart.request.host_url.replace("http://", "https://"), f"/api/oauth?state={state}", ) redirect_url = OAUTH_URL_INIT % (state, urllib.parse.quote(callback_url)) headers = { "Location": redirect_url, } return quart.Response(status=302, response="Redirecting...", headers=headers) else: # Callback from oauth.a.o ct = aiohttp.client.ClientTimeout(sock_read=15) async with aiohttp.client.ClientSession(timeout=ct) as session: rv = await session.get(OAUTH_URL_CALLBACK % code) assert rv.status == 200, "Could not verify oauth response." oauth_data = await rv.json() quart.session.clear() quart.session.update(oauth_data) # Quart sessions live for the entirety of the browser session. We don't want this to extend # too far into the future (abuse??), so let's set a fixed timeout after which a new login # will be required. quart.session["timestamp"] = int(time.time()) uid = quart.session["uid"] return quart.Response( status=200, response=f"Successfully logged in! Welcome, {uid}\n", ) quart.current_app.add_url_rule( "/api/oauth", methods=[ "GET", ], view_func=middleware.glued(process), )