Drew Short
6 years ago
commit
d70c8b30e6
11 changed files with 332 additions and 0 deletions
-
1.gitignore
-
111app.py
-
20db.py
-
14forms.py
-
108register_new_matrix_user.py
-
4requirements.txt
-
9templates/admin.html
-
24templates/base.html
-
14templates/login.html
-
15templates/register.html
-
12todo.md
@ -0,0 +1 @@ |
|||
.idea |
@ -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() |
@ -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() |
@ -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()]) |
@ -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) |
@ -0,0 +1,4 @@ |
|||
flask==1.0.2 |
|||
flask-wtf==0.14 |
|||
flask-login==0.4.1 |
|||
requests==2.21.0 |
@ -0,0 +1,9 @@ |
|||
{% extends 'base.html' %} |
|||
|
|||
{% block header %} |
|||
<h1>{% block title %}Admin{% endblock %}</h1> |
|||
{% endblock %} |
|||
|
|||
{% block content %} |
|||
<p>ADMIN</p> |
|||
{% endblock %} |
@ -0,0 +1,24 @@ |
|||
<!doctype html> |
|||
<title>{% block title %}{% endblock %} - Matrix</title> |
|||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> |
|||
{#<nav>#} |
|||
{# <h1>Flaskr</h1>#} |
|||
{# <ul>#} |
|||
{# {% if g.user %}#} |
|||
{# <li><span>{{ g.user['username'] }}</span>#} |
|||
{# <li><a href="{{ url_for('auth.logout') }}">Log Out</a>#} |
|||
{# {% else %}#} |
|||
{# <li><a href="{{ url_for('auth.register') }}">Register</a>#} |
|||
{# <li><a href="{{ url_for('auth.login') }}">Log In</a>#} |
|||
{# {% endif %}#} |
|||
{# </ul>#} |
|||
{#</nav>#} |
|||
<section class="content"> |
|||
<header> |
|||
{% block header %}{% endblock %} |
|||
</header> |
|||
{% for message in get_flashed_messages() %} |
|||
<div class="flash">{{ message }}</div> |
|||
{% endfor %} |
|||
{% block content %}{% endblock %} |
|||
</section> |
@ -0,0 +1,14 @@ |
|||
{% extends 'base.html' %} |
|||
|
|||
{% block header %} |
|||
<h1>{% block title %}Login{% endblock %}</h1> |
|||
{% endblock %} |
|||
|
|||
{% block content %} |
|||
<form method="POST"> |
|||
{{ form.csrf_token }} |
|||
{{ form.username.label }} {{ form.username(size=20) }} |
|||
{{ form.token.label }} {{ form.token() }} |
|||
<input type="submit" value="Go"> |
|||
</form> |
|||
{% endblock %} |
@ -0,0 +1,15 @@ |
|||
{% extends 'base.html' %} |
|||
|
|||
{% block header %} |
|||
<h1>{% block title %}Register{% endblock %}</h1> |
|||
{% endblock %} |
|||
|
|||
{% block content %} |
|||
<form method="POST"> |
|||
{{ form.csrf_token }} |
|||
{{ form.username.label }} {{ form.username(size=20) }} |
|||
{{ form.password.label }} {{ form.password() }} |
|||
{{ form.registration_code.label }} {{ form.registration_code() }} |
|||
<input type="submit" value="Go"> |
|||
</form> |
|||
{% endblock %} |
@ -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 |
|||
|
Reference in new issue
xxxxxxxxxx