OAuth2: Add approval and verified apps

This commit is contained in:
rubenwardy 2023-10-31 20:11:55 +00:00
parent 073dcf9517
commit 00f7dbb28d
7 changed files with 88 additions and 9 deletions

@ -72,10 +72,8 @@ def create_edit_token(username, id=None):
access_token = None
if not is_new:
token = APIToken.query.get(id)
if token is None:
if token is None or token.owner != user:
abort(404)
elif token.owner != user:
abort(403)
access_token = session.pop("token_" + str(token.id), None)

@ -27,7 +27,7 @@ from wtforms.validators import InputRequired, Length
from app import csrf
from app.blueprints.users.settings import get_setting_tabs
from app.models import db, OAuthClient, User, Permission, APIToken, AuditSeverity
from app.models import db, OAuthClient, User, Permission, APIToken, AuditSeverity, UserRank
from app.utils import random_string, add_audit_log
bp = Blueprint("oauth", __name__)
@ -63,6 +63,9 @@ def oauth_start():
if client.redirect_url != redirect_uri:
return "redirect_uri does not match client", 400
if not client.approved and client.owner != current_user:
abort(404)
scope = request.args.get("scope", "public")
if scope != "public":
return "Unsupported scope, only public is supported", 400
@ -189,6 +192,7 @@ def create_edit_client(username, id_=None):
client.owner = user
client.id = random_string(24)
client.secret = random_string(32)
client.approved = current_user.rank.atLeast(UserRank.EDITOR)
form.populate_obj(client)

@ -559,9 +559,12 @@ class OAuthClient(db.Model):
__tablename__ = "oauth_client"
id = db.Column(db.String(24), primary_key=True)
title = db.Column(db.String(64), unique=True)
secret = db.Column(db.String(32))
redirect_url = db.Column(db.String(128))
title = db.Column(db.String(64), unique=True, nullable=False)
description = db.Column(db.String(300), nullable=True)
secret = db.Column(db.String(32), nullable=False)
redirect_url = db.Column(db.String(128), nullable=False)
approved = db.Column(db.Boolean, nullable=False, default=False)
verified = db.Column(db.Boolean, nullable=False, default=False)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
owner = db.relationship("User", foreign_keys=[owner_id], back_populates="clients")

@ -28,9 +28,24 @@
</p>
</div>
</div>
{% if client.verified %}
<div class="row my-4 align-items-center">
<div class="col-2 text-center fs-3">
<i class="fas text-info fa-check-circle fa-xl"></i>
</div>
<div class="col">
<p class="my-0">
{{ _("Verified Application") }}
</p>
<p class="text-muted my-0">
{{ _("ContentDB trusts this application") }}
</p>
</div>
</div>
{% endif %}
<div class="row my-4 align-items-center">
<div class="col-2 text-center">
<i class="fas fa-globe fa-xl"></i>
<div class="col-2 text-center fs-3">
<i class="fas fa-globe-europe"></i>
</div>
<div class="col">
<p class="my-0">
@ -57,4 +72,12 @@
</div>
</article>
</form>
{% if not client.approved %}
<aside class="alert alert-danger mt-5 w-50 mx-auto">
<h3 class="mt-0">{{ _("Application isn't approved yet") }}</h3>
<p class="mb-0">
{{ _("To allow users other than yourself to log in, you'll need to contact ContentDB staff and ask them to approve your app.") }}
</p>
</aside>
{% endif %}
{% endblock %}

@ -21,6 +21,15 @@
<h1 class="mt-0">{{ self.title() }}</h1>
{% if client %}
{% if not client.approved %}
<aside class="alert alert-info my-5">
<h3 class="mt-0">{{ _("Application isn't approved yet") }}</h3>
<p class="mb-0">
{{ _("To allow users other than yourself to log in, you'll need to contact ContentDB staff and ask them to approve your app.") }}
</p>
</aside>
{% endif %}
<form class="card my-5" method="POST" action="{{ url_for("oauth.revoke_all", username=client.owner.username, id_=client.id) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<div class="card-body d-flex flex-row align-items-center">

@ -12,6 +12,9 @@
<div class="list-group">
{% for client in user.clients %}
<a class="list-group-item list-group-item-action" href="{{ url_for('oauth.create_edit_client', username=user.username, id_=client.id) }}">
{% if not client.approved %}
<span class="badge bg-warning float-end">{{ _("Unpublished") }}</span>
{% endif %}
{{ client.title }}
</a>
{% else %}

@ -0,0 +1,39 @@
"""empty message
Revision ID: 52cf6746f255
Revises: 9395ba96f853
Create Date: 2023-10-31 19:56:58.249938
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '52cf6746f255'
down_revision = '9395ba96f853'
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table('oauth_client', schema=None) as batch_op:
batch_op.add_column(sa.Column('description', sa.String(length=300), nullable=True))
batch_op.add_column(sa.Column('approved', sa.Boolean(), nullable=False, server_default="false"))
batch_op.add_column(sa.Column('verified', sa.Boolean(), nullable=False, server_default="false"))
batch_op.alter_column('title',
existing_type=sa.VARCHAR(length=64),
nullable=False)
batch_op.alter_column('secret',
existing_type=sa.VARCHAR(length=32),
nullable=False)
batch_op.alter_column('redirect_url',
existing_type=sa.VARCHAR(length=128),
nullable=False)
def downgrade():
with op.batch_alter_table('oauth_client', schema=None) as batch_op:
batch_op.drop_column('verified')
batch_op.drop_column('approved')
batch_op.drop_column('description')