Compare commits

...

10 Commits

13 changed files with 89 additions and 26 deletions

View File

@ -9,7 +9,7 @@ parser.add_argument("--token_file", default="/etc/door_tokens", help="path to th
parser.add_argument("--nfc_socket", default="/tmp/nfc.sock", help="socket for handling NFC reader commands")
parser.add_argument("--template_folder", default="templates", help="path to Flask templates folder")
parser.add_argument("--static_folder", default="static", help="path to Flask static folder")
parser.add_argument("--admin_file", help="Path to file for creating super admin users")
parser.add_argument("--admin_file", default="/etc/admins.conf", help="Path to file for creating super admin users")
parser.add_argument("--log_file", default="/var/log/webinterface.log", help="Path to flask log file")
parser.add_argument("--nfc_log", default="/var/log/nfc.log", help="Path to nfc log file")
parser.add_argument("--ldap_url", default="ldaps://ldap.imaginaerraum.de",

View File

@ -2,6 +2,7 @@ import paho.mqtt.client as mqtt
import socket
from pathlib import Path
import logging
from datetime import datetime
class DoorHandle:
def __init__(self, token_file, mqtt_host, mqtt_port=1883, nfc_socket='/tmp/nfc.sock', logger=None):
@ -53,6 +54,7 @@ class DoorHandle:
self.encoder_position = int(msg.payload)
elif msg.topic == 'door/token/last_invalid':
timestamp, token = msg.payload.decode().split(";")
timestamp = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S')
self.last_invalid = {'timestamp': timestamp, 'token': token}
def get_tokens(self):
@ -92,15 +94,15 @@ class DoorHandle:
if self.nfc_sock is not None:
self.nfc_sock.send(b'rld\n')
def open_door(self):
def open_door(self, user=''):
if self.nfc_sock is not None:
self.nfc_sock.send(b'open\n')
self.nfc_sock.send(b'open ' + user.encode() + b'\n')
else:
raise Exception("No connection to NFC socket. Cannot close door!")
def close_door(self):
def close_door(self, user=''):
if self.nfc_sock is not None:
self.nfc_sock.send(b'close\n')
self.nfc_sock.send(b'close ' + user.encode() + b'\n')
else:
raise Exception("No connection to NFC socket. Cannot close door!")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -6,7 +6,7 @@
{% block content %}
<div class="row">
Zustand der Tür:
{% if door_state == 'closed' %}
{% if door_state == 'close' %}
<div style="color: red">
Abgeschlossen
</div>
@ -35,4 +35,4 @@
{% endif %}
{% endblock %}
{% endblock %}

View File

@ -5,13 +5,37 @@
{% endblock %}
{% block content %}
{% if not token.vars %}
<div class="d-grid gap-3">
<div class="p-2 bg-light border">
Letzter gelesener unregistrierter Token: {{ token['token'] }} <br>
Gelesen: {{ token['timestamp']}}
<h2>Anleitung zur Schlüsselregistrierung</h2>
<ol>
<li>RFID-Token bereithalten (liegen im Regal im hinteren Raum)<br>
<img src="static/token.png" title="Token" alt="Token"><br>
Wichtig: Der RFID-Token darf nicht bereits registriert sein. Falls ein Token neu beschrieben werden soll,
muss zunächst die bestehende Registrierung gelöscht werden.</li>
<li>RFID-Token einmal von außen an das Lesegerät an der Tür halten. Danach diese Seite neu laden.</li>
<li>Im Feld weiter unten erscheint die ID des Token. Außerdem wird die Uhrzeit des Lesevorgangs angezeigt.
Diese bitte überprüfen, damit nicht versehentlich ein falscher Token registriert wird.</li>
<li>Wenn alles passt, kann der Token registiert werden. Hierzu im Registierungsfeld Namen,
Organisationszugehörigkeit (z.B. imaginärraum o. TransitionHaus) und eine E-Mail-Adresse zur
Kontaktaufnahme angeben. Optional kann die Gültigkeitsdauer des Tokens begrenzt werden.
Zusätzlich muss angegeben werden, dass der/die NutzerIn über die Nutzungsbedingungen aufgeklärt wurde
und diesen zustimmt.
</li>
<li>Achtung: Der Token funktioniert nicht sofort, sondern muss erst explizit aktiviert werden!
Dazu in der <a href="{{ url_for('list_tokens') }}">Token-Übersicht</a> auf das Bearbeiten-Symbol
(<img src="static/edit.png" title="Editieren" alt="Edit">) klicken und den Haken bei "Aktiv?" setzen.
</li>
<li>Jetzt kann der Token verwendet werden.</li>
</ol>
</div>
{% if 'token' in token and 'timestamp' in token %}
<div class="alert alert-success" role="alert">
<h4 class="alert-heading">Unregistrierter Token gelesen:</h4>
<p>Token ID: {{ token['token'] }}</p>
<hr>
<p class="mb-0">Zeitstempel (UTC): {{ token['timestamp']}} (vor {{ token['timedelta_minutes'] }} Minuten)</p>
</div>
<div class="p-2 bg-light border">
<h3>RaumnutzerIn registrieren:</h3>
@ -54,7 +78,11 @@
</div>
</div>
{% else %}
Keine unregistrierten Tokens in MQTT Nachrichten. Bitte Token scannen und die Seite neu laden.
<div class="alert alert-warning" role="alert">
<p>Keine unregistrierten Tokens in MQTT Nachrichten. </p>
<p>Bitte Token scannen und die Seite neu laden.</p>
</div>
{% endif %}
<script>

View File

@ -6,16 +6,26 @@
{% block content %}
<table class="table">
<thead>
<td>Date</td>
<td>Timestamp</td>
<td>Level</td>
<td>Message</td>
</thead>
<tbody>
{% for line in log %}
<tr>
{% if line[2] == 'INFO' %}
<tr style="background-color: lightgreen">
{% elif line[2] == 'WARNING'%}
<tr style="background-color: orange">
{% elif line[2] == 'ERROR' %}
<tr style="background-color: red">
{% elif line[2] == 'DEBUG'%}
<tr style="background-color: lightblue">
{% else %}
<tr>
{% endif %}
<td>{{ line[0] }}</td>
<td>{{ line[1] }}</td>
<td>{{ line[2] }}</td>
<td>{{ line[3] }}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -17,7 +17,13 @@
</thead>
<tbody>
{% for t, data in assigned_tokens.items() %}
<tr>
<tr
{% if loop.index % 2 %}
style="background-color: lightgrey"
{% else %}
style="background-color: mintcream"
{% endif %}
>
<td>{{ t }}</td>
{% for field in ['name', 'organization', 'email', 'valid_thru'] %}
<td>{{ data[field] if data[field] }}</td>
@ -29,8 +35,13 @@
</td>
</tr>
{% endfor %}
{% if inactive_tokens | length > 0 %}
<tr>
<td>Inaktive Tokens:</td>
</tr>
{% endif %}
{% for t, data in inactive_tokens.items() %}
<tr style="background-color: lightgrey">
<tr style="background-color: sandybrown">
<td>{{ t }}</td>
{% for field in ['name', 'organization', 'email', 'valid_thru'] %}
<td>{{ data[field] if data[field] }}</td>

View File

@ -23,7 +23,7 @@ from pathlib import Path
import logging
import tempfile
from datetime import date
from datetime import date, datetime, timedelta
from .door_handle import DoorHandle
@ -207,18 +207,19 @@ def create_application(config):
new_user_data['password'] = hash_password(password)
new_user_data['roles'] = []
lock_permission = con.search('ou=Users,dc=imaginaerraum,dc=de',
f'(&(uid={username})(memberof=cn=Members,ou=Groups,dc=imaginaerraum,dc=de))',
f'(&(uid={username})(memberof=cn=Keyholders,ou=Groups,dc=imaginaerraum,dc=de))',
attributes=ldap3.ALL_ATTRIBUTES)
authorized = True
if lock_permission:
new_user_data['email'] = con.entries[0].mail.value
else:
new_user_data['email'] = None
authorized = False
token_granting_permission = con.search('ou=Users,dc=imaginaerraum,dc=de',
f'(&(uid={username})(memberof=cn=Vorstand,ou=Groups,dc=imaginaerraum,dc=de))')
if token_granting_permission:
new_user_data['roles'].append('admin')
return True, new_user_data
return authorized, new_user_data
class ExtendedLoginForm(LoginForm):
email = StringField('Benutzername oder E-Mail', [Required()])
@ -484,7 +485,7 @@ def create_application(config):
with open(config.nfc_log) as f:
log += f.readlines()
log.reverse()
log = map(lambda l: l.split(' ', 2), log)
log = [l.split(' - ') for l in log]
return render_template('token_log.html', log=log)
except Exception as e:
flash(f"NFC logfile {Path(config.nfc_log).absolute()} konnte nicht gelesen werden. Exception: {e}")
@ -502,12 +503,22 @@ def create_application(config):
If the route is called via POST the provided form data is checked and if the check succeeds the /store-token route
will be called which adds the new token to the database.
"""
token = door.get_most_recent_token()
recent_token = {}
if {'token', 'timestamp'}.issubset(set(token.keys())):
dt = datetime.utcnow() - token['timestamp']
if dt < timedelta(minutes=10):
recent_token = token
recent_token['timedelta_minutes'] = int(dt.total_seconds() / 60.0)
form = TokenForm()
if request.method == 'GET':
# set default valid thru date to today to make sure form validity check passes
# (will not be used if limited validity is disabled)
form.valid_thru.data = date.today()
return render_template('register.html', token=door.get_most_recent_token(), form=form)
return render_template('register.html', token=recent_token, form=form)
elif request.method == 'POST' and form.validate():
# store data in session cookie
session['token'] = door.get_most_recent_token()['token']
@ -521,7 +532,7 @@ def create_application(config):
session['inactive'] = not form.active.data
return redirect('/store-token')
else:
return render_template('register.html', token=door.get_most_recent_token(), form=form)
return render_template('register.html', token=recent_token, form=form)
@app.route('/edit-token/<token>', methods=['GET', 'POST'])
@roles_required('admin')
@ -677,8 +688,9 @@ def create_application(config):
@app.route('/open')
@auth_required()
def open_door():
try:
door.open_door()
door.open_door(user=current_user.username)
logger.info(f"Door opened by admin user {current_user.username}")
except Exception as e:
flash(f'Could not open door. Exception: {e}')
@ -689,7 +701,7 @@ def create_application(config):
@auth_required()
def close_door():
try:
door.close_door()
door.close_door(user=current_user.username)
logger.info(f"Door closed by admin user {current_user.username}")
except Exception as e:
flash(f'Could not close door. Exception: {e}')