Compare commits
16 Commits
door_admin
...
master
Author | SHA1 | Date |
---|---|---|
Simon Pirkelmann | 8e868f1674 | |
Simon Pirkelmann | 8a9a667429 | |
Simon Pirkelmann | 8bc0a642a3 | |
Simon Pirkelmann | 7cf4e3ce36 | |
Simon Pirkelmann | 79b9b69ef8 | |
Simon Pirkelmann | 4307d54505 | |
Simon Pirkelmann | 14c783b2c1 | |
Simon Pirkelmann | 2ccec6fe32 | |
Simon Pirkelmann | 5afcf1f10a | |
Simon Pirkelmann | 75ce8c46b8 | |
Simon Pirkelmann | 9a0ae93a7b | |
Simon Pirkelmann | 9d53f80cbf | |
Simon Pirkelmann | d406d254b4 | |
Simon Pirkelmann | cffdb1f797 | |
Simon Pirkelmann | 734bed2092 | |
Simon Pirkelmann | f48f78997c |
|
@ -1,5 +1,6 @@
|
|||
include README.md
|
||||
include imaginaerraum_door_admin/templates/*
|
||||
include imaginaerraum_door_admin/templates/security/*
|
||||
include imaginaerraum_door_admin/static/*
|
||||
include imaginaerraum_door_admin/static/css/*
|
||||
include imaginaerraum_door_admin/static/js/*
|
||||
|
|
24
Makefile
|
@ -1,24 +0,0 @@
|
|||
VERSION ?= $(shell git rev-parse --short HEAD)
|
||||
|
||||
release: door_admin_v$(VERSION).tar.gz
|
||||
|
||||
TEMPLATE_FILES = $(shell find -name '*.html')
|
||||
STATIC_FILES = $(shell find -name '*.png' -o -name '*.js')
|
||||
PYTHON_FILES = $(shell find -name '*.py')
|
||||
ALL_FILES = $(TEMPLATE_FILES) $(STATIC_FILES) $(PYTHON_FILES)
|
||||
|
||||
door_admin_v$(VERSION).tar.gz: Makefile $(ALL_FILES)
|
||||
tar czf $@ --transform='s,^,door_admin_$(VERSION)/,' $^
|
||||
|
||||
install: install_templates install_static install_python
|
||||
|
||||
install_static:
|
||||
install -D -m 0755 -t $(PREFIX)/usr/share/door_admin/static $(STATIC_FILES)
|
||||
|
||||
install_templates:
|
||||
install -D -m 0755 -t $(PREFIX)/usr/share/door_admin/templates $(TEMPLATE_FILES)
|
||||
|
||||
install_python:
|
||||
install -D -m 0755 -t $(PREFIX)/bin $(PYTHON_FILES)
|
||||
|
||||
.PHONY: release install install_static install_templates install_python
|
|
@ -9,8 +9,9 @@ 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("--log_file", default="/var/log/webinterface.log", help="Path to log file")
|
||||
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",
|
||||
help="URL for LDAP server for alternative user authorization")
|
||||
parser.add_argument("--mqtt_host", default="10.10.21.2", help="IP address of MQTT broker")
|
||||
|
|
|
@ -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!")
|
||||
|
||||
|
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 89 KiB |
|
@ -31,6 +31,9 @@
|
|||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="{{ url_for('register') }}">Token Registrierung</a>
|
||||
<a class="dropdown-item" href="{{ url_for('list_tokens') }}">Token Übersicht</a>
|
||||
{% if current_user.has_role('super_admin') %}
|
||||
<a class="dropdown-item" href="{{ url_for('token_log') }}">Token Log</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
{% extends 'base.html' %}
|
||||
{% block header %}
|
||||
{% block title %}<h1>Token Log</h1>{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<td>Timestamp</td>
|
||||
<td>Level</td>
|
||||
<td>Message</td>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for line in log %}
|
||||
{% 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[2] }}</td>
|
||||
<td>{{ line[3] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
|
@ -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>
|
||||
|
|
|
@ -10,6 +10,7 @@ from flask_security import Security, SQLAlchemyUserDatastore, auth_required, has
|
|||
from flask_security.models import fsqla_v2 as fsqla
|
||||
from flask_security.forms import LoginForm, Required, PasswordField
|
||||
from flask_security.utils import find_user
|
||||
from flask_security.views import change_password
|
||||
from flask_mail import Mail
|
||||
from email_validator import validate_email
|
||||
|
||||
|
@ -22,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
|
||||
|
||||
|
||||
|
@ -162,7 +163,7 @@ def create_application(config):
|
|||
pass
|
||||
|
||||
class User(db.Model, fsqla.FsUserMixin):
|
||||
username = db.Column(db.String(255))
|
||||
pass
|
||||
|
||||
# LDAP
|
||||
ldap_server = ldap3.Server(config.ldap_url)
|
||||
|
@ -206,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()])
|
||||
|
@ -228,45 +230,61 @@ def create_application(config):
|
|||
# search for user in the current database
|
||||
user = find_user(self.email.data)
|
||||
if user is not None:
|
||||
# if a user is found we use that username for LDAP authentication
|
||||
username = user.username
|
||||
# if a user is found we check if it is associated with LDAP or with the local database
|
||||
if user.has_role('local'):
|
||||
# try authorizing locally using Flask security user datastore
|
||||
authorized = super(ExtendedLoginForm, self).validate()
|
||||
|
||||
if authorized:
|
||||
logger.info(f"User with credentials '{self.email.data}' authorized through local database")
|
||||
else:
|
||||
# run LDAP authorization
|
||||
# if the authorization succeeds we also get the new_user_data dict which contains information about
|
||||
# the user's permissions etc.
|
||||
authorized, new_user_data = validate_ldap(user.username, self.password.data)
|
||||
|
||||
if authorized:
|
||||
logger.info(f"User with credentials '{self.email.data}' authorized through LDAP")
|
||||
# update permissions and password/email to stay up to date for login with no network connection
|
||||
user.email = new_user_data['email']
|
||||
user.password = new_user_data['password']
|
||||
for role in new_user_data['roles']:
|
||||
user_datastore.add_role_to_user(user, role)
|
||||
user_datastore.commit()
|
||||
self.user = user
|
||||
else:
|
||||
self.password.errors = ['Invalid password']
|
||||
else:
|
||||
# this means there is no user with that email in the database
|
||||
# we assume that the username was entered instead of an email and use that for authentication with LDAP
|
||||
username = self.email.data
|
||||
|
||||
# run LDAP authorization
|
||||
# if the authorization succeeds we also get the new_user_data dict which contains information about the
|
||||
# user's permissions etc.
|
||||
authorized, new_user_data = validate_ldap(username, self.password.data)
|
||||
|
||||
if authorized:
|
||||
if user is None:
|
||||
# if there was no user in the database before we create a new user
|
||||
user_datastore.create_user(username=new_user_data['username'], email=new_user_data['email'],
|
||||
password=new_user_data['password'], roles=new_user_data['roles'])
|
||||
logger.info(f"New admin user '{new_user_data['username']} <{new_user_data['email']}>' created after"
|
||||
" successful LDAP authorization")
|
||||
else: # for existing users we update permissions and password/email to stay up to date
|
||||
user.email = new_user_data['email']
|
||||
user.password = new_user_data['password']
|
||||
for role in new_user_data['roles']:
|
||||
user_datastore.add_role_to_user(user, role)
|
||||
user_datastore.commit()
|
||||
|
||||
self.user = find_user(self.email.data)
|
||||
logger.info(f"User with credentials '{self.email.data}' authorized through LDAP")
|
||||
|
||||
if not authorized:
|
||||
# try authorizing locally using Flask security user datastore
|
||||
authorized = super(ExtendedLoginForm, self).validate()
|
||||
# try LDAP authorization and create a new user if it succeeds
|
||||
authorized, new_user_data = validate_ldap(username, self.password.data)
|
||||
|
||||
if authorized:
|
||||
logger.info(f"User with credentials '{self.email.data}' authorized through local database")
|
||||
# if there was no user in the database before we create a new user
|
||||
self.user = user_datastore.create_user(username=new_user_data['username'], email=new_user_data['email'],
|
||||
password=new_user_data['password'], roles=new_user_data['roles'])
|
||||
user_datastore.commit()
|
||||
logger.info(f"New admin user '{new_user_data['username']} <{new_user_data['email']}>' created after"
|
||||
" successful LDAP authorization")
|
||||
|
||||
# if any of the authorization methods is successful we authorize the user
|
||||
return authorized
|
||||
|
||||
# we override the change_password view from flask security to only allow local users to change their passwords
|
||||
# LDAP users should use the LDAP self service for changing passwords
|
||||
# this route needs to be defined before the Flask Security setup
|
||||
@app.route('/change', methods=['GET', 'POST'])
|
||||
@auth_required()
|
||||
def change_pw():
|
||||
if current_user.has_role('local'):
|
||||
# local users can change their password
|
||||
return change_password()
|
||||
else:
|
||||
# LDAP users get redirected to the LDAP self service
|
||||
return redirect('https://ldap.imaginaerraum.de/')
|
||||
|
||||
# Setup Flask-Security
|
||||
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
||||
security = Security(app, user_datastore, login_form=ExtendedLoginForm)
|
||||
|
@ -291,6 +309,7 @@ def create_application(config):
|
|||
pw = secrets.token_urlsafe(16)
|
||||
new_user = user_datastore.create_user(username=form.name.data, email=form.email.data,
|
||||
password=hash_password(pw))
|
||||
user_datastore.add_role_to_user(new_user, 'local')
|
||||
logger.info(
|
||||
f"Super admin {current_user.username} created new user account for {new_user.username} <{new_user.email}>")
|
||||
flash(f"Ein Account für den Nutzer {new_user.username} wurde erstellt. Verwende das Passwort {pw} um den Nutzer einzuloggen.")
|
||||
|
@ -458,6 +477,20 @@ def create_application(config):
|
|||
inactive_tokens = {t: data for t, data in tokens.items() if data['inactive']}
|
||||
return render_template('tokens.html', assigned_tokens=assigned_tokens, inactive_tokens=inactive_tokens)
|
||||
|
||||
@app.route('/token-log')
|
||||
@roles_required('super_admin')
|
||||
def token_log():
|
||||
log = []
|
||||
try:
|
||||
with open(config.nfc_log) as f:
|
||||
log += f.readlines()
|
||||
log.reverse()
|
||||
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}")
|
||||
return redirect('/')
|
||||
|
||||
# routes for registering, editing and deleting tokens
|
||||
@app.route('/register-token', methods=['GET', 'POST'])
|
||||
@roles_required('admin')
|
||||
|
@ -470,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']
|
||||
|
@ -489,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')
|
||||
|
@ -645,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}')
|
||||
|
@ -657,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}')
|
||||
|
@ -672,29 +716,35 @@ def create_application(config):
|
|||
else:
|
||||
# store data for new admins in memory s.t. the file can be deleted afterwards
|
||||
with open(config.admin_file) as f:
|
||||
for i, d in enumerate(f.readlines()):
|
||||
try:
|
||||
user, email, pw = d.split()
|
||||
validate_email(email)
|
||||
new_admin_data.append({'username': user, 'email': email, 'password': pw})
|
||||
except Exception as e:
|
||||
print(
|
||||
f"Error while parsing line {i} in admin config file. Config file should contain lines of "
|
||||
f"'<username> <email> <password>\\n'\n Exception: {e}\nAdmin account could not be created.")
|
||||
for i, line in enumerate(f.readlines()):
|
||||
if not line.strip().startswith('#'):
|
||||
try:
|
||||
user, email, pw = line.split()
|
||||
validate_email(email)
|
||||
new_admin_data.append({'username': user, 'email': email, 'password': pw})
|
||||
except Exception as e:
|
||||
print(
|
||||
f"Error while parsing line {i} in admin config file. Config file should contain lines of "
|
||||
f"'<username> <email> <password>\\n'\n Exception: {e}\nAdmin account could not be created.")
|
||||
|
||||
# create admin users (only if they don't exists already)
|
||||
def create_super_admins(new_admin_data):
|
||||
db.create_all()
|
||||
super_admin_role = user_datastore.find_or_create_role('super_admin') # root admin = can create other admins
|
||||
admin_role = user_datastore.find_or_create_role('admin') # 'normal' admin
|
||||
local_role = user_datastore.find_or_create_role('local') # LDAP user or local user
|
||||
|
||||
for d in new_admin_data:
|
||||
if user_datastore.find_user(email=d['email'], username=d['username']) is None:
|
||||
logger.info(f"New super admin user created with username '{d['username']}' and email '{d['email']}'")
|
||||
roles = [super_admin_role, admin_role]
|
||||
if not d['password'] == 'LDAP':
|
||||
roles.append(local_role)
|
||||
logger.info(f"New super admin user created with username '{d['username']}' and email '{d['email']}', roles = {[r.name for r in roles]}")
|
||||
|
||||
# create new admin (only if admin does not already exist)
|
||||
new_admin = user_datastore.create_user(email=d['email'], username=d['username'],
|
||||
password=hash_password(d['password']),
|
||||
roles=[super_admin_role, admin_role])
|
||||
roles=roles)
|
||||
db.session.commit()
|
||||
|
||||
create_super_admins(new_admin_data)
|
||||
|
|