changed priority for authorization (Flask first, LDAP second) and cache LDAP credentials in memory on successful authentication

This commit is contained in:
Simon Pirkelmann 2021-03-22 23:42:29 +01:00
parent 2aa958aaa0
commit eb4c027f46
2 changed files with 43 additions and 21 deletions

View File

@ -8,11 +8,12 @@ from flask_sqlalchemy import SQLAlchemy
from flask_security import Security, SQLAlchemyUserDatastore, auth_required, hash_password, uia_email_mapper
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.utils import find_user, verify_password
from flask_mail import Mail
from email_validator import validate_email
import bleach
import ldap3
from ldap3.core.exceptions import LDAPBindError, LDAPSocketOpenError
from datetime import date
from .door_handle import DoorHandle
@ -100,40 +101,61 @@ def create_application(config):
# LDAP
ldap_server = ldap3.Server(config.ldap_url)
local_ldap_cache = {} # dict for caching LDAP authorization locally (stores username + hashed password)
def validate_ldap(user, password):
# try to connect to the LDAP server
# if the connection completes successfully the given user and password is authorized
"""Validate the user and password through an LDAP server.
If the connection completes successfully the given user and password is authorized and the password is stored
locally for future authorization without internet connectivity.
If the server is not reachable we check the password against a locally stored password (if the user previously
authorized through LDAP).
Parameters
----------
user : username for the LDAP server
password : password for the LDAP server
Returns
-------
bool : result of the authorization process (True = success, False = failure)
"""
try:
con = ldap3.Connection(ldap_server, user="uid=%s,ou=Users,dc=imaginaerraum,dc=de" % (user.username,),
password=password, auto_bind=True)
except Exception:
except ldap3.core.exceptions.LDAPBindError:
# server reachable but user unauthorized -> fail
return False
return con is not None
except LDAPSocketOpenError:
# server not reachable -> try cached authorization data
return user.username in local_ldap_cache and verify_password(password, local_ldap_cache[user.username])
except Exception:
# for other Exceptions we just fail
return False
# TODO check if user has permission to edit tokens
# if LDAP authorization succeeds we cache the password locally (in memory) to allow LDAP authentication even if
# the server is not reachable
local_ldap_cache[user.username] = hash_password(password)
return True
class ExtendedLoginForm(LoginForm):
email = StringField('Benutzername oder E-Mail', [Required()])
password = PasswordField('Passwort', [Required()])
def validate(self):
# authorization in LDAP uses username -> get username associated with email from the database
user = find_user(self.email.data)
# try authorizing using LDAP
response_ldap = validate_ldap(user, self.password.data)
# try authorizing locally using Flask security user datastore
authorized = super(ExtendedLoginForm, self).validate()
if response_ldap:
# if LDAP authorization succeeds we update the currently stored password in the Flask user datastore
# with the one used for LDAP authorization. This way we can authorize with the LDAP password later
# even if the server is not reachable
user.password = hash_password(self.password.data)
# try authorizing using Flask security
response_orig = super(ExtendedLoginForm, self).validate()
if not authorized:
# try authorizing using LDAP
# authorization in LDAP uses username -> get username associated with email from the database
user = find_user(self.email.data)
authorized = validate_ldap(user, self.password.data)
# if any of the authorization methods is successful we authorize the user
return response_ldap or response_orig
app.config['SECURITY_MSG_USERID_NOT_PROVIDED'] = ('User ID not provided', 'error')
return authorized
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)

View File

@ -1,6 +1,6 @@
[metadata]
name = imaginaerraum_door_admin
version = 0.0.1
version = 0.0.7
author = Telos4
author_email = simon.pirkelmann@gmail.com
description = A simple web interface for our hackerspace's door token administration