Comment importer facilement des utilisateurs Keycloak avec JBang

Contexte

Lorsque nous mettons en place un nouveau système, il est souvent nécessaire d’effectuer une reprise. les exports disponibles sont très souvent un fichier xls ou csv.

La sécurité ne déroge pas à la règle. Nous allons étudier dans ce billet l’importation des utilisateurs lors de la mise à en place de la solution Keycloak.

Bien que Keycloak possède une API REST Admin, nous allons passé par un outil de la communauté Keycloak : keycloak-config-cli.

avertissementC’est donc un outil non officiel de Keycloak mais il est très pratique. Cela permet de réaliser de la "Configuration as Code for Keycloak". C’est à dire qu’à partir d’un fichier de configuration yaml ou json, il est possible de configurer complètement Keycloak.

astuceEn effet, Keycloak est très configurable avec beaucoup d’options de configuration. Si la configuration est faite manuellement, cela peut amener des configurations différentes entre les environnements et produire des problèmes. Le fait que cela soit dans un fichier de configuration, nous pouvons ainsi nous assurer de reproduire la même configuration sur l’ensemble des environnements.

Nous allons utiliser seulement la partie concernant la configuration des utilisateurs pour réaliser l’importation. Le fichier doit avoir le format suivant :

{
  "enabled": true,
  "realm": "realmWithUsers",
  "users": [
    {
      "username": "moi",
      "email": "prenom.nom@mail.fr",
      "enabled": true,
      "firstName": "mon prenom",
      "lastName": "mon nom"
    }
  ]
}

Coté export des utilisateurs, nous avons un fichier csv ayant les colonnes suivantes :

  • User Login

  • User Email

  • First Name

  • Last Name

C’est parti !

Nous avons besoin de générer un fichier json à partir du fichier csv. Nous pourrions passer par un script bash ou un batch Java. Finalement, j’ai opté pour jbang. L’occasion de le mettre à l’épreuve dans un cas d’usage.

avertissementSi vous ne connaissez pas cet outil, je vous invite à lire mon article précédent sur le sujet JBang, Script en java.

Nous initialisons notre script avec le commande suivante :

jbang init -t cli UsersCSVtoKeycloakJSON.java

Lecture du fichier d’entrée

Nous commençons par lire le fichier en utilisant le schéma try-with-resource. Nous utilisons aussi l’API NIO pour récupérer l’instance BufferedReader.

Path csvFichier = Path.of(fichier.getAbsolutePath());
try (BufferedReader breader = Files.newBufferedReader(csvFichier, StandardCharsets.UTF_8)) {
  ...
}

Il est possible à partir d’un BufferedReader de lire un fichier en utilisant les flux ("Stream"). Il en découle le code suivant :

  utilisateurs = breader.lines() //(1)
                        .skip(1) //(2)
                        .map(ligne -> ligne.split(";")) //(3)
                        .filter(colonnes -> colonnes.length >= 4) //(4)
                        .map(colonnes -> { //(5)
                            String login = colonnes[0];
                            String email = colonnes[1];
                            String prenom = colonnes[2];
                            String nom = colonnes[3];
                            return new Utilisateur(login, nom, prenom, email);
                        })
                        .filter(Utilisateur::isEmailValide) (6)
                        .collect(Collectors.toList()); (7)
  1. Lecture du fichier via un flux ("stream") : Stream<String>

  2. Permet d’ignorer la ligne contenant l’en-tête du fichier csv

  3. Permet de découper chaque ligne avec le séparateur, on obtient Stream<String[]

  4. Vérification que la ligne est valide avec au moins 4 colonnes

  5. Récupération des colonnes pour créer une instance d’Utilisateur : Stream<Utilisateur>

  6. Vérification que l’adresse mail est valide

  7. Collecte du flux en une liste d’utilisateurs : List<Utilisateur>

Maintenant que les enregistrements sont disponibles. Profitons-en.

record Utilisateur(String login, String nom, String prenom, String email) {
    Utilisateur {
        login = login.trim().toLowerCase(); // (1) (2)
        nom = nom.trim(); // (1)
        prenom = prenom.trim(); // (1)
        email = email.trim(); // (1)
    }

    boolean isEmailValide() { //(3)
        return this.email().length() > 1;
    }
}
  1. Suppression des espaces superflus devant et derrière. (Réaffectation du composant)

  2. Le nom d’utilisateur sur Keycloak est forcément en minuscule. Nous protégeons en conséquence.

  3. Définition d’une méthode pour valider que l’adresse mail est correcte. (Nous pourrions augmenter les tests)

Ecriture du fichier de sortie

Comme nous avons pu le voir plus haut, nous pouvons décomposé le fichier de sortie en 3 parties :

  • Un bloc pour l’en-tête

  • Un bloc pour chaque utilisateur

  • Un bloc pour le pied du fichier

Les Blocs de textes sont super pratiques pour cela :

public static String DEBUT_FICHIER_JSON = """
        {
            "enabled": true,
            "realm": "%s",
            "users": [
        """;

public static String UTILISATEUR_ENTREE = """
            {
                "username": "%s",
                "email": "%s",
                "enabled": true,
                "firstName": "%s",
                "lastName": "%s"
            }
    """;

public static String FIN_FICHIER_JSON = """
            ]
        }
        """;

astuce Les blocs sont clairs sans caractère parasite et c’est appréciable.

astuce En jouant sur l’emplacement des 3 guillemets fermants, nous pouvons ainsi générer un fichier correctement indenté. (même si cela est surtout pour nous).

Globalement, le principe est simple avec un parcours des utilisateurs lus précédemment :

try (BufferedWriter buffer = Files.newBufferedWriter(jsonFichier,
        StandardCharsets.UTF_8,
        StandardOpenOption.CREATE)) { // (1)
    ...
    buffer.append(String.format(DEBUT_FICHIER_JSON, nomRoyaume)); // (2)
    for (Utilisateur u : utilisateurs) { // (3)
        ...
        buffer.append(String.format(UTILISATEUR_ENTREE, u.login(), u.email(),
          u.prenom(), u.nom())); // (4)
    }
    buffer.append(FIN_FICHIER_JSON); // (5)
}
  1. Ouverture du fichier

  2. Ecriture du bloc d’en-tête du fichier

  3. Boucle pour chaque utilisateur

  4. Ecriture du bloc utilisateur pour l’utilisateur courant

  5. Ecriture du bloc de fin de fichier

Il ne reste plus à traiter la virgule entre chaque bloc utilisateur avec la particularité du premier utilisateur.

if (premier) {
    premier = false;
} else {
    buffer.append("        ,\n");
}

Configuration de l’outil

Ayant sélectionné le modèle cli, nous avons la librairie picocli. Nous pouvons en profiter pour rajouter les 3 options suivantes à notre outil :

  • Nom du fichier CSV d’entrée

  • Nom du fichier JSON de sortie

  • Nom du royaume ("realm")

  @Option(names = { "-r", "--realmName" }, required = true, paramLabel = "ROYAUME", description = "Nom du royaume")
  String nomRoyaume; // realmName

  @Option(names = { "-f", "--file" }, required = true, paramLabel = "FICHIER", description = "Fichier CSV d'entrée")
  File fichier;

  @Option(names = { "-o",
          "--output" }, required = true, paramLabel = "FICHIER", description = "Fichier JSON de sortie")
  File sortie;

Et voilà, le tour est joué.

astuce Le Script Complet: UsersCSVtoKeycloakJSON.java est disponible sur github.

Exécution

Nous pouvons exécuter notre script.

avertissement Utilisant les enregistrements, il faudra utiliser un JDK 16 (ou 15 avec --enable-preview). Sinon, il faudra passer par une classe (moins élégant).

jbang UsersCSVtoKeycloakJSON.java  --file tests-users.csv --output tests-users.json --realmName test

ou directement

./UsersCSVtoKeycloakJSON.java  --file tests-users.csv --output tests-users.json --realmName test

Utilisation l’outil Keycloak-Config-Cli

Maintenant que nous avons généré le fichier tests-users.json avec notre script, nous allons pouvoir utiliser l’outil keycloak-config-cli.

java -jar keycloak-config-cli-12.0.4.jar --keycloak.url=https://<mon_serveur_keycloak>/auth --keycloak.ssl-verify=false --keycloak.user=<compte_admin> --keycloak.password=<password> --import.path=./users.json

A vous de jouer ! N’hésites pas à me faire un retour sur ce billet via les commentaires ou twitter.