switching to simpler flask app

This commit is contained in:
lza_menace 2020-07-15 01:18:13 -07:00
parent eb6d6b388e
commit 95c5882e39
25 changed files with 264 additions and 235 deletions

2
.gitignore vendored
View file

@ -5,3 +5,5 @@ __pycache__
*zip
*tar.gz
*sql
flask_session
config.py

7
bin/cmd Executable file
View 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
View 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
View file

@ -0,0 +1,5 @@
#!/bin/bash
python3 -m venv .venv
source .venv/bin/activate
pip3 install -r requirements.txt

View file

@ -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()

View file

View file

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View file

@ -1,5 +0,0 @@
from django.apps import AppConfig
class MemesConfig(AppConfig):
name = 'memes'

View file

@ -1,3 +0,0 @@
from django.db import models
# Create your models here.

View file

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View file

@ -1,7 +0,0 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]

View file

@ -1,6 +0,0 @@
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
return HttpResponse("hey, testing")

View file

@ -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
View 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()

View file

@ -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()

View 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
View 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

View file

@ -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/'

View 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>

View 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 %}

View 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 %}

View 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 %}

View file

@ -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'))
]

View file

@ -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()