From 95c2327a012d560462d46f04a42a92115f18ae73 Mon Sep 17 00:00:00 2001 From: Benjamin Bertrand <benjamin.bertrand@esss.se> Date: Tue, 16 Jan 2018 22:08:53 +0100 Subject: [PATCH] Add command to sync users with LDAP server Command shall be run every night to keep the users in the database in sync with the LDAP server. If a user is not found: - set its groups to [] - revoke all its tokens --- app/commands.py | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ app/factory.py | 17 +---------- app/models.py | 2 +- 3 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 app/commands.py diff --git a/app/commands.py b/app/commands.py new file mode 100644 index 0000000..204a2c8 --- /dev/null +++ b/app/commands.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +""" +app.commands +~~~~~~~~~~~~ + +This module defines extra flask commands. + +:copyright: (c) 2018 European Spallation Source ERIC +:license: BSD 2-Clause, see LICENSE for more details. + +""" +import ldap3 +import sqlalchemy as sa +from flask import current_app +from .extensions import db, ldap_manager +from .defaults import defaults +from .models import User +from . import utils + + +def sync_user(connection, user): + """Synchronize the user from the database with information from the LDAP server""" + search_attr = current_app.config.get('LDAP_USER_LOGIN_ATTR') + object_filter = current_app.config.get('LDAP_USER_OBJECT_FILTER') + search_filter = f'(&{object_filter}({search_attr}={user.username}))' + connection.search( + search_base=ldap_manager.full_user_search_dn, + search_filter=search_filter, + search_scope=getattr( + ldap3, current_app.config.get('LDAP_USER_SEARCH_SCOPE')), + attributes=current_app.config.get('LDAP_GET_USER_ATTRIBUTES') + ) + if len(connection.response) == 1: + ldap_user = connection.response[0] + attributes = ldap_user['attributes'] + user.display_name = utils.attribute_to_string(attributes['cn']) + user.email = utils.attribute_to_string(attributes['mail']) + groups = ldap_manager.get_user_groups(dn=ldap_user['dn'], _connection=connection) + user.groups = sorted([utils.attribute_to_string(group['cn']) for group in groups]) + current_app.logger.info(f'{user} updated') + else: + # Clear user's groups + user.groups = [] + # Revoke all user's tokens + for token in user.tokens: + db.session.delete(token) + current_app.logger.info(f'{user} disabled') + return user + + +def register_cli(app): + @app.cli.command() + def initdb(): + """Create the database tables and initialize them with default values""" + db.engine.execute('CREATE EXTENSION IF NOT EXISTS citext') + db.create_all() + for instance in defaults: + db.session.add(instance) + try: + db.session.commit() + except sa.exc.IntegrityError as e: + db.session.rollback() + app.logger.debug(f'{instance} already exists') + + @app.cli.command() + def syncusers(): + """Synchronize all users from the database with information the LDAP server""" + try: + connection = ldap_manager.connection + except ldap3.core.exceptions.LDAPException as e: + current_app.logger.warning(f'Failed to connect to the LDAP server: {e}') + return + for user in User.query.all(): + sync_user(connection, user) + db.session.commit() diff --git a/app/factory.py b/app/factory.py index 79bc856..6a05d14 100644 --- a/app/factory.py +++ b/app/factory.py @@ -24,22 +24,7 @@ from .user.views import bp as user from .api.user import bp as user_api from .api.inventory import bp as inventory_api from .api.network import bp as network_api -from .defaults import defaults - - -def register_cli(app): - @app.cli.command() - def initdb(): - """Create the database tables and initialize them with default values""" - db.engine.execute('CREATE EXTENSION IF NOT EXISTS citext') - db.create_all() - for instance in defaults: - db.session.add(instance) - try: - db.session.commit() - except sa.exc.IntegrityError as e: - db.session.rollback() - app.logger.debug(f'{instance} already exists') +from .commands import register_cli def create_app(config=None): diff --git a/app/models.py b/app/models.py index 4d931f1..ed58eed 100644 --- a/app/models.py +++ b/app/models.py @@ -89,7 +89,7 @@ def save_user(dn, username, data, memberships): display_name=utils.attribute_to_string(data['cn']), email=utils.attribute_to_string(data['mail'])) # Always update the user groups to keep them up-to-date - user.groups = [utils.attribute_to_string(group['cn']) for group in memberships] + user.groups = sorted([utils.attribute_to_string(group['cn']) for group in memberships]) db.session.add(user) db.session.commit() return user -- GitLab