From patchwork Mon Jun 8 18:16:27 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Fr=C3=A9d=C3=A9ric_Mangano-Tarumi?= X-Patchwork-Id: 1680 Return-Path: Delivered-To: patchwork@archlinux.org Received: from apollo.archlinux.org (localhost [127.0.0.1]) by apollo.archlinux.org (Postfix) with ESMTP id 68CCE193F7D8E for ; Mon, 8 Jun 2020 18:16:37 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on apollo.archlinux.org X-Spam-Level: X-Spam-Status: No, score=-3.4 required=5.0 tests=DKIM_SIGNED=0.1, DKIM_VALID=-0.1,DKIM_VALID_AU=-0.1,MAILING_LIST_MULTI=-1, RCVD_IN_DNSWL_MED=-2.3,RCVD_IN_MSPIKE_H4=-0.01,RCVD_IN_MSPIKE_WL=-0.01, SPF_HELO_NONE=0.001,T_DMARC_POLICY_NONE=0.01 autolearn=ham autolearn_force=no version=3.4.4 X-Spam-BL-Results: [127.0.9.2] [127.0.0.19] Received: from orion.archlinux.org (orion.archlinux.org [88.198.91.70]) by apollo.archlinux.org (Postfix) with ESMTPS for ; Mon, 8 Jun 2020 18:16:37 +0000 (UTC) Received: from orion.archlinux.org (localhost [127.0.0.1]) by orion.archlinux.org (Postfix) with ESMTP id 4E7DB1CA94BE4B; Mon, 8 Jun 2020 18:16:34 +0000 (UTC) Received: from luna.archlinux.org (luna.archlinux.org [5.9.250.164]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-384) server-signature RSA-PSS (4096 bits)) (No client certificate requested) (Authenticated sender: luna) by orion.archlinux.org (Postfix) with ESMTPSA id 04CAB1CA94BE45; Mon, 8 Jun 2020 18:16:34 +0000 (UTC) Authentication-Results: orion.archlinux.org; dkim=pass (1024-bit key) header.d=mg0.fr header.i=@mg0.fr header.b=0Ups4f4k Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id EAA5C2B75F; Mon, 8 Jun 2020 18:16:33 +0000 (UTC) Authentication-Results: luna.archlinux.org; dkim=pass (1024-bit key) header.d=mg0.fr header.i=@mg0.fr header.b=0Ups4f4k Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id 2E8E829CC4 for ; Mon, 8 Jun 2020 18:16:30 +0000 (UTC) Received: from orion.archlinux.org (orion.archlinux.org [88.198.91.70]) by luna.archlinux.org (Postfix) with ESMTPS for ; Mon, 8 Jun 2020 18:16:30 +0000 (UTC) Received: from orion.archlinux.org (localhost [127.0.0.1]) by orion.archlinux.org (Postfix) with ESMTP id 51C7D1CA94BE3D for ; Mon, 8 Jun 2020 18:16:28 +0000 (UTC) Received: from tsubame.mg0.fr (tsubame.mg0.fr [IPv6:2001:41d0:401:3100::402b]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-384) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by orion.archlinux.org (Postfix) with ESMTPS for ; Mon, 8 Jun 2020 18:16:28 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=mg0.fr; s=tsubame; h=Message-ID:Subject:To:From:Date:cc; bh=DIx6d4GeQOBaNXkaKpGlvEcjwxdhYAs0YIAUhwIXY38=; b=0Ups4f4kIsjD/oFGHgSBFnCvKI j4DVAEGtVXtjn94Iqh+Zq7Qo6n2SzWNY249C2E4aipTBAP//wdHTR5niOjBUmKJaHW962b9WpUU2i GdL5yloXPmC/LFBIkFZopmP0a3MHHo/fp0GRxtLI8blpBMwfim9EuI/gaRHADZCZJq1M=; Received: from fmang by tsubame.mg0.fr with local (Exim 4.94) (envelope-from ) id 1jiMJv-000Iez-H4 for aur-dev@archlinux.org; Mon, 08 Jun 2020 20:16:27 +0200 Date: Mon, 8 Jun 2020 20:16:27 +0200 From: =?utf-8?b?RnLDqWTDqXJpYw==?= Mangano-Tarumi To: aur-dev@archlinux.org Subject: [PATCH 1/3] Add SSO account ID in table Users Message-ID: <20200608181627.GA71726@tsubame.mg0.fr> MIME-Version: 1.0 Content-Disposition: inline X-BeenThere: aur-dev@archlinux.org X-Mailman-Version: 2.1.33 Precedence: list List-Id: "Arch User Repository \(AUR\) Development" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: aur-dev-bounces@archlinux.org Sender: "aur-dev" This column holds a user ID issed by the single sign-on provider. For Keycloak, it is an UUID. For more flexibility, we will be using a standardly-sized VARCHAR field. --- aurweb/schema.py | 1 + ...6e1cd_add_sso_account_id_in_table_users.py | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 migrations/versions/ef39fcd6e1cd_add_sso_account_id_in_table_users.py diff --git a/aurweb/schema.py b/aurweb/schema.py index 20f3e5ce..a1d56281 100644 --- a/aurweb/schema.py +++ b/aurweb/schema.py @@ -67,6 +67,7 @@ Users = Table( Column('CommentNotify', TINYINT(1), nullable=False, server_default=text("1")), Column('UpdateNotify', TINYINT(1), nullable=False, server_default=text("0")), Column('OwnershipNotify', TINYINT(1), nullable=False, server_default=text("1")), + Column('SSOAccountID', String(255), nullable=True, unique=True), Index('UsersAccountTypeID', 'AccountTypeID'), mysql_engine='InnoDB', ) diff --git a/migrations/versions/ef39fcd6e1cd_add_sso_account_id_in_table_users.py b/migrations/versions/ef39fcd6e1cd_add_sso_account_id_in_table_users.py new file mode 100644 index 00000000..9e125165 --- /dev/null +++ b/migrations/versions/ef39fcd6e1cd_add_sso_account_id_in_table_users.py @@ -0,0 +1,30 @@ +"""Add SSO account ID in table Users + +Revision ID: ef39fcd6e1cd +Revises: f47cad5d6d03 +Create Date: 2020-06-08 10:04:13.898617 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ef39fcd6e1cd' +down_revision = 'f47cad5d6d03' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('Users', sa.Column('SSOAccountID', sa.String(length=255), nullable=True)) + op.create_unique_constraint(None, 'Users', ['SSOAccountID']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'Users', type_='unique') + op.drop_column('Users', 'SSOAccountID') + # ### end Alembic commands ### From patchwork Mon Jun 8 18:16:36 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Fr=C3=A9d=C3=A9ric_Mangano-Tarumi?= X-Patchwork-Id: 1681 Return-Path: Delivered-To: patchwork@archlinux.org Received: from apollo.archlinux.org (localhost [127.0.0.1]) by apollo.archlinux.org (Postfix) with ESMTP id 69928193F7E2C for ; Mon, 8 Jun 2020 18:16:57 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on apollo.archlinux.org X-Spam-Level: X-Spam-Status: No, score=-3.4 required=5.0 tests=DKIM_SIGNED=0.1, DKIM_VALID=-0.1,DKIM_VALID_AU=-0.1,MAILING_LIST_MULTI=-1, RCVD_IN_DNSWL_MED=-2.3,SPF_HELO_NONE=0.001,T_DMARC_POLICY_NONE=0.01 autolearn=ham autolearn_force=no version=3.4.4 X-Spam-BL-Results: [127.0.9.2] Received: from orion.archlinux.org (orion.archlinux.org [IPv6:2a01:4f8:160:6087::1]) by apollo.archlinux.org (Postfix) with ESMTPS for ; Mon, 8 Jun 2020 18:16:57 +0000 (UTC) Received: from orion.archlinux.org (localhost [127.0.0.1]) by orion.archlinux.org (Postfix) with ESMTP id A29341CA94BE73; Mon, 8 Jun 2020 18:16:46 +0000 (UTC) Received: from luna.archlinux.org (luna.archlinux.org [5.9.250.164]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-384) server-signature RSA-PSS (4096 bits)) (No client certificate requested) (Authenticated sender: luna) by orion.archlinux.org (Postfix) with ESMTPSA id 4152E1CA94BE6E; Mon, 8 Jun 2020 18:16:46 +0000 (UTC) Authentication-Results: orion.archlinux.org; dkim=pass (1024-bit key) header.d=mg0.fr header.i=@mg0.fr header.b=InR1GWIs Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id 241902B75F; Mon, 8 Jun 2020 18:16:45 +0000 (UTC) Authentication-Results: luna.archlinux.org; dkim=pass (1024-bit key) header.d=mg0.fr header.i=@mg0.fr header.b=InR1GWIs Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id 0ADFF29CC4 for ; Mon, 8 Jun 2020 18:16:42 +0000 (UTC) Received: from orion.archlinux.org (orion.archlinux.org [IPv6:2a01:4f8:160:6087::1]) by luna.archlinux.org (Postfix) with ESMTPS for ; Mon, 8 Jun 2020 18:16:42 +0000 (UTC) Received: from orion.archlinux.org (localhost [127.0.0.1]) by orion.archlinux.org (Postfix) with ESMTP id F358A1CA94BE67 for ; Mon, 8 Jun 2020 18:16:37 +0000 (UTC) Received: from tsubame.mg0.fr (tsubame.mg0.fr [IPv6:2001:41d0:401:3100::402b]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-384) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by orion.archlinux.org (Postfix) with ESMTPS for ; Mon, 8 Jun 2020 18:16:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=mg0.fr; s=tsubame; h=Message-ID:Subject:To:From:Date:cc; bh=ozwwDZ2fpdzocfKXlU6cJobOakJut6H4V1i19dZVaG0=; b=InR1GWIsQ8VNOql8KJnjyGo/ho E1XYykbN9P+wTdL6T7QwnWeR4rOz7/B4nQ0iuoHphtwI6KoBu7WpJzaSNQQi5nnCUhprE8DN62qGX i0I84x7k5GntgBDQeHvziASQDfyvXpEYsMArQmht24oujL5WNnssQypHNLSOT3hjGeOY=; Received: from fmang by tsubame.mg0.fr with local (Exim 4.94) (envelope-from ) id 1jiMK4-000IfD-Hz for aur-dev@archlinux.org; Mon, 08 Jun 2020 20:16:36 +0200 Date: Mon, 8 Jun 2020 20:16:36 +0200 From: =?utf-8?b?RnLDqWTDqXJpYw==?= Mangano-Tarumi To: aur-dev@archlinux.org Subject: [PATCH 2/3] Integrate SQLAlchemy into FastAPI Message-ID: <20200608181636.GA71741@tsubame.mg0.fr> MIME-Version: 1.0 Content-Disposition: inline X-BeenThere: aur-dev@archlinux.org X-Mailman-Version: 2.1.33 Precedence: list List-Id: "Arch User Repository \(AUR\) Development" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: aur-dev-bounces@archlinux.org Sender: "aur-dev" --- aurweb/db.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/aurweb/db.py b/aurweb/db.py index 1ccd9a07..02aeba38 100644 --- a/aurweb/db.py +++ b/aurweb/db.py @@ -10,6 +10,8 @@ except ImportError: import aurweb.config +engine = None # See get_engine + def get_sqlalchemy_url(): """ @@ -38,6 +40,34 @@ def get_sqlalchemy_url(): raise ValueError('unsupported database backend') +def get_engine(): + """ + Return the global SQLAlchemy engine. + + The engine is created on the first call to get_engine and then stored in the + `engine` global variable for the next calls. + """ + from sqlalchemy import create_engine + global engine + if engine is None: + engine = create_engine(get_sqlalchemy_url(), + # check_same_thread is for a SQLite technicality + # https://fastapi.tiangolo.com/tutorial/sql-databases/#note + connect_args={"check_same_thread": False}) + return engine + + +def connect(): + """ + Return an SQLAlchemy connection. Connections are usually pooled. See + . + + Since SQLAlchemy connections are context managers too, you should use it + with Python’s `with` operator, or with FastAPI’s dependency injection. + """ + return get_engine().connect() + + class Connection: _conn = None _paramstyle = None From patchwork Mon Jun 8 18:16:49 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Fr=C3=A9d=C3=A9ric_Mangano-Tarumi?= X-Patchwork-Id: 1682 Return-Path: Delivered-To: patchwork@archlinux.org Received: from apollo.archlinux.org (localhost [127.0.0.1]) by apollo.archlinux.org (Postfix) with ESMTP id 2ED98193F7E93 for ; Mon, 8 Jun 2020 18:17:14 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on apollo.archlinux.org X-Spam-Level: X-Spam-Status: No, score=-3.4 required=5.0 tests=DKIM_SIGNED=0.1, DKIM_VALID=-0.1,DKIM_VALID_AU=-0.1,MAILING_LIST_MULTI=-1, RCVD_IN_DNSWL_MED=-2.3,SPF_HELO_NONE=0.001,T_DMARC_POLICY_NONE=0.01 autolearn=ham autolearn_force=no version=3.4.4 X-Spam-BL-Results: [127.0.9.2] Received: from orion.archlinux.org (orion.archlinux.org [IPv6:2a01:4f8:160:6087::1]) by apollo.archlinux.org (Postfix) with ESMTPS for ; Mon, 8 Jun 2020 18:17:14 +0000 (UTC) Received: from orion.archlinux.org (localhost [127.0.0.1]) by orion.archlinux.org (Postfix) with ESMTP id 8F3971CA955089; Mon, 8 Jun 2020 18:17:09 +0000 (UTC) Received: from luna.archlinux.org (luna.archlinux.org [IPv6:2a01:4f8:160:3033::2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-384) server-signature RSA-PSS (4096 bits)) (No client certificate requested) (Authenticated sender: luna) by orion.archlinux.org (Postfix) with ESMTPSA id 302F91CA955088; Mon, 8 Jun 2020 18:17:09 +0000 (UTC) Authentication-Results: orion.archlinux.org; dkim=pass (1024-bit key) header.d=mg0.fr header.i=@mg0.fr header.b=2Hg+gEKg Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id 7CE842B75F; Mon, 8 Jun 2020 18:17:05 +0000 (UTC) Authentication-Results: luna.archlinux.org; dkim=pass (1024-bit key) header.d=mg0.fr header.i=@mg0.fr header.b=2Hg+gEKg Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id 5E47D29CC4 for ; Mon, 8 Jun 2020 18:17:02 +0000 (UTC) Received: from orion.archlinux.org (orion.archlinux.org [88.198.91.70]) by luna.archlinux.org (Postfix) with ESMTPS for ; Mon, 8 Jun 2020 18:17:02 +0000 (UTC) Received: from orion.archlinux.org (localhost [127.0.0.1]) by orion.archlinux.org (Postfix) with ESMTP id 747F51CA95507C for ; Mon, 8 Jun 2020 18:16:57 +0000 (UTC) Received: from tsubame.mg0.fr (tsubame.mg0.fr [IPv6:2001:41d0:401:3100::402b]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-384) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by orion.archlinux.org (Postfix) with ESMTPS for ; Mon, 8 Jun 2020 18:16:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=mg0.fr; s=tsubame; h=Message-ID:Subject:To:From:Date:cc; bh=TX4f6FoohPI5bwiIXuXW+Ai/YpzYGex4eCuWPzbRmcE=; b=2Hg+gEKgmyO9CY//Jy/OpRMfdN mGgvEDKdCygCFVpif02EHbdSyVXnxjn7c4Hd5xGLpvi+DQ5ix0BNx2TGuUjF0gATXBautuiVEgBXT vB+A4SqatGNlflFvD4jEsvxWdDSgvU903xqT8z8/OMbiTafikoIQJrTAQh0UdyoKnAC0=; Received: from fmang by tsubame.mg0.fr with local (Exim 4.94) (envelope-from ) id 1jiMKH-000IfO-3L for aur-dev@archlinux.org; Mon, 08 Jun 2020 20:16:49 +0200 Date: Mon, 8 Jun 2020 20:16:49 +0200 From: =?utf-8?b?RnLDqWTDqXJpYw==?= Mangano-Tarumi To: aur-dev@archlinux.org Subject: [PATCH 3/3] Open AUR sessions from SSO Message-ID: <20200608181649.GA71751@tsubame.mg0.fr> MIME-Version: 1.0 Content-Disposition: inline X-BeenThere: aur-dev@archlinux.org X-Mailman-Version: 2.1.33 Precedence: list List-Id: "Arch User Repository \(AUR\) Development" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: aur-dev-bounces@archlinux.org Sender: "aur-dev" Only the core functionality is implemented here. See the TODOs. --- aurweb/routers/sso.py | 51 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/aurweb/routers/sso.py b/aurweb/routers/sso.py index b16edffb..d0802c34 100644 --- a/aurweb/routers/sso.py +++ b/aurweb/routers/sso.py @@ -1,9 +1,18 @@ +import time +import uuid + import fastapi from authlib.integrations.starlette_client import OAuth +from fastapi import Depends, HTTPException +from fastapi.responses import RedirectResponse +from sqlalchemy.sql import select from starlette.requests import Request import aurweb.config +import aurweb.db + +from aurweb.schema import Sessions, Users router = fastapi.APIRouter() @@ -23,8 +32,46 @@ async def login(request: Request): return await oauth.sso.authorize_redirect(request, redirect_uri, prompt="login") +def open_session(conn, user_id): + """ + Create a new user session into the database. Return its SID. + """ + # TODO check for account suspension + # TODO apply [options] max_sessions_per_user + sid = uuid.uuid4().hex + conn.execute(Sessions.insert().values( + UsersID=user_id, + SessionID=sid, + LastUpdateTS=time.time(), + )) + # TODO update Users.LastLogin and Users.LastLoginIPAddress + return sid + + @router.get("/sso/authenticate") -async def authenticate(request: Request): +async def authenticate(request: Request, conn=Depends(aurweb.db.connect)): + """ + Receive an OpenID Connect ID token, validate it, then process it to create + an new AUR session. + """ + # TODO check for banned IPs token = await oauth.sso.authorize_access_token(request) user = await oauth.sso.parse_id_token(request, token) - return dict(user) + sub = user.get("sub") # this is the SSO account ID in JWT terminology + if not sub: + raise HTTPException(status_code=400, detail="JWT is missing its `sub` field.") + + aur_accounts = conn.execute(select([Users.c.ID]).where(Users.c.SSOAccountID == sub)) \ + .fetchall() + if not aur_accounts: + return "Sorry, we don’t seem to know you Sir " + sub + elif len(aur_accounts) == 1: + sid = open_session(conn, aur_accounts[0][Users.c.ID]) + response = RedirectResponse("/") + # TODO redirect to the referrer + response.set_cookie(key="AURSID", value=sid, httponly=True, + secure=request.url.scheme == "https") + return response + else: + # We’ve got a severe integrity violation. + raise Exception("Multiple accounts found for SSO account " + sub)