changed priority for authorization (Flask first, LDAP second) and cache LDAP credentials in memory on successful authentication
This commit is contained in:
parent
2aa958aaa0
commit
eb4c027f46
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user