mirror of
https://github.com/YunoHost/apps.git
synced 2024-09-03 20:06:07 +02:00
133 lines
3.8 KiB
Python
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
|