From 08ef5005d4ce0dcf4d1272257325c304dd723fd0 Mon Sep 17 00:00:00 2001 From: Drew Short Date: Wed, 30 Jan 2019 18:15:31 -0600 Subject: [PATCH] Attempting to create a working docker deploy --- .dockerignore | 11 +++++ .gitignore | 7 +++- Dockerfile | 18 ++++++++ app.py | 83 ++++++++++++++++++++++++++++++------- db.py | 6 ++- docker-compose.yml | 14 +++++++ example.env | 7 ++++ register_new_matrix_user.py | 44 ++++++++------------ templates/base.html | 3 +- 9 files changed, 148 insertions(+), 45 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 example.env diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e42a7d8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +*.env +*.conf + +.git +.gitignore +*.db + +Dockerfile +docker-compose.yml + +tmp \ No newline at end of file diff --git a/.gitignore b/.gitignore index 713ed43..66a2dfa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ -.idea +__pycache__ +tmp -*.db \ No newline at end of file +.idea +*.db +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5e27441 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.7 + +MAINTAINER Drew Short + +RUN mkdir /srv/portal /srv/portal/data + +ENV APP_DATA_DIRECTORY=/srv/portal/data + +VOLUME /srv/portal/data + +WORKDIR /srv/portal + +CMD ["gunicorn", "-b", "0.0.0.0:8080", "app:app"] + +COPY . /srv/portal +RUN cd /srv/portal \ + && pip install -r requirements.txt \ + && pip install gunicorn \ No newline at end of file diff --git a/app.py b/app.py index a654642..4cca343 100644 --- a/app.py +++ b/app.py @@ -1,12 +1,12 @@ import logging import os import uuid +from logging.config import dictConfig from urllib.parse import urlparse, urljoin import flask -from flask import Flask, redirect, render_template, request, g, flash -from flask_login import LoginManager, login_required, login_user, logout_user, UserMixin, \ - current_user +from flask import Flask, redirect, render_template, request, g, flash, url_for +from flask_login import LoginManager, login_required, login_user, logout_user, UserMixin from flask_wtf import CSRFProtect from db import get_db, get_registration_codes, add_registration_code, \ @@ -14,15 +14,36 @@ from db import get_db, get_registration_codes, add_registration_code, \ add_registered_user from forms import RegistrationForm, LoginForm, RegistrationCodeForm, \ ExpireRegistrationCodeForm +from register_new_matrix_user import register_new_user csrf = CSRFProtect() login_manager = LoginManager() +dictConfig({ + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': {'default': { + 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s', + }}, + 'handlers': { + 'wsgi': { + 'class': 'logging.StreamHandler', + 'stream': 'ext://flask.logging.wsgi_errors_stream', + 'formatter': 'default' + } + }, + 'root': { + 'level': 'INFO', + 'handlers': ['wsgi'] + } +}) + log = logging.getLogger(__name__) def init_db(flask_app): with flask_app.app_context(): + log.info("Initializing DB") db = get_db() with flask_app.open_resource('schema.sql', mode='r') as f: db.cursor().executescript(f.read()) @@ -34,12 +55,16 @@ def create_app(): flask_app = Flask(__name__) flask_app.config.update(dict( + APPLICATION_ROOT=os.getenv("APPLICATION_ROOT", "/"), ADMIN_TOKEN=os.getenv("ADMIN_TOKEN", uuid.uuid4().__str__()), SECRET_KEY=os.getenv("SECRET_KEY", "changeme"), - WTF_CSRF_SECRET_KEY=os.getenv("CSRF_SECRET_KEY", "csrf_changeme") + WTF_CSRF_SECRET_KEY=os.getenv("CSRF_SECRET_KEY", "csrf_changeme"), + MATRIX_HOMESERVER=os.getenv("MATRIX_HOMESERVER"), + MATRIX_SHARED_SECRET=os.getenv("MATRIX_SHARED_SECRET"), + REGISTRATION_SUCCESS_REDIRECT=os.getenv("REGISTRATION_SUCCESS_REDIRECT") )) - print("Admin Token: %s" % flask_app.config.get("ADMIN_TOKEN")) + log.info("Admin Token: %s" % flask_app.config.get("ADMIN_TOKEN")) csrf.init_app(flask_app) @@ -48,11 +73,15 @@ def create_app(): init_db(flask_app) + log.info("Application ready") + return flask_app app = create_app() +log.info("Bound reverse proxy wsgi app") + def flash_form_errors(form): if hasattr(form, 'errors') and len(form.errors) > 0: @@ -82,6 +111,13 @@ def is_safe_url(target): return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc +def get_successful_registration_redirect(): + target = app.config.get('REGISTRATION_SUCCESS_REDIRECT') + if target is None or not target.startswith('http'): + return url_for('index', _external=True) + return target + + @login_manager.user_loader def load_user(user_id): if user_id == "admin": @@ -94,15 +130,33 @@ def load_user(user_id): @app.route('/') def index(): - return redirect('/register') + return redirect(url_for('registration')) @app.route('/register', methods=('GET', 'POST')) def registration(): form = RegistrationForm() if form.validate_on_submit(): - add_registered_user(form.registration_code.data, form.username.data) - return redirect('/success') + if app.config.get("MATRIX_HOMESERVER") is None: + flash("Matrix Homeserver Currently Unavailable. Please Try Again Later!") + return render_template('register.html', form=form) + else: + if app.config.get("MATRIX_SHARED_SECRET") is None: + flash("Registration Configuration Is Invalid. Contact Administrator!") + return render_template('register.html', form=form) + else: + successful = register_new_user( + form.username.data, + form.password.data, + app.config.get("MATRIX_HOMESERVER"), + app.config.get("MATRIX_SHARED_SECRET") + ) + if successful: + add_registered_user(form.registration_code.data, form.username.data) + return redirect(get_successful_registration_redirect()) + else: + flash("Registration Failure. Contact Administrator!") + return render_template('register.html', form=form) flash_form_errors(form) @@ -131,11 +185,11 @@ def admin_add_registration_code(): expiration_time = form.expiration_time.data max_usages = form.max_usages.data add_registration_code(expiration_time, max_usages) - redirect('/admin') + redirect(url_for('admin_index', _external=True)) flash_form_errors(form) - return redirect('/admin') + return redirect(url_for('admin_index', _external=True)) @app.route('/admin/expire_registration_code', methods=['POST']) @@ -147,17 +201,16 @@ def admin_expire_registration_code(): expire_registration_code(form.registration_code.data) elif form.delete.data: delete_registration_code(form.registration_code.data) - redirect('/admin') + redirect(url_for('admin_index', _external=True)) flash_form_errors(form) - return redirect('/admin') + return redirect(url_for('admin_index', _external=True)) @app.route('/admin/login', methods=('GET', 'POST')) def admin_login(): form = LoginForm() - tmp = current_user if form.validate_on_submit(): user = load_user(form.username.data) if user is not None: @@ -172,7 +225,7 @@ def admin_login(): if next_loc is not None: return redirect(next_loc) else: - return redirect('/admin') + return redirect(url_for('admin_index', _external=True)) flash_form_errors(form) @@ -184,7 +237,7 @@ def admin_login(): def admin_logout(): logout_user() flask.flash('Logged out successfully.') - return redirect('/') + return redirect(url_for('index', _external=True)) @app.teardown_appcontext diff --git a/db.py b/db.py index d604932..d2d7e0e 100644 --- a/db.py +++ b/db.py @@ -1,3 +1,4 @@ +import logging import os import sqlite3 import uuid @@ -8,7 +9,7 @@ from flask import g from models import RegistrationCode, RegisteredUser -DATABASE = os.getenv("DATA_DIRECTORY", ".") + "/data.db" +DATABASE = os.getenv("APP_DATA_DIRECTORY", ".") + "/data.db" REGISTRATION_CODE_INSERT_SQL = """INSERT INTO @@ -24,10 +25,13 @@ registered_users(registrationCode, username, registeredTime) VALUES(?, ?, ?) """ +log = logging.getLogger(__name__) + def get_db(): db = getattr(g, '_database', None) if db is None: + log.info("Using database at: %s" % DATABASE) db = g._database = sqlite3.connect(DATABASE) return db diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4a478af --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: "2" + +services: + portal: + build: . + environment: + - ADMIN_TOKEN + - SECRET_KEY + - WTF_CSRF_SECRET_KEY + - MATRIX_HOMESERVER + - MATRIX_SHARED_SECRET + - DATA_DIRECTORY=/srv/portal/data + volumes: + - ${DATA_DIR}:/srv/portal/data:Z diff --git a/example.env b/example.env new file mode 100644 index 0000000..1a963d5 --- /dev/null +++ b/example.env @@ -0,0 +1,7 @@ +ADMIN_TOKEN=changeme +SECRET_KEY=changeme_too +WTF_CSRF_SECRET_KEY=csrf_changeme_also +MATRIX_HOMESERVER=your_matrix_homeserver +MATRIX_SHARED_SECRET=the_shared_registration_secret +DATA_DIRECTORY=/srv/portal/data +REGISTRATION_SUCCESS_REDIRECT=/success \ No newline at end of file diff --git a/register_new_matrix_user.py b/register_new_matrix_user.py index 367bb10..02fee2c 100644 --- a/register_new_matrix_user.py +++ b/register_new_matrix_user.py @@ -24,11 +24,14 @@ import hashlib import hmac -import sys +import logging import requests as _requests +log = logging.getLogger(__name__) + + def request_registration( user, password, @@ -36,9 +39,7 @@ def request_registration( shared_secret, admin=False, user_type=None, - requests=_requests, - _print=print, - exit=sys.exit, + requests=_requests ): url = "%s/_matrix/client/r0/admin/register" % (server_location,) @@ -47,13 +48,13 @@ def request_registration( r = requests.get(url, verify=False) if r.status_code is not 200: - _print("ERROR! Received %d %s" % (r.status_code, r.reason)) + log.error("ERROR! Received %d %s" % (r.status_code, r.reason)) if 400 <= r.status_code < 500: try: - _print(r.json()["error"]) - except Exception: - pass - return exit(1) + log.error(r.json()["error"]) + except Exception as e: + log.error(e) + return False nonce = r.json()["nonce"] @@ -81,28 +82,19 @@ def request_registration( "user_type": user_type, } - _print("Sending registration request...") + log.debug("Sending registration request...") r = requests.post(url, json=data, verify=False) if r.status_code is not 200: - _print("ERROR! Received %d %s" % (r.status_code, r.reason)) + log.error("ERROR! Received %d %s" % (r.status_code, r.reason)) if 400 <= r.status_code < 500: try: - _print(r.json()["error"]) - except Exception: - pass - return exit(1) - - _print("Success!") + log.error(r.json()["error"]) + except Exception as e: + log.error(e) + return False + return True def register_new_user(user, password, server_location, shared_secret): - if not user: - print("Invalid user name") - sys.exit(1) - - if not password: - print("Invalid user name") - sys.exit(1) - - request_registration(user, password, server_location, shared_secret, False, None) + return request_registration(user, password, server_location, shared_secret, False, None) diff --git a/templates/base.html b/templates/base.html index 52ba751..8179d36 100644 --- a/templates/base.html +++ b/templates/base.html @@ -6,8 +6,9 @@