dffdMerge branch 'master' of https://github.com/Aevann1/Drama
commit
8a602a61a9
|
@ -2,11 +2,13 @@ FROM ubuntu:20.04
|
||||||
|
|
||||||
COPY supervisord.conf /etc/supervisord.conf
|
COPY supervisord.conf /etc/supervisord.conf
|
||||||
|
|
||||||
RUN apt update \ && apt install -y python3.8 python3-pip supervisor
|
RUN apt update && apt install -y python3.8 python3-pip supervisor
|
||||||
|
|
||||||
RUN mkdir ./service
|
RUN mkdir -p ./service
|
||||||
|
|
||||||
RUN pip3 install -r requirements.txt \ && cd ./service
|
COPY requirements.txt ./service/requirements.txt
|
||||||
|
|
||||||
|
RUN cd ./service && pip3 install -r requirements.txt
|
||||||
|
|
||||||
EXPOSE 80/tcp
|
EXPOSE 80/tcp
|
||||||
|
|
||||||
|
|
|
@ -4,26 +4,19 @@ from .alerts import send_notification
|
||||||
from drama.__main__ import app
|
from drama.__main__ import app
|
||||||
|
|
||||||
|
|
||||||
def get_logged_in_user(db=None):
|
def get_logged_in_user():
|
||||||
|
|
||||||
if not db:
|
|
||||||
db=g.db
|
|
||||||
|
|
||||||
if request.headers.get("Authorization"):
|
if request.headers.get("Authorization"):
|
||||||
token = request.headers.get("Authorization")
|
token = request.headers.get("Authorization")
|
||||||
if not token: return None, None
|
if not token: return None, None
|
||||||
|
|
||||||
token = token.split()
|
token = token.split()
|
||||||
if len(token) < 2:
|
if len(token) < 2: return None, None
|
||||||
return None, None
|
|
||||||
|
|
||||||
token = token[1]
|
token = token[1]
|
||||||
if not token:
|
if not token: return None, None
|
||||||
return None, None
|
|
||||||
|
|
||||||
client = db.query(ClientAuth).filter(
|
client = g.db.query(ClientAuth).filter(ClientAuth.access_token == token).first()
|
||||||
ClientAuth.access_token == token).first()
|
|
||||||
#ClientAuth.access_token_expire_utc > int(time.time()
|
|
||||||
|
|
||||||
x = (client.user, client) if client else (None, None)
|
x = (client.user, client) if client else (None, None)
|
||||||
|
|
||||||
|
@ -239,31 +232,6 @@ def validate_formkey(f):
|
||||||
wrapper.__name__ = f.__name__
|
wrapper.__name__ = f.__name__
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def no_cors(f):
|
|
||||||
"""
|
|
||||||
Decorator prevents content being iframe'd
|
|
||||||
"""
|
|
||||||
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
|
|
||||||
origin = request.headers.get("Origin", None)
|
|
||||||
|
|
||||||
if origin and origin != "https://" + app.config["SERVER_NAME"] and app.config["FORCE_HTTPS"]==1:
|
|
||||||
|
|
||||||
return "This page may not be embedded in other webpages.", 403
|
|
||||||
|
|
||||||
resp = make_response(f(*args, **kwargs))
|
|
||||||
resp.headers.add("Access-Control-Allow-Origin",
|
|
||||||
app.config["SERVER_NAME"]
|
|
||||||
)
|
|
||||||
|
|
||||||
return resp
|
|
||||||
|
|
||||||
wrapper.__name__ = f.__name__
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def api(*scopes, no_ban=False):
|
def api(*scopes, no_ban=False):
|
||||||
|
|
||||||
def wrapper_maker(f):
|
def wrapper_maker(f):
|
||||||
|
|
|
@ -440,23 +440,11 @@ def admin_removed(v):
|
||||||
@admin_level_required(4)
|
@admin_level_required(4)
|
||||||
def admin_appdata(v):
|
def admin_appdata(v):
|
||||||
|
|
||||||
url=request.args.get("link")
|
return render_template(
|
||||||
|
"admin/app_data.html",
|
||||||
if url:
|
v=v,
|
||||||
|
thing=get_post(4020)
|
||||||
thing = get_from_permalink(url, v=v)
|
)
|
||||||
|
|
||||||
return render_template(
|
|
||||||
"admin/app_data.html",
|
|
||||||
v=v,
|
|
||||||
thing=thing
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
return render_template(
|
|
||||||
"admin/app_data.html",
|
|
||||||
v=v)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/admin/image_purge")
|
@app.post("/admin/image_purge")
|
||||||
@admin_level_required(5)
|
@admin_level_required(5)
|
||||||
|
|
|
@ -7,7 +7,6 @@ valid_password_regex = re.compile("^.{8,100}$")
|
||||||
|
|
||||||
|
|
||||||
@app.get("/login")
|
@app.get("/login")
|
||||||
@no_cors
|
|
||||||
@auth_desired
|
@auth_desired
|
||||||
def login_get(v):
|
def login_get(v):
|
||||||
|
|
||||||
|
@ -51,7 +50,6 @@ def check_for_alts(current_id):
|
||||||
# login post procedure
|
# login post procedure
|
||||||
|
|
||||||
|
|
||||||
@no_cors
|
|
||||||
@app.post("/login")
|
@app.post("/login")
|
||||||
@limiter.limit("6/minute")
|
@limiter.limit("6/minute")
|
||||||
def login_post():
|
def login_post():
|
||||||
|
@ -152,7 +150,6 @@ def logout(v):
|
||||||
|
|
||||||
|
|
||||||
@app.get("/signup")
|
@app.get("/signup")
|
||||||
@no_cors
|
|
||||||
@auth_desired
|
@auth_desired
|
||||||
def sign_up_get(v):
|
def sign_up_get(v):
|
||||||
with open('./disablesignups', 'r') as f:
|
with open('./disablesignups', 'r') as f:
|
||||||
|
@ -207,7 +204,6 @@ def sign_up_get(v):
|
||||||
|
|
||||||
|
|
||||||
@app.post("/signup")
|
@app.post("/signup")
|
||||||
@no_cors
|
|
||||||
@auth_desired
|
@auth_desired
|
||||||
def sign_up_post(v):
|
def sign_up_post(v):
|
||||||
with open('./disablesignups', 'r') as f:
|
with open('./disablesignups', 'r') as f:
|
||||||
|
|
|
@ -5,217 +5,38 @@ from drama.classes import *
|
||||||
from flask import *
|
from flask import *
|
||||||
from drama.__main__ import app
|
from drama.__main__ import app
|
||||||
|
|
||||||
SCOPES = {
|
@app.get("/authorize")
|
||||||
'identity': 'See your username',
|
|
||||||
'create': 'Save posts and comments as you',
|
|
||||||
'read': 'View Drama as you, including private or restricted content',
|
|
||||||
'update': 'Edit your posts and comments',
|
|
||||||
'delete': 'Delete your posts and comments',
|
|
||||||
'vote': 'Cast votes as you',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/oauth/authorize")
|
|
||||||
@auth_required
|
@auth_required
|
||||||
def oauth_authorize_prompt(v):
|
def authorize_prompt(v):
|
||||||
'''
|
|
||||||
This page takes the following URL parameters:
|
|
||||||
* client_id - Your application client ID
|
|
||||||
* scope - Comma-separated list of scopes. Scopes are described above
|
|
||||||
* redirect_uri - Your redirect link
|
|
||||||
* state - Your anti-csrf token
|
|
||||||
'''
|
|
||||||
|
|
||||||
client_id = request.args.get("client_id")
|
client_id = request.args.get("client_id")
|
||||||
|
|
||||||
|
|
||||||
application = g.db.query(OauthApp).filter_by(client_id=client_id).first()
|
application = g.db.query(OauthApp).filter_by(client_id=client_id).first()
|
||||||
|
|
||||||
if not application:
|
if not application: return {"oauth_error": "Invalid `client_id`"}, 401
|
||||||
return {"oauth_error": "Invalid `client_id`"}, 401
|
if application.is_banned: return {"oauth_error": f"Application `{application.app_name}` is suspended."}, 403
|
||||||
|
|
||||||
if application.is_banned:
|
|
||||||
return {"oauth_error": f"Application `{application.app_name}` is suspended."}, 403
|
|
||||||
|
|
||||||
scopes_txt = request.args.get('scope', "")
|
|
||||||
|
|
||||||
scopes = scopes_txt.split(',')
|
|
||||||
if not scopes:
|
|
||||||
return {"oauth_error": "One or more scopes must be specified as a comma-separated list."}, 400
|
|
||||||
|
|
||||||
for scope in scopes:
|
|
||||||
if scope not in SCOPES:
|
|
||||||
return {"oauth_error": f"The provided scope `{scope}` is not valid."}, 400
|
|
||||||
|
|
||||||
if any(x in scopes for x in ["create", "update"]) and "identity" not in scopes:
|
|
||||||
return {"oauth_error": f"`identity` scope required when requesting `create` or `update` scope."}, 400
|
|
||||||
|
|
||||||
redirect_uri = request.args.get("redirect_uri")
|
redirect_uri = request.args.get("redirect_uri")
|
||||||
if not redirect_uri:
|
if not redirect_uri: return {"oauth_error": f"`redirect_uri` must be provided."}, 400
|
||||||
return {"oauth_error": f"`redirect_uri` must be provided."}, 400
|
return render_template("oauth.html", v=v, application=application, redirect_uri=redirect_uri)
|
||||||
|
|
||||||
valid_redirect_uris = [x.strip()
|
|
||||||
for x in application.redirect_uri.split(",")]
|
|
||||||
|
|
||||||
if redirect_uri not in valid_redirect_uris:
|
|
||||||
return {"oauth_error": "Invalid redirect_uri"}, 400
|
|
||||||
|
|
||||||
state = request.args.get("state")
|
|
||||||
if not state:
|
|
||||||
return {'oauth_error': 'state argument required'}, 400
|
|
||||||
|
|
||||||
permanent = bool(request.args.get("permanent"))
|
|
||||||
|
|
||||||
return render_template("oauth.html",
|
|
||||||
v=v,
|
|
||||||
application=application,
|
|
||||||
SCOPES=SCOPES,
|
|
||||||
state=state,
|
|
||||||
scopes=scopes,
|
|
||||||
scopes_txt=scopes_txt,
|
|
||||||
redirect_uri=redirect_uri,
|
|
||||||
permanent=int(permanent),
|
|
||||||
i=random_image()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/oauth/authorize")
|
@app.post("/authorize")
|
||||||
@auth_required
|
@auth_required
|
||||||
@validate_formkey
|
@validate_formkey
|
||||||
def oauth_authorize_post(v):
|
def oauth(v):
|
||||||
|
|
||||||
client_id = request.form.get("client_id")
|
client_id = request.form.get("client_id")
|
||||||
scopes_txt = request.form.get("scopes")
|
|
||||||
state = request.form.get("state")
|
|
||||||
redirect_uri = request.form.get("redirect_uri")
|
|
||||||
|
|
||||||
application = g.db.query(OauthApp).filter_by(client_id=client_id).first()
|
application = g.db.query(OauthApp).filter_by(client_id=client_id).first()
|
||||||
if not application:
|
if not application: return {"oauth_error": "Invalid `client_id`"}, 401
|
||||||
return {"oauth_error": "Invalid `client_id`"}, 401
|
if application.is_banned: return {"oauth_error": f"Application `{application.app_name}` is suspended."}, 403
|
||||||
if application.is_banned:
|
access_token = secrets.token_urlsafe(128)[:128]
|
||||||
return {"oauth_error": f"Application `{application.app_name}` is suspended."}, 403
|
|
||||||
|
|
||||||
valid_redirect_uris = [x.strip()
|
|
||||||
for x in application.redirect_uri.split(",")]
|
|
||||||
if redirect_uri not in valid_redirect_uris:
|
|
||||||
return {"oauth_error": "Invalid redirect_uri"}, 400
|
|
||||||
|
|
||||||
scopes = scopes_txt.split(',')
|
|
||||||
if not scopes:
|
|
||||||
return {"oauth_error": "One or more scopes must be specified as a comma-separated list"}, 400
|
|
||||||
|
|
||||||
for scope in scopes:
|
|
||||||
if scope not in SCOPES:
|
|
||||||
return {"oauth_error": f"The provided scope `{scope}` is not valid."}, 400
|
|
||||||
|
|
||||||
if any(x in scopes for x in ["create", "update"]) and "identity" not in scopes:
|
|
||||||
return {"oauth_error": f"`identity` scope required when requesting `create` or `update` scope."}, 400
|
|
||||||
|
|
||||||
if not state:
|
|
||||||
return {'oauth_error': 'state argument required'}, 400
|
|
||||||
|
|
||||||
permanent = bool(int(request.values.get("permanent", 0)))
|
|
||||||
|
|
||||||
new_auth = ClientAuth(
|
new_auth = ClientAuth(
|
||||||
oauth_client=application.id,
|
oauth_client = application.id,
|
||||||
oauth_code=secrets.token_urlsafe(128)[:128],
|
user_id = v.id,
|
||||||
user_id=v.id,
|
access_token=access_token
|
||||||
scope_identity="identity" in scopes,
|
|
||||||
scope_create="create" in scopes,
|
|
||||||
scope_read="read" in scopes,
|
|
||||||
scope_update="update" in scopes,
|
|
||||||
scope_delete="delete" in scopes,
|
|
||||||
scope_vote="vote" in scopes,
|
|
||||||
refresh_token=secrets.token_urlsafe(128)[:128] if permanent else None
|
|
||||||
)
|
)
|
||||||
|
|
||||||
g.db.add(new_auth)
|
g.db.add(new_auth)
|
||||||
|
|
||||||
return redirect(f"{redirect_uri}?code={new_auth.oauth_code}&scopes={scopes_txt}&state={state}")
|
return redirect(f"{application.redirect_uri}?token={access_token}")
|
||||||
|
|
||||||
|
|
||||||
@app.post("/oauth/grant")
|
|
||||||
def oauth_grant():
|
|
||||||
'''
|
|
||||||
This endpoint takes the following parameters:
|
|
||||||
* code - The code parameter provided in the redirect
|
|
||||||
* client_id - Your client ID
|
|
||||||
* client_secret - your client secret
|
|
||||||
'''
|
|
||||||
|
|
||||||
application = g.db.query(OauthApp).filter_by(
|
|
||||||
client_id=request.values.get("client_id"),
|
|
||||||
client_secret=request.values.get("client_secret")).first()
|
|
||||||
if not application:
|
|
||||||
return {"oauth_error": "Invalid `client_id` or `client_secret`"}, 401
|
|
||||||
if application.is_banned:
|
|
||||||
return {"oauth_error": f"Application `{application.app_name}` is suspended."}, 403
|
|
||||||
|
|
||||||
if request.values.get("grant_type") == "code":
|
|
||||||
|
|
||||||
code = request.values.get("code")
|
|
||||||
if not code:
|
|
||||||
return {"oauth_error": "code required"}, 400
|
|
||||||
|
|
||||||
auth = g.db.query(ClientAuth).filter_by(
|
|
||||||
oauth_code=code,
|
|
||||||
access_token=None,
|
|
||||||
oauth_client=application.id
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if not auth:
|
|
||||||
return {"oauth_error": "Invalid code"}, 401
|
|
||||||
|
|
||||||
auth.oauth_code = None
|
|
||||||
auth.access_token = secrets.token_urlsafe(128)[:128]
|
|
||||||
auth.access_token_expire_utc = int(time.time()) + 60 * 60
|
|
||||||
|
|
||||||
g.db.add(auth)
|
|
||||||
|
|
||||||
g.db.commit()
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"access_token": auth.access_token,
|
|
||||||
"scopes": auth.scopelist,
|
|
||||||
"expires_at": auth.access_token_expire_utc,
|
|
||||||
"token_type": "Bearer"
|
|
||||||
}
|
|
||||||
|
|
||||||
if auth.refresh_token:
|
|
||||||
data["refresh_token"] = auth.refresh_token
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
elif request.values.get("grant_type") == "refresh":
|
|
||||||
|
|
||||||
refresh_token = request.values.get('refresh_token')
|
|
||||||
if not refresh_token:
|
|
||||||
return {"oauth_error": "refresh_token required"}, 401
|
|
||||||
|
|
||||||
auth = g.db.query(ClientAuth).filter_by(
|
|
||||||
refresh_token=refresh_token,
|
|
||||||
oauth_code=None,
|
|
||||||
oauth_client=application.id
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if not auth:
|
|
||||||
return {"oauth_error": "Invalid refresh_token"}, 401
|
|
||||||
|
|
||||||
auth.access_token = secrets.token_urlsafe(128)[:128]
|
|
||||||
auth.access_token_expire_utc = int(time.time()) + 60 * 60
|
|
||||||
|
|
||||||
g.db.add(auth)
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"access_token": auth.access_token,
|
|
||||||
"scopes": auth.scopelist,
|
|
||||||
"expires_at": auth.access_token_expire_utc
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
else:
|
|
||||||
return {"oauth_error": f"Invalid grant_type `{request.values.get('grant_type','')}`. Expected `code` or `refresh`."}, 400
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api_keys")
|
@app.post("/api_keys")
|
||||||
|
@ -391,77 +212,6 @@ def admin_app_id_comments(v, aid):
|
||||||
@admin_level_required(3)
|
@admin_level_required(3)
|
||||||
def admin_apps_list(v):
|
def admin_apps_list(v):
|
||||||
|
|
||||||
apps = g.db.query(OauthApp).options(
|
apps = g.db.query(OauthApp).all()
|
||||||
joinedload(
|
|
||||||
OauthApp.author)).filter(
|
|
||||||
OauthApp.client_id==None).order_by(
|
|
||||||
OauthApp.id.desc()).all()
|
|
||||||
|
|
||||||
return render_template("admin/apps.html", v=v, apps=apps)
|
return render_template("admin/apps.html", v=v, apps=apps)
|
||||||
|
|
||||||
|
|
||||||
@app.post("/oauth/reroll/<aid>")
|
|
||||||
@auth_required
|
|
||||||
def reroll_oauth_tokens(aid, v):
|
|
||||||
|
|
||||||
aid = aid
|
|
||||||
|
|
||||||
a = g.db.query(OauthApp).filter_by(id=aid).first()
|
|
||||||
|
|
||||||
if a.author_id != v.id:
|
|
||||||
abort(403)
|
|
||||||
|
|
||||||
a.client_id = secrets.token_urlsafe(64)[:64]
|
|
||||||
a.client_secret = secrets.token_urlsafe(128)[:128]
|
|
||||||
|
|
||||||
g.db.add(a)
|
|
||||||
|
|
||||||
return {"message": "Tokens Rerolled", "id": a.client_id, "secret": a.client_secret}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/oauth/rescind/<aid>")
|
|
||||||
@auth_required
|
|
||||||
@validate_formkey
|
|
||||||
def oauth_rescind_app(aid, v):
|
|
||||||
|
|
||||||
aid = aid
|
|
||||||
auth = g.db.query(ClientAuth).filter_by(id=aid).first()
|
|
||||||
|
|
||||||
if auth.user_id != v.id:
|
|
||||||
abort(403)
|
|
||||||
|
|
||||||
g.db.delete(auth)
|
|
||||||
|
|
||||||
return {"message": f"{auth.application.app_name} Revoked"}
|
|
||||||
|
|
||||||
@app.post("/release")
|
|
||||||
@auth_required
|
|
||||||
def oauth_release_auth(v):
|
|
||||||
|
|
||||||
token=request.headers.get("Authorization").split()[1]
|
|
||||||
|
|
||||||
auth = g.db.query(ClientAuth).filter_by(user_id=v.id, access_token=token).first()
|
|
||||||
if not auth:
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
if not auth.refresh_token:
|
|
||||||
abort(400)
|
|
||||||
|
|
||||||
auth.access_token_expire_utc=0
|
|
||||||
g.db.add(auth)
|
|
||||||
|
|
||||||
return {"message":"Authorization released"}
|
|
||||||
|
|
||||||
@app.post("/kill")
|
|
||||||
@auth_required
|
|
||||||
def oauth_kill_auth(v):
|
|
||||||
|
|
||||||
token=request.headers.get("Authorization").split()[1]
|
|
||||||
|
|
||||||
auth = g.db.query(ClientAuth).filter_by(user_id=v.id, access_token=token).first()
|
|
||||||
if not auth:
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
g.db.delete(auth)
|
|
||||||
|
|
||||||
return {"message":"Authorization released"}
|
|
Loading…
Reference in New Issue