~edwargix/git.sr.ht

f00557d6b90f6ebdb99b21dd281068701961b30c — Drew DeVault 7 years ago d01d4b9
Break ground on the API
M gitsrht/alembic/env.py => gitsrht/alembic/env.py +8 -3
@@ 37,7 37,11 @@ def run_migrations_offline():

    """
    url = config.get_main_option("sqlalchemy.url")
    context.configure(url=url, target_metadata=target_metadata)
    context.configure(
            url=url,
            target_metadata=target_metadata,
            include_schemas=True
        )

    with context.begin_transaction():
        context.run_migrations()


@@ 57,8 61,9 @@ def run_migrations_online():
    connection = engine.connect()
    context.configure(
                connection=connection,
                target_metadata=target_metadata
                )
                target_metadata=target_metadata,
                include_schemas=True
            )

    try:
        with context.begin_transaction():

A gitsrht/alembic/versions/bfcdce82e0fc_add_oauthtoken_table.py => gitsrht/alembic/versions/bfcdce82e0fc_add_oauthtoken_table.py +37 -0
@@ 0,0 1,37 @@
"""Add oauthtoken table

Revision ID: bfcdce82e0fc
Revises: 125cf4a92c42
Create Date: 2017-09-19 08:24:43.614375

"""

# revision identifiers, used by Alembic.
revision = 'bfcdce82e0fc'
down_revision = '125cf4a92c42'

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('oauthtoken',
    sa.Column('id', sa.INTEGER(), nullable=False),
    sa.Column('created', postgresql.TIMESTAMP(), autoincrement=False, nullable=False),
    sa.Column('updated', postgresql.TIMESTAMP(), autoincrement=False, nullable=False),
    sa.Column('expires', postgresql.TIMESTAMP(), autoincrement=False, nullable=False),
    sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('token_hash', sa.VARCHAR(length=128), autoincrement=False, nullable=False),
    sa.Column('token_partial', sa.VARCHAR(length=8), autoincrement=False, nullable=False),
    sa.Column('scopes', sa.VARCHAR(length=512), autoincrement=False, nullable=False),
    sa.ForeignKeyConstraint(['user_id'], ['user.id'], name='oauthtoken_user_id_fkey'),
    sa.PrimaryKeyConstraint('id', name='oauthtoken_pkey')
    )
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('oauthtoken')
    # ### end Alembic commands ###

M gitsrht/app.py => gitsrht/app.py +3 -0
@@ 15,6 15,7 @@ app = SrhtFlask("git", __name__)
app.secret_key = cfg("server", "secret-key")
login_manager = LoginManager()
login_manager.init_app(app)
import gitsrht.oauth

@login_manager.user_loader
def load_user(username):


@@ 37,10 38,12 @@ def oauth_url(return_to):
        "," + builds_sr_ht + "/jobs:write" if builds_sr_ht else "",
        urllib.parse.quote_plus(return_to))

from gitsrht.blueprints.api import api
from gitsrht.blueprints.auth import auth
from gitsrht.blueprints.public import public
from gitsrht.blueprints.manage import manage

app.register_blueprint(api)
app.register_blueprint(auth)
app.register_blueprint(public)
app.register_blueprint(manage)

A gitsrht/blueprints/api.py => gitsrht/blueprints/api.py +33 -0
@@ 0,0 1,33 @@
from flask import Blueprint, request
from gitsrht.types import Repository, RepoVisibility
from gitsrht.access import UserAccess, has_access, get_repo
from srht.oauth import oauth

api = Blueprint("api", __name__)

repo_json = lambda r: {
    "id": r.id,
    "name": r.name,
    "description": r.description,
    "created": r.created,
    "updated": r.updated,
    "visibility": r.visibility.value
}

@api.route("/api/repos")
@oauth("repos")
def repos_GET(oauth_token):
    start = request.args.get('start') or -1
    repos = Repository.query.filter(Repository.owner_id == oauth_token.user_id)
    if start != -1:
        repos = repos.filter(Repository.id <= start)
    repos = repos.order_by(Repository.id.desc()).limit(11).all()
    if len(repos) != 11:
        next_id = -1
    else:
        next_id = repos[-1].id
        repos = repos[:10]
    return {
        "next": next_id,
        "results": [repo_json(r) for r in repos]
    }

A gitsrht/oauth.py => gitsrht/oauth.py +46 -0
@@ 0,0 1,46 @@
from srht.config import cfg
from srht.oauth import OAuthScope, AbstractOAuthService, set_base_service
from srht.oauth import meta_delegated_exchange
from srht.flask import DATE_FORMAT
from srht.database import db
from gitsrht.types import OAuthToken, User
from datetime import datetime

client_id = cfg("meta.sr.ht", "oauth-client-id")
client_secret = cfg("meta.sr.ht", "oauth-client-secret")
revocation_url = "{}://{}/oauth/revoke".format(
    cfg("server", "protocol"), cfg("server", "domain"))

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 = meta_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.get("username")
            user.email = profile.get("email")
            user.paid = profile.get("paid")
            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())

M gitsrht/types/__init__.py => gitsrht/types/__init__.py +1 -0
@@ 1,2 1,3 @@
from .user import User
from .repository import Repository, RepoVisibility
from .oauthtoken import OAuthToken

A gitsrht/types/oauthtoken.py => gitsrht/types/oauthtoken.py +24 -0
@@ 0,0 1,24 @@
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()