Skip to main content
  1. Posts/

FLASK with LDAP Authentication against Active Directory and group authorization for specific pages

·457 words·3 mins·
Netdevops Blog Python Flask Ldap Active_directory
Maximilian Thoma
Author
Maximilian Thoma
network engineer
Table of Contents

This is an example of how to implement authentication for a FLASK website using Active Directory with LDAP. Additionally, there is a decorator that allows restricting access to specific subpages to individual or multiple AD groups. The following PIP packages are required for the example: flask, flask-login, ldap3. The functions, etc., are documented in the code.

Code
#

#!/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)

Related

Merge subnet lists
·80 words·1 min
Netdevops Blog Python Subnet Cidr Merge
APIFlask Webhook Listener for Netbox
·258 words·2 mins
Netdevops Blog Netbox Python Api Apiflask
Ansible: Disable / Enable proxy for single task
·152 words·1 min
Netdevops Blog Ansible Proxy Tower