Skip to content

Instantly share code, notes, and snippets.

@lanbugs
Created September 9, 2023 22:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lanbugs/40f94d6a7b849dcd0803179e0a7bb137 to your computer and use it in GitHub Desktop.
Save lanbugs/40f94d6a7b849dcd0803179e0a7bb137 to your computer and use it in GitHub Desktop.
FLASK with LDAP3 authentication against active directory and authorization check for group membership
#!/usr/bin/env python3
# FLASK with LDAP3 authentication against active directory and authorization check for group membership
# Written by Maximilian Thoma 2023
# Visit: https://lanbugs.de for more ...
from functools import wraps
from flask import Flask, request, redirect, url_for, render_template, abort
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from ldap3 import Server, Connection, SUBTREE, SIMPLE
# LDAP Settings
LDAP_USER = "CN=LDAP Bind,CN=Users,DC=ad,DC=local"
LDAP_PASS = "SuperSecret12345567"
LDAP_SERVER = "ldap://ad01.ad.local"
AD_DOMAIN = "ADLOCAL"
SEARCH_BASE = "CN=Users,DC=ad,DC=local"
# Init Flask
app = Flask(__name__)
app.secret_key = "ThisSecretIsVeryWeakDoItBetter"
# Init LoginManager
login_manager = LoginManager()
login_manager.login_view = "login"
login_manager.init_app(app)
class User(UserMixin):
"""
The user model
"""
def __init__(self, username):
self.id = username
self.groups = []
def authenticate_ldap(username, password):
"""
Check authentication of user against AD with LDAP
:param username: Username
:param password: Password
:return: True is authentication is successful, else False
"""
server = Server(LDAP_SERVER, use_ssl=True)
try:
with Connection(server,
user=f'{AD_DOMAIN}\\{username}',
password=password,
authentication=SIMPLE,
check_names=True,
raise_exceptions=True) as conn:
if conn.bind():
print("Authentication successful")
return True
except Exception as e:
print(f"LDAP authentication failed: {e}")
return False
def get_user_groups(username):
"""
Connect to LDAP and query for all groups
:param username: Username
:return: List of group names
"""
server = Server(LDAP_SERVER, use_ssl=True)
with Connection(server,
user=LDAP_USER,
password=LDAP_PASS,
auto_bind=True) as conn:
search_filter = f'(sAMAccountName={username})'
conn.search(search_base=SEARCH_BASE,
search_filter=search_filter,
attributes=['memberOf'],
search_scope=SUBTREE)
if conn.entries:
user_entry = conn.entries[0]
group_dns = user_entry.memberOf
group_names = [group.split(',')[0].split('=')[1] for group in group_dns]
return group_names
return []
@login_manager.user_loader
def load_user(user_id):
"""
The user_loader of flask-login, this will load Usermodel and the groups from AD
:param user_id: Username
:return: user object
"""
user = User(user_id)
user.groups = get_user_groups(user_id)
return user
def group_required(groups):
"""
Decorator to check group membership
:param groups: list of groups which are allowed to see the site
"""
def decorator(func):
@wraps(func)
def decorated_function(*args, **kwargs):
for g in groups:
if current_user.is_authenticated and g in current_user.groups:
return func(*args, **kwargs)
abort(403)
return decorated_function
return decorator
@app.route('/login', methods=['GET', 'POST'])
def login():
"""
Login page
"""
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if authenticate_ldap(username, password):
user = User(username)
login_user(user)
return redirect(url_for('user_panel'))
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
"""
Logout page
"""
logout_user()
return redirect(url_for('login'))
@app.route('/admin')
@login_required
@group_required(["p_admin"])
def admin_panel():
"""
Protected admin panel, only users of group p_admin are allowed to see the page
"""
return 'Admin Panel'
@app.route('/user')
@login_required
@group_required(["p_user", "p_admin"])
def user_panel():
"""
Protected user panel, only users of group p_user and p_admin are allowed to see the page
"""
return 'User Panel'
if __name__ == "__main__":
app.run(debug=True)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
<div class="row">
<div class="col-md-6 offset-md-3">
<div class="card">
<div class="card-header">
<h3>Login</h3>
</div>
<div class="card-body">
<form method="POST">
<div class="form-group">
<label for="username">Username:</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
</title>
</head>
<body>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment