Compare commits

..

No commits in common. "daed27372bdbeb7d0fe5995b9c411b09a1e7b831" and "4d405c66d68752c90fafe43f0aa34a1c6aa64673" have entirely different histories.

4 changed files with 19 additions and 48 deletions

42
app.py
View File

@ -5,46 +5,27 @@ from wtforms.fields.html5 import DateField, EmailField
from wtforms.fields import StringField, BooleanField from wtforms.fields import StringField, BooleanField
from wtforms.validators import DataRequired, ValidationError from wtforms.validators import DataRequired, ValidationError
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_security import Security, SQLAlchemyUserDatastore, auth_required, hash_password, uia_email_mapper from flask_security import Security, SQLAlchemyUserDatastore, auth_required, hash_password
from flask_security.models import fsqla_v2 as fsqla from flask_security.models import fsqla_v2 as fsqla
from flask_security.forms import LoginForm, Required, PasswordField from flask_security.forms import LoginForm, Required, PasswordField
import bleach
from datetime import date from datetime import date
from door_handle import DoorHandle from door_handle import DoorHandle
import argparse MQTT_BROKER = '10.10.21.2'
parser = argparse.ArgumentParser() door = DoorHandle(MQTT_BROKER)
parser.add_argument("--token_file", default="/etc/door_tokens")
parser.add_argument("--template_folder", default="/usr/share/door_admin/templates")
parser.add_argument("--static_folder", default="/usr/share/door_admin/static")
parser.add_argument("--mqtt_host", default="10.10.21.2")
config = parser.parse_args()
# create door objects which provides access to the token file and current door state via MQTT app = Flask(__name__, template_folder='/usr/share/door_admin/templates', static_url_path='/usr/share/door_admin/static')
door = DoorHandle(config.mqtt_host, token_file=config.token_file)
app = Flask(__name__, template_folder=config.template_folder, static_folder=config.static_folder)
# Generate a nice key using secrets.token_urlsafe() # Generate a nice key using secrets.token_urlsafe()
app.config['SECRET_KEY'] = os.environ.get("SECRET_KEY", 'Q7PJu2fg2jabYwP-Psop6c6f2G4') app.config['SECRET_KEY'] = os.environ.get("SECRET_KEY", 'Q7PJu2fg2jabYwP-Psop6c6f2G4')
# Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt # Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt
# Generate a good salt using: secrets.SystemRandom().getrandbits(128) # Generate a good salt using: secrets.SystemRandom().getrandbits(128)
app.config['SECURITY_PASSWORD_SALT'] = os.environ.get("SECURITY_PASSWORD_SALT", app.config['SECURITY_PASSWORD_SALT'] = os.environ.get("SECURITY_PASSWORD_SALT", '10036796768252925167749545152988277953')
'10036796768252925167749545152988277953') app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ('username', 'email')
def uia_username_mapper(identity):
# we allow pretty much anything - but we bleach it.
return bleach.clean(identity, strip=True)
app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = [
{"email": {"mapper": uia_email_mapper, "case_insensitive": True}},
{"username": {"mapper": uia_username_mapper}}
]
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///admin.db' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///admin.db'
# As of Flask-SQLAlchemy 2.4.0 it is easy to pass in options directly to the # As of Flask-SQLAlchemy 2.4.0 it is easy to pass in options directly to the
# underlying engine. This option makes sure that DB connections from the # underlying engine. This option makes sure that DB connections from the
@ -60,15 +41,12 @@ db = SQLAlchemy(app)
# Define models # Define models
fsqla.FsModels.set_db_info(db) fsqla.FsModels.set_db_info(db)
class Role(db.Model, fsqla.FsRoleMixin): class Role(db.Model, fsqla.FsRoleMixin):
pass pass
class User(db.Model, fsqla.FsUserMixin): class User(db.Model, fsqla.FsUserMixin):
username = db.Column(db.String(255)) username = db.Column(db.String(255))
class ExtendedLoginForm(LoginForm): class ExtendedLoginForm(LoginForm):
email = StringField('Benutzername oder E-Mail', [Required()]) email = StringField('Benutzername oder E-Mail', [Required()])
password = PasswordField('Passwort', [Required()]) password = PasswordField('Passwort', [Required()])
@ -80,7 +58,7 @@ security = Security(app, user_datastore, login_form=ExtendedLoginForm)
def validate_valid_thru_date(form, field): def validate_valid_thru_date(form, field):
if form.limit_validity.data: # only check date format if limited validity of token is set if form.limit_validity.data: # only check date format if limited validity of token is set
try: try:
if not field.data >= date.today(): if not field.data >= date.today():
raise ValueError raise ValueError
@ -89,7 +67,6 @@ def validate_valid_thru_date(form, field):
raise ValidationError raise ValidationError
return True return True
class TokenForm(FlaskForm): class TokenForm(FlaskForm):
name = StringField('Name', validators=[DataRequired()]) name = StringField('Name', validators=[DataRequired()])
email = EmailField('E-Mail', validators=[DataRequired()]) email = EmailField('E-Mail', validators=[DataRequired()])
@ -190,7 +167,7 @@ def edit_token(token):
except Exception: except Exception:
form.valid_thru.data = date.today() form.valid_thru.data = date.today()
return render_template('edit.html', token=token, form=form) return render_template('edit.html',token=token, form=form)
else: else:
# flash an error message if the route is accessed with an invalid token # flash an error message if the route is accessed with an invalid token
flash(f'Ausgewaehlter Token {token} in Tokenfile nicht gefunden.') flash(f'Ausgewaehlter Token {token} in Tokenfile nicht gefunden.')
@ -212,6 +189,7 @@ def edit_token(token):
return render_template('edit.html', token=token, form=form) return render_template('edit.html', token=token, form=form)
@app.route('/store-token') @app.route('/store-token')
@auth_required() @auth_required()
def store_token(): def store_token():
@ -243,7 +221,7 @@ def delete_token():
""" """
token = request.form.get('token') token = request.form.get('token')
tokens = door.get_tokens() tokens = door.get_tokens()
if token in tokens: # check if token exists if token in tokens: # check if token exists
tokens.pop(token) tokens.pop(token)
door.store_tokens(tokens) door.store_tokens(tokens)
return "success" return "success"

View File

@ -1,17 +1,13 @@
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
from pathlib import Path
class DoorHandle: class DoorHandle:
def __init__(self, host, port=1883, token_file='/etc/door_tokens'): def __init__(self, host, port=1883, token_file='/etc/door_tokens'):
self.state = None self.state = None
self.encoder_position = None self.encoder_position = None
self.last_invalid = {}
if not Path(token_file).exists():
raise FileNotFoundError(f"File with door tokens could not be found at {Path(token_file).absolute()}")
self.token_file = token_file self.token_file = token_file
self.last_invalid = {}
self.mqtt_client = mqtt.Client() self.mqtt_client = mqtt.Client()
self.mqtt_client.on_connect = self.on_connect self.mqtt_client.on_connect = self.on_connect
@ -36,9 +32,9 @@ class DoorHandle:
self.state = msg.payload.decode() self.state = msg.payload.decode()
elif msg.topic == 'door/position/value': elif msg.topic == 'door/position/value':
self.encoder_position = int(msg.payload) self.encoder_position = int(msg.payload)
elif msg.topic == 'door/token/last_invalid': elif msg.topic == 'token/last_invalid':
timestamp, token = msg.payload.decode().split(";") timestamp, token = msg.payload.split(';')
self.last_invalid = {'timestamp': timestamp, 'token': token} self.last_invalid = {'token': token, 'timestamp': timestamp}
def get_tokens(self): def get_tokens(self):
tokens = {} tokens = {}
@ -73,6 +69,6 @@ class DoorHandle:
with open(self.token_file, 'w') as f: with open(self.token_file, 'w') as f:
f.write(output) f.write(output)
def get_most_recent_token(self): def get_most_recent_token(self):
# read last invalid token from logfile
return self.last_invalid return self.last_invalid

View File

@ -1,5 +1,2 @@
flask~=1.1.2 flask
Flask-Security-Too~=4.0.0 Flask-Security-Too
WTForms~=2.3.3
paho-mqtt~=1.5.1
bleach~=3.3.0

View File

@ -18,7 +18,7 @@
{% endwith %} {% endwith %}
{% if token is not none %} {% if token is not none %}
Letzter unregistrierter Token: {{ token['token'] }} <br> Letzter unregistrierter Token: {{ token['token'] }} <br>
Gelesen: {{ token['timestamp']}} Timestamp {{ token['timestamp'] }}
<div> <div>
<p> <p>