1
0
Fork 0
mirror of https://github.com/YunoHost/apps.git synced 2024-09-03 20:06:07 +02:00
apps/store/app.py
2023-08-17 13:57:32 +02:00

133 lines
3.8 KiB
Python

import toml
import base64
import hashlib
import hmac
import os
import random
import urllib
import json
import sys
from flask import Flask, send_from_directory, render_template, session, redirect, request
app = Flask(__name__)
catalog = json.load(open("apps.json"))
catalog['categories'] = {c['id']:c for c in catalog['categories']}
try:
config = toml.loads(open("config.toml").read())
DISCOURSE_SSO_SECRET = config["DISCOURSE_SSO_SECRET"]
DISCOURSE_SSO_ENDPOINT = config["DISCOURSE_SSO_ENDPOINT"]
CALLBACK_URL_AFTER_LOGIN_ON_DISCOURSE = config["CALLBACK_URL_AFTER_LOGIN_ON_DISCOURSE"]
except Exception as e:
print("You should create a config.toml with the appropriate key/values, cf config.toml.example")
print(e)
sys.exit(1)
if config.get("DEBUG"):
app.debug = True
app.config["DEBUG"] = True
app.config['TEMPLATES_AUTO_RELOAD'] = True
category_color = {
"synchronization": "sky",
"publishing": "yellow",
"communication": "amber",
"office": "lime",
"productivity_and_management": "purple",
"small_utilities": "",
"reading": "emerald",
"multimedia": "fuchsia",
"social_media": "rose",
"games": "violet",
"dev": "stone",
"system_tools": "white",
"iot": "orange",
"wat": "teal",
}
for id_, category in catalog['categories'].items():
category["color"] = category_color[id_]
wishlist = json.load(open("wishlist.json"))
# This is the secret key used for session signing
app.secret_key = ''.join([str(random.randint(0, 9)) for i in range(99)])
@app.route('/login_using_discourse')
def login_using_discourse():
"""
Send auth request to Discourse:
"""
nonce, url = create_nonce_and_build_url_to_login_on_discourse_sso()
session.clear()
session["nonce"] = nonce
return redirect(url)
@app.route('/sso_login_callback')
def sso_login_callback():
response = base64.b64decode(request.args['sso'].encode()).decode()
user_data = urllib.parse.parse_qs(response)
if user_data['nonce'][0] != session.get("nonce"):
return "Invalid nonce", 401
else:
session.clear()
session['user'] = {
"id": user_data["external_id"][0],
"username": user_data["username"][0],
"avatar_url": user_data["avatar_url"][0],
}
return redirect("/")
@app.route('/logout')
def logout():
session.clear()
return redirect("/")
@app.route('/')
def index():
return render_template("index.html", user=session.get('user', {}), catalog=catalog)
@app.route('/catalog')
def browse_catalog(category_filter=None):
return render_template("catalog.html", user=session.get('user', {}), catalog=catalog)
@app.route('/app/<app_id>')
def app_info(app_id):
infos = catalog["apps"].get(app_id)
if not infos:
return f"App {app_id} not found", 404
return render_template("app.html", user=session.get('user', {}), app_id=app_id, infos=infos)
@app.route('/wishlist')
def browse_wishlist():
return render_template("wishlist.html", user=session.get('user', {}), wishlist=wishlist)
################################################
def create_nonce_and_build_url_to_login_on_discourse_sso():
"""
Redirect the user to DISCOURSE_ROOT_URL/session/sso_provider?sso=URL_ENCODED_PAYLOAD&sig=HEX_SIGNATURE
"""
nonce = ''.join([str(random.randint(0, 9)) for i in range(99)])
url_data = {"nonce": nonce, "return_sso_url": CALLBACK_URL_AFTER_LOGIN_ON_DISCOURSE}
url_encoded = urllib.parse.urlencode(url_data)
payload = base64.b64encode(url_encoded.encode()).decode()
sig = hmac.new(DISCOURSE_SSO_SECRET.encode(), msg=payload.encode(), digestmod=hashlib.sha256).hexdigest()
data = {"sig": sig, "sso": payload}
url = f"{DISCOURSE_SSO_ENDPOINT}?{urllib.parse.urlencode(data)}"
return nonce, url