Mise en place de politiques de validation dans OpenIDM

OpenIDM permet de valider le format des attributs, via des policies.
Ces règles sont définies par défaut dans le fichier OPENIDM_HOME/bin/defaults/script/policy.js. On y trouve par exemple des règles de type : required, not-empty, unique, valid-date, valid-email-address-format, min-length et bien d'autres. Une vingtaine de règles sont ainsi disponibles, mais il est également possible d'ajouter des règles spécifiques si besoin.

Les différentes étapes pour ajouter une règle spécifique sont les suivantes :

  • Définition de la règle, via une fonction Javascript, dans un fichier spécifique du projet
  • Déclaration pour prise en compte du nouveau fichier
  • Modification du fichier policy.json pour prendre en compte la nouvelle règle sur un attribut

Tout ceci est expliqué dans la documentation Forgerock, dans le document OpenIDM Integrator's Guide, au chapitre 9 : Using Policies to Validate Data
Rien ne valant un exemple, nous allons montrer ici comment ajouter 2 règles qui seront utilisées sur l'attribut password (mais qui pourraient servir ailleurs) :

  • une règle permettant de vérifier que la longueur de l'attribut ne dépasse pas une longueur maximale
  • une règle vérifiant que l'attribut ne fait pas partie d'une liste de mots (azerty, 123456, qwerty)

Tests avant modification

Par défaut, la règle sur le mot de passe impose les restrictions suivantes :

  • Minimum 6 caractères
  • Au moins 1 chiffre
  • Au moins 1 caractère en Majuscules

Mot de passe non conforme

Exemple avec un mot de passe non conforme (pour le confort de lecture, j'ai omis les header de la commande curl) :

curl --request POST \
 --data '[ { "operation":"replace", "field":"/password", "value":"test" } ]' \
 'http://localhost:8080/openidm/managed/user/c06d764d-72cd-4e33-85a7-32c7bf34352d?_action=patch'
{
    "code": 403,
    "detail": {
        "failedPolicyRequirements": [
            {
                "policyRequirements": [
                    {
                        "params": {
                            "numCaps": 1
                        },
                        "policyRequirement": "AT_LEAST_X_CAPITAL_LETTERS"
                    }
                ],
                "property": "password"
            },
            {
                "policyRequirements": [
                    {
                        "params": {
                            "numNums": 1
                        },
                        "policyRequirement": "AT_LEAST_X_NUMBERS"
                    }
                ],
                "property": "password"
            },
            {
                "policyRequirements": [
                    {
                        "params": {
                            "minLength": 6
                        },
                        "policyRequirement": "MIN_LENGTH"
                    }
                ],
                "property": "password"
            }
        ],
        "result": false
    },
    "message": "Failed policy validation",
    "reason": "Forbidden"
}

Le mot de passe n'est pas conforme au trois règles.

Mot de passe trop court

On peut retester avec un mot de passe trop court, mais avec un chiffre et une lettre majuscule :

curl --request POST \
 --data '[ { "operation":"replace", "field":"/password", "value":"Test1" } ]' \
 'http://localhost:8080/openidm/managed/user/c06d764d-72cd-4e33-85a7-32c7bf34352d?_action=patch'
{
    "code": 403,
    "detail": {
        "failedPolicyRequirements": [
            {
                "policyRequirements": [
                    {
                        "params": {
                            "minLength": 6
                        },
                        "policyRequirement": "MIN_LENGTH"
                    }
                ],
                "property": "password"
            }
        ],
        "result": false
    },
    "message": "Failed policy validation",
    "reason": "Forbidden"
}

Dans ce cas, seul la règle "longueur minimale" n'est pas respectée.

Test conforme

Si on teste avec un mot correct, la mise à jour s'effectue sans erreur.

Création du fichier de définition des règles

Structure des répertoires

Nous avons structuré notre projet avec plusieurs répertoires et sous-répertoires pour les scripts :

├── conf
│   ├── audit.json
│   ├── .../...
│   ├── policy.json
│   ├── .../...
└── script
    ├── AD
    ├── auth
    ├── endpoints
    ├── LDAP    
    ├── policies
    └── user

Les scripts concernant les règles spécifiques seront donc créés dans le répertoire script/policies.

Règle de longueur maximale

Nous allons créer un fichier script/policies/customPolicies.js qui va contenir nos règles spécifiques. Nous aurions pu également faire un fichier par règle.
Le contenu de ce fichier est décomposé en trois parties : la création d'un objet de type policy, comprenant plusieurs attributs :

  • policyId : c'est un ID unique, qui permet d'identifer la policy, c'est ce qui sera utilisé dans le fichier de paramétrage pour appliquer la règle sur un attribut
  • policyExec : le nom de la fonction qui implémente la règle
  • policyRequirements : un tableau qui contient les éléments qui doivent être validés. Généralement une règle ne valide qu'un seul éléments

Dans notre cas, on aura :

var custompolicy1 = {
  "policyId" : "max-length",
  "policyExec" : "maxLength",
  "policyRequirements" : ["MAX_LENGTH"]
};

On trouve ensuite l'appel à une fonction OpenIDM, addPolicy, qui permet de déclarer la nouvelle règle :

addPolicy(custompolicy1);

Puis enfin, le code de la fonction, qui correspond à l'attribut policyExec. Dans notre exemple, la fonction s'appelera donc maxLength. Le code est très inspiré de la fonction par défaut minLength :

/**
 * Checks max length of password. Based on default minLength policy
 */  
function maxLength(fullObject, value, params, property) {
  var isRequired = _.find(this.failedPolicyRequirements, function (fpr) { 
    return fpr.policyRequirement === "REQUIRED"; 
  });
  var isNonEmptyString = (typeof(value) === "string" && value.length)
  var hasMaxLength = isNonEmptyString ? (value.length <= params.maxLength) : false;
  
  if ((isRequired || isNonEmptyString) && !hasMaxLength) {
    return [ { "policyRequirement" : "MAX_LENGTH", "params" : {"maxLength":params.maxLength} } ];
  }
  return [];
}

Vérification parmi une liste de mots interdits

Pour cette vérification, nous allons créer ajouter une déclaration de règle et la fonction associée dans le fichier script/policies/customPolicies.js :

var custompolicy2 = {
  "policyId" : "forbidden-words",
  "policyExec" : "forbiddenWords",
  "policyRequirements" : ["FORBIDDEN_WORDS"]
};

addPolicy(custompolicy2);

/**
 * Checks that password is not in the list of forbidden words
 * based on cannotContainOthers
 * 
 * @params : disallowedWords
 */ 
function forbiddenWords(fullObject, value, params, property) {
  var wordsArray = params.disallowedWords.split(","), i;

  if (value && typeof(value) === "string" && value.length) {
    for (i = 0; i < wordsArray.length; i++) {
      if ( value.match(wordsArray[i]) ) {        
        return [{"policyRequirement": "FORBIDDEN_WORDS", params: {"disallowedWords": wordsArray[i]}}];
      }
    }
  }
  return [];
}

Déclaration de la prise en compte du fichier

La modification sera à faire dans le fichier conf/policy.json du projet. On peut voir dans les premières lignes du fichier qu'il contient le nom du fichier de règles par défaut (policy.js) :

{
    "type" : "text/javascript",
    "file" : "policy.js",
    "additionalFiles" : [ ],
    "resources" : [ ...

On peut voir aussi un attribut additionalFiles. C'est ici que l'on va déclarer le nom du fichier contenant nos règles spécifiques. Dans notre cas :

{
    "type" : "text/javascript",
    "file" : "policy.js",
    "additionalFiles" : [
        "script/user/customPolicies.js"
    ],
    "resources" : [ ...

Ceci va permettre à OpenIDM de charger ce fichier spécifique, et d'ajouter les policies.

Prise en compte sur les attributs

Les modifications seront à faire dans le fichier conf/policy.json du projet. Il faut identifier l'objet et l'attribut concerné. Par exemple pour le mot de passe, on choisira comme ressource "resource" : "managed/user/*", et dans l'attribut password, on ajoute la référence à notre règle via le policyID, et on passe en paramètre la longueur maximale

{
  "name" : "password",
    "policies" : [
      { "policyId" : "not-empty" },
      { "policyId" : "at-least-X-capitals",
        "params" : { "numCaps" : 1 }
      },
      { "policyId" : "at-least-X-numbers",
        "params" : {  "numNums" : 1 }
      },
      { "policyId" : "minimum-length",
        "params" : { "minLength" : 6 }
      },
      
      { "policyId" : "max-length",
        "params" : { "maxLength" : 10 }
      },
      

      { "policyId" : "cannot-contain-others",
        .../...

Pour prendre en compte la liste des mots interdits, on ajoute de la même manière, pour l'attribut password, la règle et la liste de mots interdits :

      {
        "policyId" : "forbidden-words",
          "params" : {
            "disallowedWords" : "Myc0mpany,Qwerty1,Azerty1"
          }
      },

Nouvelle série de tests

Pour valider nos modifications, nous allons tester avec une longueur dépassant le maximum, puis avec un mot interdit.

Longueur maximale

Si on tente de modifier le mot de passe avec une valeur de plus de 10 caractères, on obtient un message de retour :

curl --request POST \
  --data '[ { "operation":"replace", "field":"/password", "value":"Test5678901" } ]' \
  'http://localhost:8080/openidm/managed/user/c06d764d-72cd-4e33-85a7-32c7bf34352d?_action=patch'
{
    "code": 403,
    "detail": {
        "failedPolicyRequirements": [
            {
                "policyRequirements": [
                    {
                        "params": {
                            "maxLength": 10
                        },
                        "policyRequirement": "MAX_LENGTH"
                    }
                ],
                "property": "password"
            }
        ],
        "result": false
    },
    "message": "Failed policy validation",
    "reason": "Forbidden"
}

Mot interdit

Si on tente de passer un mot interdit de la liste, la modification du mot de passe est rejetée :

curl --request POST \
  --data '[ { "operation":"replace", "field":"/password", "value":"Azerty1" } ]' \
  'http://localhost:8080/openidm/managed/user/c06d764d-72cd-4e33-85a7-32c7bf34352d?_action=patch'
{
    "code": 403,
    "detail": {
        "failedPolicyRequirements": [
            {
                "policyRequirements": [
                    {
                        "params": {
                            "disallowedWords": "Azerty1"
                        },
                        "policyRequirement": "FORBIDDEN_WORDS"
                    }
                ],
                "property": "password"
            }
        ],
        "result": false
    },
    "message": "Failed policy validation",
    "reason": "Forbidden"
}

Utilisation de la règle sur un autre attribut

Une fois la policy définie, il est possible de l'utiliser pour valider n'importe quel attribut. Par exemple si on veut s'assurer qu'en numéro de matricule comporte au plus 8 caractères, on pourra ajouter dans le fichier conf/policy.json les lignes :

  {
    "name" : "employeeNumber",
    "policies" : [
      {
        "policyId" : "max-length",
          "params" : {
            "maxLength" : 8
          }
      }
      ]
  },

On peut alors tester la règle de validation :

curl --request POST \
  --data '[ { "operation":"replace", "field":"/employeeNumber", "value":"INT2015123" } ]'  \
  'http://localhost:8080/openidm/managed/user/c06d764d-72cd-4e33-85a7-32c7bf34352d?_action=patch'
{
    "code": 403,
    "detail": {
        "failedPolicyRequirements": [
            {
                "policyRequirements": [
                    {
                        "params": {
                            "maxLength": 8
                        },
                        "policyRequirement": "MAX_LENGTH"
                    }
                ],
                "property": "employeeNumber"
            }
        ],
        "result": false
    },
    "message": "Failed policy validation",
    "reason": "Forbidden"
}

Note : la validation des attributs s'effectue avant de déclencher les autres scripts (par exemple un script onCreate).
Tant que les données ne sont pas validées, les processus de provisionnement et de mise à jour ne seront pas lancés.

Catégorie