mirror of
https://git.wownero.com/lza_menace/suchwow.git
synced 2024-08-15 01:03:19 +00:00
switching to simpler flask app
This commit is contained in:
parent
eb6d6b388e
commit
95c5882e39
25 changed files with 264 additions and 235 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -5,3 +5,5 @@ __pycache__
|
|||
*zip
|
||||
*tar.gz
|
||||
*sql
|
||||
flask_session
|
||||
config.py
|
||||
|
|
7
bin/cmd
Executable file
7
bin/cmd
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
source .venv/bin/activate
|
||||
export FLASK_APP=suchwow/app.py
|
||||
export FLASK_SECRETS=config.py
|
||||
export FLASK_DEBUG=1
|
||||
flask $1
|
7
bin/dev
Executable file
7
bin/dev
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
source .venv/bin/activate
|
||||
export FLASK_APP=suchwow/app.py
|
||||
export FLASK_SECRETS=config.py
|
||||
export FLASK_DEBUG=1
|
||||
flask run
|
5
bin/setup
Executable file
5
bin/setup
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip3 install -r requirements.txt
|
21
manage.py
21
manage.py
|
@ -1,21 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'suchwow.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,3 +0,0 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -1,5 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MemesConfig(AppConfig):
|
||||
name = 'memes'
|
|
@ -1,3 +0,0 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
|
@ -1,3 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -1,7 +0,0 @@
|
|||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.index, name='index'),
|
||||
]
|
|
@ -1,6 +0,0 @@
|
|||
from django.shortcuts import render
|
||||
from django.http import HttpResponse
|
||||
|
||||
|
||||
def index(request):
|
||||
return HttpResponse("hey, testing")
|
|
@ -1,17 +1,4 @@
|
|||
asgiref==3.2.10
|
||||
certifi==2020.6.20
|
||||
chardet==3.0.4
|
||||
Django==3.0.8
|
||||
django-keycloak==0.1.1
|
||||
django-vote==2.2.0
|
||||
ecdsa==0.15
|
||||
idna==2.10
|
||||
pyasn1==0.4.8
|
||||
python-jose==3.1.0
|
||||
python-keycloak-client==0.2.3
|
||||
pytz==2020.1
|
||||
requests==2.24.0
|
||||
rsa==4.6
|
||||
six==1.15.0
|
||||
sqlparse==0.3.1
|
||||
urllib3==1.25.9
|
||||
requests
|
||||
flask
|
||||
flask-session
|
||||
peewee
|
||||
|
|
166
suchwow/app.py
Normal file
166
suchwow/app.py
Normal file
|
@ -0,0 +1,166 @@
|
|||
from functools import wraps
|
||||
import uuid
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
from flask import Flask, g, request, redirect, url_for, abort
|
||||
from flask import jsonify, render_template, flash, session
|
||||
from flask import send_from_directory, make_response
|
||||
from flask_session import Session
|
||||
from werkzeug.utils import secure_filename
|
||||
from suchwow.models import Meme, db
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_envvar("FLASK_SECRETS")
|
||||
app.secret_key = app.config["SECRET_KEY"]
|
||||
Session(app)
|
||||
|
||||
OPENID_URL = app.config["OIDC_URL"][0]
|
||||
OPENID_CLIENT_ID = app.config["OIDC_CLIENT_ID"][0]
|
||||
OPENID_CLIENT_SECRET = app.config["OIDC_CLIENT_SECRET"][0]
|
||||
OPENID_REDIRECT_URI = "http://localhost:5000/auth"
|
||||
|
||||
|
||||
def allowed_file(filename):
|
||||
return "." in filename and \
|
||||
filename.rsplit(".", 1)[1].lower() in app.config["ALLOWED_EXTENSIONS"]
|
||||
|
||||
def login_required(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if "auth" not in session or not session["auth"]:
|
||||
return redirect(url_for("login"))
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template("index.html")
|
||||
|
||||
@app.route("/meme/<id>")
|
||||
def view(id):
|
||||
if Meme.filter(id=id):
|
||||
m = Meme.get(Meme.id == id)
|
||||
return render_template("view.html", meme=m)
|
||||
else:
|
||||
return "no meme there brah"
|
||||
|
||||
@app.route("/uploads/<path:filename>")
|
||||
def uploaded_file(filename):
|
||||
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
|
||||
|
||||
@app.route("/submit", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def submit():
|
||||
if request.method == "POST":
|
||||
# check if the post request has the file part
|
||||
if "file" not in request.files:
|
||||
flash("No file part")
|
||||
return redirect(request.url)
|
||||
file = request.files["file"]
|
||||
# if user does not select file, browser also
|
||||
# submit an empty part without filename
|
||||
if file.filename == "":
|
||||
flash("No selected file")
|
||||
return redirect(request.url)
|
||||
if file and allowed_file(file.filename):
|
||||
filename = secure_filename(file.filename)
|
||||
save_path = os.path.join(app.config["UPLOAD_FOLDER"], filename)
|
||||
file.save(save_path)
|
||||
meme = Meme(
|
||||
title=request.form.get('title'),
|
||||
submitter=session["auth"]["preferred_username"],
|
||||
image_name=filename,
|
||||
)
|
||||
meme.save()
|
||||
return redirect(url_for("view", id=meme.id))
|
||||
return render_template("submit.html")
|
||||
|
||||
@app.route("/login")
|
||||
def login():
|
||||
state = uuid.uuid4().hex
|
||||
session["auth_state"] = state
|
||||
url = f"{OPENID_URL}/auth?" \
|
||||
f"client_id={OPENID_CLIENT_ID}&" \
|
||||
f"redirect_uri={OPENID_REDIRECT_URI}&" \
|
||||
f"response_type=code&" \
|
||||
f"state={state}"
|
||||
|
||||
return redirect(url)
|
||||
|
||||
|
||||
@app.route("/auth/")
|
||||
def auth():
|
||||
# todo
|
||||
assert "state" in request.args
|
||||
assert "session_state" in request.args
|
||||
assert "code" in request.args
|
||||
|
||||
# verify state
|
||||
if not session.get("auth_state"):
|
||||
return "session error", 500
|
||||
if request.args["state"] != session["auth_state"]:
|
||||
return "attack detected :)", 500
|
||||
|
||||
# with this authorization code we can fetch an access token
|
||||
url = f"{OPENID_URL}/token"
|
||||
data = {
|
||||
"grant_type": "authorization_code",
|
||||
"code": request.args["code"],
|
||||
"redirect_uri": OPENID_REDIRECT_URI,
|
||||
"client_id": OPENID_CLIENT_ID,
|
||||
"client_secret": OPENID_CLIENT_SECRET,
|
||||
"state": request.args["state"]
|
||||
}
|
||||
try:
|
||||
resp = requests.post(url, data=data)
|
||||
resp.raise_for_status()
|
||||
except:
|
||||
return resp.content, 500
|
||||
|
||||
data = resp.json()
|
||||
assert "access_token" in data
|
||||
assert data.get("token_type") == "bearer"
|
||||
access_token = data["access_token"]
|
||||
|
||||
# fetch user information with the access token
|
||||
url = f"{OPENID_URL}/userinfo"
|
||||
|
||||
try:
|
||||
resp = requests.post(url, headers={"Authorization": f"Bearer {access_token}"})
|
||||
resp.raise_for_status()
|
||||
user_profile = resp.json()
|
||||
except:
|
||||
return resp.content, 500
|
||||
|
||||
# user can now visit /secret
|
||||
session["auth"] = user_profile
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@app.route("/debug")
|
||||
@login_required
|
||||
def debug():
|
||||
return f"""
|
||||
<h3>We are logged in!</h3>
|
||||
<pre>{json.dumps(session["auth"], indent=4, sort_keys=True)}</pre><br>
|
||||
<a href="/logout">Logout</a>
|
||||
"""
|
||||
|
||||
@app.route("/logout")
|
||||
def logout():
|
||||
session["auth"] = None
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(error):
|
||||
return make_response(jsonify({"error": "Page not found"}), 404)
|
||||
|
||||
@app.cli.command('dbinit')
|
||||
def dbinit():
|
||||
db.create_tables([Meme])
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
ASGI config for suchwow project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'suchwow.settings')
|
||||
|
||||
application = get_asgi_application()
|
9
suchwow/config.example.py
Normal file
9
suchwow/config.example.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
OIDC_URL = 'https://login.wownero.com/auth/realms/master/protocol/openid-connect',
|
||||
OIDC_CLIENT_ID = 'suchwowxxx',
|
||||
OIDC_CLIENT_SECRET = 'xxxxxxxxxx',
|
||||
OIDC_REDIRECT_URL = 'http://localhost:5000/auth'
|
||||
SECRET = 'yyyyyyyyyyyyy',
|
||||
SESSION_TYPE = 'filesystem'
|
||||
UPLOAD_FOLDER = '/path/to/the/uploads'
|
||||
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])
|
||||
MAX_CONTENT_LENGTH = 16 * 1024 * 1024
|
15
suchwow/models.py
Normal file
15
suchwow/models.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from peewee import *
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
db = SqliteDatabase('data/sqlite.db')
|
||||
|
||||
class Meme(Model):
|
||||
id = AutoField()
|
||||
title = CharField()
|
||||
submitter = CharField()
|
||||
image_name = CharField()
|
||||
timestamp = DateTimeField(default=datetime.now)
|
||||
|
||||
class Meta:
|
||||
database = db
|
|
@ -1,114 +0,0 @@
|
|||
"""
|
||||
Django settings for suchwow project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 3.0.8.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.0/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/3.0/ref/settings/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
SECRET_KEY = os.environ['SECRET_KEY']
|
||||
DEBUG = os.environ['DEBUG']
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django_keycloak.apps.KeycloakAppConfig'
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
# 'django_keycloak.middleware.BaseKeycloakMiddleware'
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'suchwow.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'suchwow.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'data/db.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
# Authentication
|
||||
# AUTHENTICATION_BACKENDS = [
|
||||
# 'django_keycloak.auth.backends.KeycloakAuthorizationCodeBackend',
|
||||
# ]
|
||||
# LOGIN_URL = 'keycloak_login'
|
||||
KEYCLOAK_OIDC_PROFILE_MODEL = 'django_keycloak.OpenIdConnectProfile'
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.0/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
TIME_ZONE = 'UTC'
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.0/howto/static-files/
|
||||
STATIC_URL = '/static/'
|
17
suchwow/templates/base.html
Normal file
17
suchwow/templates/base.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Meme Factory</title>
|
||||
<meta name="author" content="name">
|
||||
<meta name="description" content="description here">
|
||||
<meta name="keywords" content="keywords,here">
|
||||
</head>
|
||||
<body>
|
||||
<a href="/">Home</a><br>
|
||||
<a href="/view">See da meemz</a>
|
||||
<hr>
|
||||
{% block content %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
7
suchwow/templates/index.html
Normal file
7
suchwow/templates/index.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<a href="{{ url_for('login') }}">Login</a><br>
|
||||
<a href="{{ url_for('submit') }}">Submit A Meme</a><br>
|
||||
<a href="{{ url_for('debug') }}">Visit the secret page!</a>
|
||||
{% endblock %}
|
11
suchwow/templates/submit.html
Normal file
11
suchwow/templates/submit.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<title>Upload new File</title>
|
||||
<h1>Upload new File</h1>
|
||||
<form method=post enctype=multipart/form-data>
|
||||
<input type="text" name="title"><br>
|
||||
<input type=file name=file>
|
||||
<input type=submit value=Submit>
|
||||
</form>
|
||||
{% endblock %}
|
14
suchwow/templates/view.html
Normal file
14
suchwow/templates/view.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}View Meme{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>Submitter: {{ session['auth']['preferred_username'] }}</p>
|
||||
<p>ID: {{ meme.id }}</p>
|
||||
<p>Title: {{ meme.title }}</p>
|
||||
<p>Submitted: {{ meme.timestamp }}</p>
|
||||
<p>Image Name: {{ meme.image_name }}</p>
|
||||
<img src="{{ url_for('uploaded_file', filename=meme.image_name) }}" width=400/>
|
||||
{% endblock %}
|
|
@ -1,24 +0,0 @@
|
|||
"""suchwow URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/3.0/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
import memes
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('keycloak/', include('django_keycloak.urls')),
|
||||
path('', include('memes.urls'))
|
||||
]
|
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
WSGI config for suchwow project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'suchwow.settings')
|
||||
|
||||
application = get_wsgi_application()
|
Loading…
Reference in a new issue