From 107dd922bb10f0eab42e301ad6e8bf5b9f0de36c Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 26 Feb 2025 16:38:42 +0100 Subject: [PATCH] =?UTF-8?q?Ajout=20d'un=20script=20Python=20pour=20synchro?= =?UTF-8?q?niser=20les=20droits=20administrateurs=20dans=20Nextcloud=20et?= =?UTF-8?q?=20mise=20=C3=A0=20jour=20du=20script=20de=20synchronisation=20?= =?UTF-8?q?existant=20pour=20utiliser=20ce=20nouveau=20script.=20Configura?= =?UTF-8?q?tion=20des=20permissions=20et=20ajout=20du=20script=20=C3=A0=20?= =?UTF-8?q?cron=20pour=20une=20ex=C3=A9cution=20horaire.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ansible/playbooks/5_nextcloud.yml | 24 ++++- ansible/run_playbooks.sh | 2 + front/app/api/users/[id]/route.ts | 1 + nextcloud/sync_admin_rights.sh | 73 ++++--------- nextcloud/sync_admins.py | 166 ++++++++++++++++++++++++++++++ 5 files changed, 214 insertions(+), 52 deletions(-) create mode 100644 nextcloud/sync_admins.py diff --git a/ansible/playbooks/5_nextcloud.yml b/ansible/playbooks/5_nextcloud.yml index 716bd0e..6ef3a4c 100644 --- a/ansible/playbooks/5_nextcloud.yml +++ b/ansible/playbooks/5_nextcloud.yml @@ -84,7 +84,7 @@ --mapping-uid preferred_username --mapping-display-name name --mapping-email email - --mapping-groups groups + --mapping-groups realm_roles --mapping-quota quota --unique-uid preferred_username --group-provisioning true @@ -109,3 +109,25 @@ until: allow_local_remote_servers is success retries: 3 delay: 5 + + - name: Copier le script de synchronisation des droits admin + copy: + src: "{{ git_dest }}/nextcloud/sync_admin_rights.sh" + dest: "/etc/cron.hourly/sync_admin_rights" + mode: "0755" + remote_src: yes + register: copy_script + until: copy_script is success + retries: 3 + delay: 5 + + - name: Configurer les permissions du script + file: + path: "/etc/cron.hourly/sync_admin_rights" + owner: root + group: root + mode: "0755" + register: set_permissions + until: set_permissions is success + retries: 3 + delay: 5 diff --git a/ansible/run_playbooks.sh b/ansible/run_playbooks.sh index 9e84da8..4f73850 100755 --- a/ansible/run_playbooks.sh +++ b/ansible/run_playbooks.sh @@ -9,10 +9,12 @@ NC='\033[0m' # No Color SUDO_PASSWORD="12345" # Liste des playbooks à exécuter +#TODO: Supprimer "playbooks/dev/3_1_keycloak_dev.yml" PLAYBOOKS=( "playbooks/1_docker.yml" "playbooks/2_portainer.yml" "playbooks/3_keycloak.yml" + "playbooks/dev/3_1_keycloak_dev.yml" "playbooks/4_mysql.yml" "playbooks/5_nextcloud.yml" "playbooks/0_front.yml" diff --git a/front/app/api/users/[id]/route.ts b/front/app/api/users/[id]/route.ts index 746c9fa..785cae2 100644 --- a/front/app/api/users/[id]/route.ts +++ b/front/app/api/users/[id]/route.ts @@ -2,6 +2,7 @@ import { getServerSession } from "next-auth"; import { authOptions } from "../../auth/[...nextauth]/route"; import { NextResponse } from "next/server"; +//TODO: Ajouter la suppression automatique du compte Nextcloud export async function DELETE( req: Request, { params }: { params: { id: string } } diff --git a/nextcloud/sync_admin_rights.sh b/nextcloud/sync_admin_rights.sh index b662216..8226fa4 100644 --- a/nextcloud/sync_admin_rights.sh +++ b/nextcloud/sync_admin_rights.sh @@ -1,58 +1,29 @@ #!/bin/bash -# Activer le mode debug et sortie en cas d'erreur -set -e -set -u +# Chemin vers les scripts +SCRIPT_DIR="/opt/Neah-Enkun/nextcloud" +PYTHON_SCRIPT="${SCRIPT_DIR}/sync_admins.py" +LOG_FILE="/var/log/nextcloud_admin_sync.log" -# Configuration -CONTAINER="neah-nextcloud" -LOG_FILE="/var/log/nextcloud/admin_sync.log" +# Nom du conteneur Nextcloud +CONTAINER_NAME="neah-nextcloud" -log() { - echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" -} +# Vérifier que le conteneur est en cours d'exécution +if ! docker ps | grep -q $CONTAINER_NAME; then + echo "$(date) - Le conteneur $CONTAINER_NAME n'est pas en cours d'exécution" >> $LOG_FILE + exit 1 +fi -# Créer le répertoire de logs si nécessaire -mkdir -p "$(dirname "$LOG_FILE")" +# Exécuter le script Python +echo "$(date) - Lancement de la synchronisation des administrateurs" >> $LOG_FILE +python3 $PYTHON_SCRIPT --container $CONTAINER_NAME -log "Début de la synchronisation des droits administrateurs" +# Vérifier le code de sortie +EXIT_CODE=$? +if [ $EXIT_CODE -ne 0 ]; then + echo "$(date) - La synchronisation a échoué avec le code $EXIT_CODE" >> $LOG_FILE + exit $EXIT_CODE +fi -# Récupérer la liste des administrateurs actuels -log "Récupération des administrateurs actuels..." -current_admins=$(docker exec -u 33 $CONTAINER php occ user:list --output=json | jq -r 'to_entries[] | select(.value.isEnabled == true and .value.isAdmin == true) | .key') - -# Pour chaque admin actuel -for admin in $current_admins; do - log "Vérification des droits pour l'administrateur: $admin" - - # Vérifier si l'utilisateur est dans le groupe admin - is_in_admin_group=$(docker exec -u 33 $CONTAINER php occ group:list --output=json | \ - jq -r --arg group "admin" --arg user "$admin" '.[$group].users | contains([$user]) // false') - - if [ "$is_in_admin_group" = "false" ]; then - log "Retrait des droits admin pour: $admin" - docker exec -u 33 $CONTAINER php occ user:info "$admin" --output=json - docker exec -u 33 $CONTAINER php occ group:removeuser admin "$admin" - fi -done - -# Récupérer la liste des membres du groupe admin -log "Récupération des membres du groupe admin..." -admin_group_members=$(docker exec -u 33 $CONTAINER php occ group:list --output=json | \ - jq -r '.admin.users[]? // empty') - -# Pour chaque membre du groupe admin -for member in $admin_group_members; do - log "Vérification des droits admin pour le membre: $member" - - # Vérifier si l'utilisateur a les droits admin - is_admin=$(docker exec -u 33 $CONTAINER php occ user:list --output=json | \ - jq -r --arg user "$member" '.[$user].isAdmin // false') - - if [ "$is_admin" = "false" ]; then - log "Ajout des droits admin pour: $member" - docker exec -u 33 $CONTAINER php occ group:adduser admin "$member" - fi -done - -log "Fin de la synchronisation des droits administrateurs" \ No newline at end of file +echo "$(date) - Synchronisation terminée avec succès" >> $LOG_FILE +exit 0 \ No newline at end of file diff --git a/nextcloud/sync_admins.py b/nextcloud/sync_admins.py new file mode 100644 index 0000000..69e5a5d --- /dev/null +++ b/nextcloud/sync_admins.py @@ -0,0 +1,166 @@ +import subprocess +import logging +import json +import argparse +import sys +from datetime import datetime + +# Configuration du logging +log_file = "/var/log/nextcloud_admin_sync.log" +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[ + logging.FileHandler(log_file), + logging.StreamHandler(sys.stdout) + ] +) +logger = logging.getLogger("admin_sync") + +def run_command(command): + """Exécute une commande shell et retourne le résultat""" + logger.debug(f"Exécution de la commande: {command}") + try: + result = subprocess.run(command, shell=True, check=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + text=True) + return result.stdout.strip() + except subprocess.CalledProcessError as e: + logger.error(f"Erreur lors de l'exécution de la commande: {e}") + logger.error(f"Sortie d'erreur: {e.stderr}") + return None + +def get_nextcloud_groups(container_name): + """Récupère tous les groupes Nextcloud avec leurs membres""" + cmd = f"docker exec -u 33 {container_name} php occ group:list --output=json" + output = run_command(cmd) + if output: + try: + return json.loads(output) + except json.JSONDecodeError: + logger.error("Impossible de décoder la sortie JSON des groupes") + return {} + return {} + +def get_group_members(groups_data, group_name): + """Récupère les membres d'un groupe spécifique à partir des données de group:list""" + if group_name in groups_data: + return groups_data[group_name] + return [] + +def get_admin_groups(container_name, groups): + """Identifie les groupes admin""" + admin_groups = [] + for group_name in groups: + # Vérifier si le nom du groupe est 'admin' + if group_name == 'admin': + admin_groups.append(group_name) + else: + # Vérifier si le displayName du groupe est 'admin' + cmd = f"docker exec -u 33 {container_name} php occ group:info {group_name} --output=json" + output = run_command(cmd) + if output: + try: + group_info = json.loads(output) + if 'displayName' in group_info and group_info['displayName'] == 'admin': + admin_groups.append(group_name) + except json.JSONDecodeError: + logger.error(f"Impossible de décoder les infos du groupe {group_name}") + + return admin_groups + +def get_current_admins(container_name): + """Récupère la liste actuelle des administrateurs en utilisant la commande group:list pour le groupe admin""" + cmd = f"docker exec -u 33 {container_name} php occ group:list --output=json" + output = run_command(cmd) + if output: + try: + groups_data = json.loads(output) + # Le groupe "admin" contient les administrateurs Nextcloud + if "admin" in groups_data: + return groups_data["admin"] + else: + logger.warning("Groupe 'admin' non trouvé dans Nextcloud") + return [] + except json.JSONDecodeError: + logger.error("Impossible de décoder la sortie JSON des groupes") + return [] + +def set_admin_status(container_name, username, is_admin=True): + """Définit ou révoque le statut d'administrateur pour un utilisateur en ajoutant/retirant l'utilisateur du groupe admin""" + if is_admin: + cmd = f"docker exec -u 33 {container_name} php occ group:adduser admin {username}" + status = "accordé" + else: + cmd = f"docker exec -u 33 {container_name} php occ group:removeuser admin {username}" + status = "révoqué" + + output = run_command(cmd) + if output is not None: + logger.info(f"Statut d'administrateur {status} pour {username}") + return True + else: + logger.error(f"Impossible de {status} le statut d'administrateur pour {username}") + return False + +def main(container_name): + logger.info("=== Début de la synchronisation des administrateurs ===") + + # Récupération de tous les groupes avec leurs membres + groups_data = get_nextcloud_groups(container_name) + if not groups_data: + logger.error("Impossible de récupérer les groupes Nextcloud") + return False + + groups = list(groups_data.keys()) + logger.info(f"Groupes trouvés: {len(groups)}") + + # Identification des groupes admin + admin_groups = get_admin_groups(container_name, groups) + logger.info(f"Groupes admin identifiés: {admin_groups}") + + # Récupération des utilisateurs dans les groupes admin + should_be_admins = set() + for group in admin_groups: + members = get_group_members(groups_data, group) + logger.info(f"Groupe {group}: {len(members)} membres") + should_be_admins.update(members) + + logger.info(f"Utilisateurs qui devraient être administrateurs: {len(should_be_admins)}") + + # Récupération des administrateurs actuels + current_admins = get_current_admins(container_name) + logger.info(f"Administrateurs actuels: {len(current_admins)}") + + # Attribution des droits admin aux nouveaux admins + for username in should_be_admins: + if username not in current_admins: + logger.info(f"Attribution des droits admin à {username}") + set_admin_status(container_name, username, True) + + # Révocation des droits admin pour ceux qui ne sont plus dans un groupe admin + for username in current_admins: + if username not in should_be_admins: + logger.info(f"Révocation des droits admin pour {username}") + set_admin_status(container_name, username, False) + + # Résumé + new_admins = get_current_admins(container_name) + logger.info("=== Résumé de la synchronisation ===") + logger.info(f"Groupes admin: {', '.join(admin_groups)}") + logger.info(f"Utilisateurs dans des groupes admin: {len(should_be_admins)}") + logger.info(f"Administrateurs avant synchronisation: {len(current_admins)}") + logger.info(f"Administrateurs après synchronisation: {len(new_admins)}") + logger.info(f"Ajouts: {len(should_be_admins - set(current_admins))}") + logger.info(f"Suppressions: {len(set(current_admins) - should_be_admins)}") + logger.info("=== Fin de la synchronisation ===") + + return True + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Synchronise les administrateurs Nextcloud avec Keycloak') + parser.add_argument('--container', default='neah-nextcloud', + help='Nom du conteneur Nextcloud (défaut: neah-nextcloud)') + args = parser.parse_args() + + main(args.container) \ No newline at end of file