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 import Security, SQLAlchemyUserDatastore, auth_required, hash_password, uia_email_mapper
|
||||||
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
|
||||||
from flask_security.utils import find_user
|
from flask_security.utils import find_user, verify_password
|
||||||
from flask_mail import Mail
|
from flask_mail import Mail
|
||||||
from email_validator import validate_email
|
from email_validator import validate_email
|
||||||
import bleach
|
import bleach
|
||||||
import ldap3
|
import ldap3
|
||||||
|
from ldap3.core.exceptions import LDAPBindError, LDAPSocketOpenError
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from .door_handle import DoorHandle
|
from .door_handle import DoorHandle
|
||||||
|
@ -100,40 +101,61 @@ def create_application(config):
|
||||||
|
|
||||||
# LDAP
|
# LDAP
|
||||||
ldap_server = ldap3.Server(config.ldap_url)
|
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):
|
def validate_ldap(user, password):
|
||||||
# try to connect to the LDAP server
|
"""Validate the user and password through an LDAP server.
|
||||||
# if the connection completes successfully the given user and password is authorized
|
|
||||||
|
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:
|
try:
|
||||||
con = ldap3.Connection(ldap_server, user="uid=%s,ou=Users,dc=imaginaerraum,dc=de" % (user.username,),
|
con = ldap3.Connection(ldap_server, user="uid=%s,ou=Users,dc=imaginaerraum,dc=de" % (user.username,),
|
||||||
password=password, auto_bind=True)
|
password=password, auto_bind=True)
|
||||||
except Exception:
|
except ldap3.core.exceptions.LDAPBindError:
|
||||||
|
# server reachable but user unauthorized -> fail
|
||||||
return False
|
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):
|
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()])
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
# authorization in LDAP uses username -> get username associated with email from the database
|
# try authorizing locally using Flask security user datastore
|
||||||
user = find_user(self.email.data)
|
authorized = super(ExtendedLoginForm, self).validate()
|
||||||
# try authorizing using LDAP
|
|
||||||
response_ldap = validate_ldap(user, self.password.data)
|
|
||||||
|
|
||||||
if response_ldap:
|
if not authorized:
|
||||||
# if LDAP authorization succeeds we update the currently stored password in the Flask user datastore
|
# try authorizing using LDAP
|
||||||
# with the one used for LDAP authorization. This way we can authorize with the LDAP password later
|
# authorization in LDAP uses username -> get username associated with email from the database
|
||||||
# even if the server is not reachable
|
user = find_user(self.email.data)
|
||||||
user.password = hash_password(self.password.data)
|
authorized = validate_ldap(user, self.password.data)
|
||||||
|
|
||||||
# try authorizing using Flask security
|
|
||||||
response_orig = super(ExtendedLoginForm, self).validate()
|
|
||||||
|
|
||||||
# if any of the authorization methods is successful we authorize the user
|
# if any of the authorization methods is successful we authorize the user
|
||||||
return response_ldap or response_orig
|
return authorized
|
||||||
|
|
||||||
app.config['SECURITY_MSG_USERID_NOT_PROVIDED'] = ('User ID not provided', 'error')
|
|
||||||
|
|
||||||
# Setup Flask-Security
|
# Setup Flask-Security
|
||||||
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
name = imaginaerraum_door_admin
|
name = imaginaerraum_door_admin
|
||||||
version = 0.0.1
|
version = 0.0.7
|
||||||
author = Telos4
|
author = Telos4
|
||||||
author_email = simon.pirkelmann@gmail.com
|
author_email = simon.pirkelmann@gmail.com
|
||||||
description = A simple web interface for our hackerspace's door token administration
|
description = A simple web interface for our hackerspace's door token administration
|
||||||
|
|
Loading…
Reference in New Issue
Block a user