From f2ae768f76888abc9c1521ff264676fadfcffc46 Mon Sep 17 00:00:00 2001 From: redfast00 Date: Mon, 24 Sep 2018 20:57:36 +0200 Subject: [PATCH] Batman! --- .gitignore | 113 ++++++++++++++++++++++++++++++++++++++++++++++ app/__init__.py | 1 + app/app.py | 104 ++++++++++++++++++++++++++++++++++++++++++ app/models.py | 16 +++++++ config.py | 7 +++ requirements.txt | 6 +++ run_dev.py | 4 ++ setup_database.py | 8 ++++ 8 files changed, 259 insertions(+) create mode 100644 .gitignore create mode 100644 app/__init__.py create mode 100644 app/app.py create mode 100644 app/models.py create mode 100644 config.py create mode 100644 requirements.txt create mode 100644 run_dev.py create mode 100644 setup_database.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c441f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +venv/ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..a063340 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1 @@ +from app.app import app diff --git a/app/app.py b/app/app.py new file mode 100644 index 0000000..ca74cb4 --- /dev/null +++ b/app/app.py @@ -0,0 +1,104 @@ +import json +from functools import wraps +from flask import Flask, request, Response, abort +from flask_sqlalchemy import SQLAlchemy +import requests +import config + + +app = Flask(__name__) + +app.config['SQLALCHEMY_DATABASE_URI'] = config.DATABASE_URL +# Supress Flask warning +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +db = SQLAlchemy(app) + +response_setting = "ephemeral" + +from app import models + + +def check_regular(username): + '''Check if a user has the permissions of a regular user.''' + return models.User.query.filter_by(username=username, authorized=True).first() is not None + + +def check_admin(username): + '''Check if a user is an admin''' + return models.User.query.filter_by(username=username, authorized=True, admin=True).first() is not None + + +def requires_regular(f): + '''Decorator to require a regular user''' + @wraps(f) + def decorated(*args, **kwargs): + username = request.values.get('user_name') + if not username or not check_regular(username): + return abort(401) + return f(username, *args, **kwargs) + return decorated + + +def requires_admin(f): + '''Decorator to require an admin user''' + @wraps(f) + def decorated(*args, **kwargs): + username = request.values.get('user_name') + if not username or not check_admin(username): + return abort(401) + return f(username, *args, **kwargs) + return decorated + + +def requires_token(token_name): + '''Decorator to require a correct Mattermost token''' + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + expected_token = config.tokens[token_name] + token = request.values.get('token') + print(token, expected_token) + if expected_token != token: + return abort(401) + return f(*args, **kwargs) + return decorated_function + return decorator + + +def mattermost_response(message): + response_dict = {"response_type": response_setting, + "text": message} + return Response(json.dumps(response_dict), mimetype="application/json") + + +@app.route('/authorize', methods=['POST']) +@requires_token('authorize') +@requires_admin +def authorize(admin_username): + '''Slash-command to authorize a new user or modify an existing user''' + tokens = request.values.get('text').strip().split() + to_authorize = tokens[0] + as_admin = len(tokens) == 2 and tokens[1] == 'admin' + user = models.User.query.filter_by(username=to_authorize).first() + if not user: + user = models.User(to_authorize) + user.authorized = True + user.admin = as_admin + db.session.add(user) + db.session.commit() + return mattermost_response("Succesfully added '{}' as regular user".format(to_authorize)) + + +def slotmachien_request(username, command): + r = requests.post(config.slotmachien_url, data={ + 'username': username, 'token': config.slotmachien_token, 'text': command}) + return r.text + + +@app.route('/door', methods=['POST']) +@requires_token('door') +@requires_regular +def door(username): + tokens = request.values.get('text').strip().split() + command = tokens[0].lower() + return mattermost_response(slotmachien_request(username, command)) diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..3d8839f --- /dev/null +++ b/app/models.py @@ -0,0 +1,16 @@ +from .app import db + + +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String, unique=True, nullable=False) + authorized = db.Column(db.Boolean, default=True) + admin = db.Column(db.Boolean, default=False) + + def __repr__(self): + return '' % self.username + + def __init__(self, username, admin=False): + super() + self.username = username + self.admin = admin diff --git a/config.py b/config.py new file mode 100644 index 0000000..a5ed26f --- /dev/null +++ b/config.py @@ -0,0 +1,7 @@ +DATABASE_URL = 'sqlite:////tmp/mattermost.db' +tokens = { + 'authorize': '123', + 'door': '123' +} +slotmachien_url = 'https://kelder.zeus.ugent.be/slotmachien/slack/' +slotmachien_token = '123' diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..34ffbb7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +click==6.7 +Flask==1.0.2 +itsdangerous==0.24 +Jinja2==2.10 +MarkupSafe==1.0 +Werkzeug==0.14.1 diff --git a/run_dev.py b/run_dev.py new file mode 100644 index 0000000..fb68785 --- /dev/null +++ b/run_dev.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 + +from app import app +app.run(debug=True) diff --git a/setup_database.py b/setup_database.py new file mode 100644 index 0000000..25c16f8 --- /dev/null +++ b/setup_database.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +from app.app import db, models + +db.create_all() + +db.session.add(models.User('admin', admin=True)) +db.session.commit()