~edwargix/git.sr.ht

32f2f83b883772226c05d7fad5f4de8c8abed92b — Drew DeVault 7 years ago 9e17a0e
Move more OAuth code into core.sr.ht
6 files changed, 48 insertions(+), 133 deletions(-)

A gitsrht/alembic/versions/447ac6ff8f40_add_user_mixin_properties.py
M gitsrht/app.py
M gitsrht/oauth.py
M gitsrht/types/__init__.py
D gitsrht/types/oauthtoken.py
D gitsrht/types/user.py
A gitsrht/alembic/versions/447ac6ff8f40_add_user_mixin_properties.py => gitsrht/alembic/versions/447ac6ff8f40_add_user_mixin_properties.py +26 -0
@@ 0,0 1,26 @@
"""Add user mixin properties

Revision ID: 447ac6ff8f40
Revises: f86f4bd632a4
Create Date: 2018-12-29 20:42:10.821748

"""

# revision identifiers, used by Alembic.
revision = '447ac6ff8f40'
down_revision = 'f86f4bd632a4'

from alembic import op
import sqlalchemy as sa


def upgrade():
    op.add_column("user", sa.Column("url", sa.String(256)))
    op.add_column("user", sa.Column("location", sa.Unicode(256)))
    op.add_column("user", sa.Column("bio", sa.Unicode(4096)))


def downgrade():
    op.delete_column("user", "url")
    op.delete_column("user", "location")
    op.delete_column("user", "bio")

M gitsrht/app.py => gitsrht/app.py +4 -25
@@ 9,12 9,12 @@ from srht.database import DbSession

db = DbSession(cfg("git.sr.ht", "connection-string"))

from gitsrht.types import User, UserType
from gitsrht.types import User

db.init()

import gitsrht.oauth
from gitsrht import urls
from gitsrht.oauth import GitOAuthService
from gitsrht.git import commit_time, trim_commit

def lookup_user(email):


@@ 22,7 22,8 @@ def lookup_user(email):

class GitApp(SrhtFlask):
    def __init__(self):
        super().__init__("git.sr.ht", __name__)
        super().__init__("git.sr.ht", __name__,
                oauth_service=GitOAuthService())

        self.url_map.strict_slashes = False



@@ 42,14 43,6 @@ class GitApp(SrhtFlask):
        self.add_template_filter(urls.log_rss_url)
        self.add_template_filter(urls.refs_rss_url)

        meta_client_id = cfg("git.sr.ht", "oauth-client-id")
        meta_client_secret = cfg("git.sr.ht", "oauth-client-secret")
        builds_client_id = cfg("builds.sr.ht", "oauth-client-id", default=None)
        self.configure_meta_auth(meta_client_id, meta_client_secret,
                base_scopes=["profile"] + ([
                    "{}/jobs:write".format(builds_client_id)
                ] if builds_client_id else []))

        @self.context_processor
        def inject():
            notice = session.get("notice")


@@ 70,18 63,4 @@ class GitApp(SrhtFlask):
            # TODO: Switch to a session token
            return User.query.filter(User.username == username).one_or_none()

    def lookup_or_register(self, exchange, profile, scopes):
        user = User.query.filter(User.username == profile["name"]).first()
        if not user:
            user = User()
            db.session.add(user)
        user.username = profile["name"]
        user.email = profile["email"]
        user.user_type = UserType(profile["user_type"])
        user.oauth_token = exchange["token"]
        user.oauth_token_expires = exchange["expires"]
        user.oauth_token_scopes = scopes
        db.session.commit()
        return user

app = GitApp()

M gitsrht/oauth.py => gitsrht/oauth.py +9 -39
@@ 1,45 1,15 @@
from srht.config import cfg
from srht.oauth import OAuthScope, AbstractOAuthService, set_base_service
from srht.oauth import delegated_exchange
from srht.flask import DATE_FORMAT
from srht.database import db
from gitsrht.types import OAuthToken, User, UserType
from datetime import datetime
from srht.oauth import AbstractOAuthService
from gitsrht.types import OAuthToken, User

client_id = cfg("git.sr.ht", "oauth-client-id")
client_secret = cfg("git.sr.ht", "oauth-client-secret")
revocation_url = "{}/oauth/revoke".format(cfg("git.sr.ht", "origin"))
builds_client_id = cfg("builds.sr.ht", "oauth-client-id", default=None)

class GitOAuthService(AbstractOAuthService):
    def get_client_id(self):
        return client_id

    def get_token(self, token, token_hash, scopes):
        now = datetime.utcnow()
        oauth_token = (OAuthToken.query
                .filter(OAuthToken.token_hash == token_hash)
                .filter(OAuthToken.expires > now)
        ).first()
        if oauth_token:
            return oauth_token
        _token, profile = delegated_exchange(
                token, client_id, client_secret, revocation_url)
        expires = datetime.strptime(_token["expires"], DATE_FORMAT)
        scopes = set(OAuthScope(s) for s in _token["scopes"].split(","))
        user = User.query.filter(User.username == profile["username"]).first()
        if not user:
            user = User()
            user.username = profile["username"]
            user.email = profile["email"]
            user.user_type = UserType(profile["user_type"])
            user.oauth_token = token
            user.oauth_token_expires = expires
            db.session.add(user)
            db.session.flush()
        oauth_token = OAuthToken(user, token, expires)
        oauth_token.scopes = ",".join(str(s) for s in scopes)
        db.session.add(oauth_token)
        db.session.commit()
        return oauth_token

set_base_service(GitOAuthService())
    def __init__(self):
        super().__init__(client_id, client_secret,
                required_scopes=["profile"] + ([
                    "{}/jobs:write".format(builds_client_id)
                ] if builds_client_id else []),
                token_class=OAuthToken, user_class=User)

M gitsrht/types/__init__.py => gitsrht/types/__init__.py +9 -2
@@ 1,6 1,13 @@
from .user import User, UserType
from srht.database import Base
from srht.oauth import ExternalUserMixin, ExternalOAuthTokenMixin

class User(Base, ExternalUserMixin):
    pass

class OAuthToken(Base, ExternalOAuthTokenMixin):
    pass

from .repository import Repository, RepoVisibility
from .oauthtoken import OAuthToken
from .webhook import Webhook
from .redirect import Redirect
from .access import Access, AccessMode

D gitsrht/types/oauthtoken.py => gitsrht/types/oauthtoken.py +0 -24
@@ 1,24 0,0 @@
import sqlalchemy as sa
from datetime import datetime, timedelta
from srht.database import Base
import hashlib
import binascii
import os

class OAuthToken(Base):
    __tablename__ = 'oauthtoken'
    id = sa.Column(sa.Integer, primary_key=True)
    created = sa.Column(sa.DateTime, nullable=False)
    updated = sa.Column(sa.DateTime, nullable=False)
    expires = sa.Column(sa.DateTime, nullable=False)
    user_id = sa.Column(sa.Integer, sa.ForeignKey('user.id'))
    user = sa.orm.relationship('User', backref=sa.orm.backref('oauth_tokens'))
    token_hash = sa.Column(sa.String(128), nullable=False)
    token_partial = sa.Column(sa.String(8), nullable=False)
    scopes = sa.Column(sa.String(512), nullable=False)

    def __init__(self, user, token, expires):
        self.user_id = user.id
        self.expires = datetime.now() + timedelta(days=365)
        self.token_partial = token[:8]
        self.token_hash = hashlib.sha512(token.encode()).hexdigest()

D gitsrht/types/user.py => gitsrht/types/user.py +0 -43
@@ 1,43 0,0 @@
import sqlalchemy as sa
import sqlalchemy_utils as sau
from srht.database import Base
from enum import Enum

class UserType(Enum):
    unconfirmed = "unconfirmed"
    active_non_paying = "active_non_paying"
    active_free = "active_free"
    active_paying = "active_paying"
    active_delinquent = "active_delinquent"
    admin = "admin"

class User(Base):
    __tablename__ = 'user'
    id = sa.Column(sa.Integer, primary_key=True)
    username = sa.Column(sa.Unicode(256))
    created = sa.Column(sa.DateTime, nullable=False)
    updated = sa.Column(sa.DateTime, nullable=False)
    oauth_token = sa.Column(sa.String(256), nullable=False)
    oauth_token_expires = sa.Column(sa.DateTime, nullable=False)
    oauth_token_scopes = sa.Column(sa.String, nullable=False, default="")
    email = sa.Column(sa.String(256), nullable=False)
    user_type = sa.Column(
            sau.ChoiceType(UserType, impl=sa.String()),
            nullable=False,
            default=UserType.unconfirmed)

    def __repr__(self):
        return '<User {} {}>'.format(self.id, self.username)

    @property
    def canonical_name(self):
        return "~" + self.username

    def is_authenticated(self):
        return True
    def is_active(self):
        return True
    def is_anonymous(self):
        return False
    def get_id(self):
        return self.username