Skip to content
This repository was archived by the owner on Apr 25, 2022. It is now read-only.

Commit 2ef6e70

Browse files
committed
Check access before generating download tokens
Add a table, UserOwnedApp, to track which users have bought/otherwise have access to which apps. Currently there is no way to add entries to this table; that is part of the payment processing work.
1 parent 8f915e6 commit 2ef6e70

File tree

3 files changed

+110
-3
lines changed

3 files changed

+110
-3
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""UserOwnedApp
2+
3+
Revision ID: e0e33ce55700
4+
Revises: 253ab628caf8
5+
Create Date: 2022-03-17 19:05:06.230760
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = 'e0e33ce55700'
14+
down_revision = '253ab628caf8'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_table('userownedapp',
22+
sa.Column('app_id', sa.String(), nullable=False),
23+
sa.Column('account', sa.Integer(), nullable=False),
24+
sa.Column('created', sa.DateTime(), nullable=False),
25+
sa.ForeignKeyConstraint(['account'], ['flathubuser.id'], ondelete='CASCADE'),
26+
sa.PrimaryKeyConstraint('app_id', 'account')
27+
)
28+
# ### end Alembic commands ###
29+
30+
31+
def downgrade():
32+
# ### commands auto generated by Alembic - please adjust! ###
33+
op.drop_table('userownedapp')
34+
# ### end Alembic commands ###

app/main.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55

66
import jwt
77
import sentry_sdk
8-
from fastapi import FastAPI, Response
8+
from fastapi import Depends, FastAPI, Response
99
from fastapi.middleware.cors import CORSMiddleware
10+
from fastapi.responses import JSONResponse
11+
from fastapi_sqlalchemy import db as sqldb
1012
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
1113

14+
from app import models
15+
1216
from . import (
1317
apps,
1418
config,
@@ -204,10 +208,27 @@ def get_summary(appid: str, response: Response):
204208

205209

206210
@app.post("/generate-download-token", status_code=200)
207-
def get_download_token(appids: List[str]):
211+
def get_download_token(appids: List[str], login=Depends(logins.login_state)):
208212
"""Generates a download token for the given app IDs."""
209213

210-
# TODO: Check the user has rights to download the given app IDs!
214+
if not login["state"].logged_in():
215+
return JSONResponse({ "detail": "not_logged_in" }, status_code=401)
216+
user = login["user"]
217+
218+
unowned = [
219+
app_id
220+
for app_id in appids
221+
if not models.UserOwnedApp.user_owns_app(sqldb, user, app_id)
222+
]
223+
224+
if len(unowned) != 0:
225+
return JSONResponse(
226+
{
227+
"detail": "purchase_necessary",
228+
"missing_appids": unowned,
229+
},
230+
status_code=403,
231+
)
211232

212233
encoded = jwt.encode(
213234
{

app/models.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,55 @@ def delete_user(db, user: FlathubUser):
327327

328328

329329
FlathubUser.TABLES_FOR_DELETE.append(UserVerifiedApp)
330+
331+
332+
class UserOwnedApp(Base):
333+
__tablename__ = "userownedapp"
334+
335+
app_id = Column(String, nullable=False, primary_key=True)
336+
account = Column(
337+
Integer,
338+
ForeignKey(FlathubUser.id, ondelete="CASCADE"),
339+
nullable=False,
340+
primary_key=True,
341+
)
342+
created = Column(DateTime, nullable=False)
343+
344+
@staticmethod
345+
def user_owns_app(db, user: FlathubUser, app_id: str):
346+
# If the user is trying to download "org.gnome.Clocks.Locale", permission for "org.gnome.Clocks" should suffice.
347+
# Note that the authoritative check for this rule is in flat-manager, not here.
348+
segments = app_id.split(".")
349+
prefixes = [".".join(segments[:i]) for i in range(len(segments) + 1)]
350+
351+
return (
352+
db.session.query(UserOwnedApp)
353+
.filter_by(account=user.id)
354+
.filter(UserOwnedApp.app_id.in_(prefixes))
355+
.first()
356+
is not None
357+
)
358+
359+
@staticmethod
360+
def all_by_user(db, user: FlathubUser):
361+
return db.session.query(UserOwnedApp).filter_by(account=user.id)
362+
363+
@staticmethod
364+
def delete_hash(hasher: utils.Hasher, db, user: FlathubUser):
365+
"""
366+
Add a user's owned apps to the hasher for token generation
367+
"""
368+
apps = [app.app_id for app in UserOwnedApp.all_by_user(db, user)]
369+
apps.sort()
370+
for app in apps:
371+
hasher.add_string(app)
372+
373+
@staticmethod
374+
def delete_user(db, user: FlathubUser):
375+
"""
376+
Delete any app ownerships associated with this user
377+
"""
378+
db.session.execute(delete(UserOwnedApp).where(UserOwnedApp.account == user.id))
379+
380+
381+
FlathubUser.TABLES_FOR_DELETE.append(UserOwnedApp)

0 commit comments

Comments
 (0)