mirror of
https://github.com/minetest/contentdb.git
synced 2025-01-03 03:37:28 +01:00
WIP prototype oauth scopes
This commit is contained in:
parent
d4b1344f6a
commit
27e2b64e41
@ -66,14 +66,18 @@ def oauth_start():
|
||||
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
|
||||
valid_scopes = {"user:email", "package", "package:release", "package:screenshot"}
|
||||
scope = request.args.get("scope", "")
|
||||
scopes = [x.strip() for x in scope.split(",")]
|
||||
scopes = set([x for x in scopes if x != ""])
|
||||
unknown_scopes = scopes - valid_scopes
|
||||
if unknown_scopes:
|
||||
return f"Unknown scopes: {', '.join(unknown_scopes)}", 400
|
||||
|
||||
state = request.args.get("state")
|
||||
|
||||
token = APIToken.query.filter(APIToken.client == client, APIToken.owner == current_user).first()
|
||||
if token:
|
||||
if token and not (scopes - token.get_scopes()):
|
||||
token.access_token = random_string(32)
|
||||
token.auth_code = random_string(32)
|
||||
db.session.commit()
|
||||
@ -85,15 +89,19 @@ def oauth_start():
|
||||
return redirect(client.redirect_url)
|
||||
|
||||
elif action == "authorize":
|
||||
if token is None:
|
||||
token = APIToken()
|
||||
token.access_token = random_string(32)
|
||||
token.name = f"Token for {client.title} by {client.owner.username}"
|
||||
token.owner = current_user
|
||||
token.client = client
|
||||
|
||||
token.access_token = random_string(32)
|
||||
assert client is not None
|
||||
token.auth_code = random_string(32)
|
||||
db.session.add(token)
|
||||
|
||||
token.set_scopes(scopes)
|
||||
|
||||
add_audit_log(AuditSeverity.USER, current_user,
|
||||
f"Granted \"{scope}\" to OAuth2 application \"{client.title}\" by {client.owner.username} [{client_id}] ",
|
||||
url_for("users.profile", username=current_user.username))
|
||||
@ -102,7 +110,42 @@ def oauth_start():
|
||||
|
||||
return redirect(build_redirect_url(client.redirect_url, token.auth_code, state))
|
||||
|
||||
return render_template("oauth/authorize.html", client=client)
|
||||
scopes_info = []
|
||||
if not scopes:
|
||||
scopes_info.append({
|
||||
"icon": "globe-europe",
|
||||
"title": "Public data only",
|
||||
"description": "Read-only access to your public data",
|
||||
})
|
||||
|
||||
if "user:email" in scopes:
|
||||
scopes_info.append({
|
||||
"icon": "user",
|
||||
"title": gettext("Personal data"),
|
||||
"description": gettext("Email address (read-only)"),
|
||||
})
|
||||
|
||||
if ("package" in scopes or
|
||||
"package:release" in scopes or
|
||||
"package:screenshot" in scopes):
|
||||
if "package" in scopes:
|
||||
msg = gettext("Ability to edit packages and their releases, screenshots, and related data")
|
||||
elif "package:release" in scopes and "package:screenshot" in scopes:
|
||||
msg = gettext("Ability to create and edit releases and screenshots")
|
||||
elif "package:release" in scopes:
|
||||
msg = gettext("Ability to create and edit releases")
|
||||
elif "package:screenshot" in scopes:
|
||||
msg = gettext("Ability to create and edit screenshots")
|
||||
else:
|
||||
assert False, "This should never happen"
|
||||
|
||||
scopes_info.append({
|
||||
"icon": "pen",
|
||||
"title": gettext("Packages"),
|
||||
"description": msg,
|
||||
})
|
||||
|
||||
return render_template("oauth/authorize.html", client=client, scopes=scopes_info)
|
||||
|
||||
|
||||
def error(code: int, msg: str):
|
||||
|
@ -8,11 +8,6 @@ ContentDB allows you to create an OAuth2 Application and obtain access tokens
|
||||
for users.
|
||||
|
||||
|
||||
## Scopes
|
||||
|
||||
OAuth2 applications can currently only access public user data, using the whoami API.
|
||||
|
||||
|
||||
## Create an OAuth2 Client
|
||||
|
||||
Go to Settings > [OAuth2 Applications](/user/apps/) > Create
|
||||
@ -100,3 +95,12 @@ Next, you should check the access token works by getting the user information:
|
||||
curl https://content.minetest.net/api/whoami/ \
|
||||
-H "Authorization: Bearer YOURTOKEN"
|
||||
```
|
||||
|
||||
|
||||
## Scopes
|
||||
|
||||
* (no scope) - public data only
|
||||
* `user:email`: read user email
|
||||
* `package`: write access to packages
|
||||
* `package:release`: create and delete releases
|
||||
* `package:screenshot`: create, edit, delete screenshots
|
||||
|
6
app/logic/scope.py
Normal file
6
app/logic/scope.py
Normal file
@ -0,0 +1,6 @@
|
||||
from app.models import APIToken
|
||||
|
||||
|
||||
class Scope:
|
||||
def copy_to_token(self, token: APIToken):
|
||||
pass
|
@ -52,8 +52,38 @@ class APIToken(db.Model):
|
||||
client = db.relationship("OAuthClient", foreign_keys=[client_id], back_populates="tokens")
|
||||
auth_code = db.Column(db.String(34), unique=True, nullable=True)
|
||||
|
||||
scope_user_email = db.Column(db.Boolean, nullable=False, default=False)
|
||||
scope_package = db.Column(db.Boolean, nullable=False, default=False)
|
||||
scope_package_release = db.Column(db.Boolean, nullable=False, default=False)
|
||||
scope_package_screenshot = db.Column(db.Boolean, nullable=False, default=False)
|
||||
|
||||
def get_scopes(self) -> set[str]:
|
||||
ret = set()
|
||||
if self.scope_user_email:
|
||||
ret.add("user:email")
|
||||
if self.scope_package:
|
||||
ret.add("package")
|
||||
if self.scope_package_release:
|
||||
ret.add("package:release")
|
||||
if self.scope_package_screenshot:
|
||||
ret.add("package:screenshot")
|
||||
return ret
|
||||
|
||||
def set_scopes(self, v: set[str]):
|
||||
def pop(key: str):
|
||||
if key in v:
|
||||
v.remove(key)
|
||||
return True
|
||||
|
||||
self.scope_user_email = pop("user:email")
|
||||
self.scope_package = pop("package")
|
||||
self.scope_package_release = pop("package:release") or self.scope_package
|
||||
self.scope_package_screenshot = pop("package:screenshot") or self.scope_package
|
||||
return v
|
||||
|
||||
def can_operate_on_package(self, package):
|
||||
if self.client is not None:
|
||||
if (self.client is not None and
|
||||
not (self.scope_package or self.scope_package_release or self.scope_package_screenshot)):
|
||||
return False
|
||||
|
||||
if self.package and self.package != package:
|
||||
|
@ -51,19 +51,21 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for item in scopes %}
|
||||
<div class="row my-4 align-items-center">
|
||||
<div class="col-2 text-center fs-3">
|
||||
<i class="fas fa-globe-europe"></i>
|
||||
<i class="fas fa-{{ item.icon }}"></i>
|
||||
</div>
|
||||
<div class="col">
|
||||
<p class="my-0">
|
||||
{{ _("Public data only") }}
|
||||
{{ item.title }}
|
||||
</p>
|
||||
<p class="text-muted my-0">
|
||||
{{ _("Read-only access to your public data") }}
|
||||
{{ item.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<div class="row mt-5">
|
||||
<div class="col">
|
||||
|
39
migrations/versions/3d0999440b81_.py
Normal file
39
migrations/versions/3d0999440b81_.py
Normal file
@ -0,0 +1,39 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 3d0999440b81
|
||||
Revises: 52cf6746f255
|
||||
Create Date: 2023-11-01 00:45:24.057951
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '3d0999440b81'
|
||||
down_revision = '52cf6746f255'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.batch_alter_table('api_token', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('scope_user_email', sa.Boolean(), nullable=False, server_default="false"))
|
||||
batch_op.add_column(sa.Column('scope_package', sa.Boolean(), nullable=False, server_default="false"))
|
||||
batch_op.add_column(sa.Column('scope_package_release', sa.Boolean(), nullable=False, server_default="false"))
|
||||
batch_op.add_column(sa.Column('scope_package_screenshot', sa.Boolean(), nullable=False, server_default="false"))
|
||||
|
||||
op.execute("""
|
||||
UPDATE api_token SET
|
||||
scope_user_email = true,
|
||||
scope_package = true,
|
||||
scope_package_release = true,
|
||||
scope_package_screenshot = true;
|
||||
""")
|
||||
|
||||
|
||||
def downgrade():
|
||||
with op.batch_alter_table('api_token', schema=None) as batch_op:
|
||||
batch_op.drop_column('scope_package_screenshot')
|
||||
batch_op.drop_column('scope_package_release')
|
||||
batch_op.drop_column('scope_package')
|
||||
batch_op.drop_column('scope_user_email')
|
Loading…
Reference in New Issue
Block a user