commit d70c8b30e68210b299f66c233dd0c1d00ea503a2 Author: Drew Short Date: Tue Jan 29 19:44:47 2019 -0600 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..f05f9fd --- /dev/null +++ b/app.py @@ -0,0 +1,111 @@ +import os +import uuid +from urllib.parse import urlparse, urljoin + +import flask +from flask import Flask, redirect, render_template, request +from flask_login import LoginManager, login_required, login_user, logout_user, UserMixin, \ + current_user +from flask_wtf import CSRFProtect + +from forms import RegistrationForm, LoginForm + +app = Flask(__name__) + +app.config.update(dict( + 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") +)) + +print("Admin Token: %s" % app.config.get("ADMIN_TOKEN")) + +csrf = CSRFProtect(app) + +login_manager = LoginManager() +login_manager.init_app(app) +login_manager.login_view = "admin_login" + + +class User(UserMixin): + username: str + token: str + authenticated: bool = False + + def __init__(self, username: str, token: str): + self.username = username + self.token = token + + def is_authenticated(self): + return self.authenticated + + def get_id(self): + return self.username + + +def is_safe_url(target): + ref_url = urlparse(request.host_url) + test_url = urlparse(urljoin(request.host_url, target)) + return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc + + +@login_manager.user_loader +def load_user(user_id): + if user_id == "admin": + return User("admin", app.config.get("ADMIN_TOKEN")) + else: + return None + + +@app.route('/') +def index(): + return 'Hello World!' + + +@app.route('/register', methods=('GET', 'POST')) +def registration(): + form = RegistrationForm() + if form.validate_on_submit(): + return redirect('/success') + return render_template('register.html', form=form) + + +@app.route('/admin') +@login_required +def admin_index(): + return render_template('admin.html') + + +@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: + if form.token.data == user.token: + user.authenticated = True + login_user(user) + flask.flash('Logged in successfully.') + next_loc = flask.request.args.get('next') + if not is_safe_url(next_loc): + return flask.abort(400) + else: + if next_loc is not None: + return redirect(next_loc) + else: + return redirect('/admin') + + return render_template('login.html', form=form) + + +@app.route("/admin/logout") +@login_required +def admin_logout(): + logout_user() + flask.flash('Logged out successfully.') + return redirect('/') + + +if __name__ == '__main__': + app.run() diff --git a/db.py b/db.py new file mode 100644 index 0000000..5e7d323 --- /dev/null +++ b/db.py @@ -0,0 +1,20 @@ +import os +import sqlite3 + +from flask import g + +DATABASE = os.getenv("DATA_DIRECTORY", ".") + "/data.db" + + +def get_db(): + db = getattr(g, '_database', None) + if db is None: + db = g._database = sqlite3.connect(DATABASE) + return db + + +@app.teardown_appcontext +def close_connection(exception): + db = getattr(g, '_database', None) + if db is not None: + db.close() diff --git a/forms.py b/forms.py new file mode 100644 index 0000000..8d3687e --- /dev/null +++ b/forms.py @@ -0,0 +1,14 @@ +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField +from wtforms.validators import DataRequired + + +class RegistrationForm(FlaskForm): + username = StringField('Username', validators=[DataRequired()]) + password = PasswordField('Password', validators=[DataRequired()]) + registration_code = StringField('Registration Code', validators=[DataRequired()]) + + +class LoginForm(FlaskForm): + username = StringField('Username', validators=[DataRequired()]) + token = PasswordField('Token', validators=[DataRequired()]) diff --git a/register_new_matrix_user.py b/register_new_matrix_user.py new file mode 100644 index 0000000..367bb10 --- /dev/null +++ b/register_new_matrix_user.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# Copyright 2015, 2016 OpenMarket Ltd +# Copyright 2018 New Vector +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# This script has been simplified and adapted from +# https://raw.githubusercontent.com/matrix-org/synapse/master/synapse/_scripts/register_new_matrix_user.py +# +# The purpose is to facilitate registration using a shared_secret over the open +# registration that is supported in the matrix spec +# + +import hashlib +import hmac +import sys + +import requests as _requests + + +def request_registration( + user, + password, + server_location, + shared_secret, + admin=False, + user_type=None, + requests=_requests, + _print=print, + exit=sys.exit, +): + + url = "%s/_matrix/client/r0/admin/register" % (server_location,) + + # Get the nonce + r = requests.get(url, verify=False) + + if r.status_code is not 200: + _print("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) + + nonce = r.json()["nonce"] + + mac = hmac.new(key=shared_secret.encode('utf8'), digestmod=hashlib.sha1) + + mac.update(nonce.encode('utf8')) + mac.update(b"\x00") + mac.update(user.encode('utf8')) + mac.update(b"\x00") + mac.update(password.encode('utf8')) + mac.update(b"\x00") + mac.update(b"admin" if admin else b"notadmin") + if user_type: + mac.update(b"\x00") + mac.update(user_type.encode('utf8')) + + mac = mac.hexdigest() + + data = { + "nonce": nonce, + "username": user, + "password": password, + "mac": mac, + "admin": admin, + "user_type": user_type, + } + + _print("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)) + if 400 <= r.status_code < 500: + try: + _print(r.json()["error"]) + except Exception: + pass + return exit(1) + + _print("Success!") + + +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) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d5443ed --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +flask==1.0.2 +flask-wtf==0.14 +flask-login==0.4.1 +requests==2.21.0 \ No newline at end of file diff --git a/templates/admin.html b/templates/admin.html new file mode 100644 index 0000000..8f0f3f8 --- /dev/null +++ b/templates/admin.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Admin{% endblock %}

+{% endblock %} + +{% block content %} +

ADMIN

+{% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..8afc700 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,24 @@ + +{% block title %}{% endblock %} - Matrix + +{##} +
+
+ {% block header %}{% endblock %} +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block content %}{% endblock %} +
\ No newline at end of file diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..20243ff --- /dev/null +++ b/templates/login.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Login{% endblock %}

+{% endblock %} + +{% block content %} +
+ {{ form.csrf_token }} + {{ form.username.label }} {{ form.username(size=20) }} + {{ form.token.label }} {{ form.token() }} + +
+{% endblock %} \ No newline at end of file diff --git a/templates/register.html b/templates/register.html new file mode 100644 index 0000000..01050d0 --- /dev/null +++ b/templates/register.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Register{% endblock %}

+{% endblock %} + +{% block content %} +
+ {{ form.csrf_token }} + {{ form.username.label }} {{ form.username(size=20) }} + {{ form.password.label }} {{ form.password() }} + {{ form.registration_code.label }} {{ form.registration_code() }} + +
+{% endblock %} \ No newline at end of file diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..a49cfbf --- /dev/null +++ b/todo.md @@ -0,0 +1,12 @@ +# TODO +* Admin endpoint + * List existing registration codes + * Create new registration codes + * Deactivate registration codes + * View registrations +* Registration Form + * Fields: + * Desired username + * Password + * Registration Code + \ No newline at end of file