Skip to content

Vue d'ensemble

Ce guide montre comment configurer ServiceNow pour créer des tickets ou des tâches afin de gérer les demandes d'approvisionnement manuel.

ServiceNow est une solution puissante et il existe de nombreuses façons d'organiser les éléments de travail. Ce guide montre comment utiliser le catalogue pour créer des éléments de travail et comment y ajouter des tâches.

Configuration

Pour configurer l'approvisionnement manuel pour ServiceNow, suivez les étapes générales de configuration d'approvisionnement manuel décrites dans le guide Approvisionnement manuel.

API

Tous les scripts d'approvisionnement manuel ont accès à l'API suivante : API Reference

L'API fournit l'accès aux demandes qui doivent être traitées ainsi qu'aux méthodes utilitaires.

Gestion des demandes basée sur la commande d'un élément dans le catalogue et l'ajout de tâches

Ce script d'exemple montre comment gérer les demandes basées sur la commande d'un élément dans le catalogue et l'ajout de tâches à celui-ci.

Prérequis

  • Un élément de catalogue ServiceNow qui déclenchera la création d'un élément de demande
  • Le script suppose certains champs, mais ils doivent être personnalisés pour répondre aux exigences spécifiques de l'organisation.

Personnalisation

Le script doit être personnalisé pour répondre aux exigences spécifiques de l'organisation. Plus précisément, ces éléments doivent être personnalisés :

  • Les champs dans l'élément de catalogue
  • Les champs dans la table utilisateur, si les utilisateurs doivent être créés dynamiquement
  • Les champs dans la table des tâches

Le script déclare plusieurs variables et fonctions de hook qui doivent être fournies pour le faire fonctionner.

Script d'exemple

groovy
import com.okiok.pheonix.api.manualProvisioning.*
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import okhttp3.*
import org.slf4j.Logger

api = api as ManualProvisioningAPI
utils = api.utils as ManualProvisioningAPIUtils
logger = api.logger as Logger
httpClient = api.httpClient as OkHttpClient
provisioningRequests = api.provisioningRequests

/****** Variables qui changent selon l'environnement *******/
serviceNowURL = "https://example.service-now.com"
serviceNowUser = "[VOTRE_UTILISATEUR_SERVICE_NOW]"
serviceNowPassword = "[VOTRE_MOT_DE_PASSE_SERVICE_NOW]"
racmURL = "https://racm.example.com"

/****** Configuration ServiceNow *******/
// Identifiant de l'élément de catalogue ServiceNow pour commander les demandes
serviceNowCatalogItemId = "[ID_DE_L_ELEMENT_DE_CATALOGUE_SERVICE_NOW]"

// Groupes d'assignation par défaut
defaultAssignmentGroup = "[GROUPE_D_ASSIGNATION_PAR_DEFAUT]"
defaultWrittenRequestAssignmentGroup = "[GROUPE_D_ASSIGNATION_PAR_DEFAUT_POUR_LES_DEMANDES_ECRITES]"

// Email par défaut pour les demandes générées par le système (lorsque requester.id == 0)
defaultSystemRequesterEmail = "[EMAIL_PAR_DEFAUT_POUR_LES_DEMANDES_GENEREES_PAR_LE_SYSTEME]"

/****** Fonctions de hook pour personnaliser le script *******/
// Remplacez ces fonctions pour personnaliser la logique de regroupement des applications
// Par défaut, chaque application est regroupée séparément
Closure getApplicationGroupName = { String applicationName ->
    return applicationName
}

// Remplacez cette fonction pour personnaliser la détermination du groupe d'assignation pour les éléments de demande
// Par défaut, retourne le defaultAssignmentGroup
Closure getRequestItemAssignmentGroup = { ProvisioningRequest provisioningRequest ->
    if(provisioningRequest.accountRequests.isEmpty() && provisioningRequest.action != Action.IDENTITY_TERMINATION) {
        return defaultWrittenRequestAssignmentGroup
    }
    return defaultAssignmentGroup
}

// Remplacez cette fonction pour personnaliser la détermination du groupe d'assignation pour les tâches
// Par défaut, retourne le defaultAssignmentGroup
Closure getTaskAssignmentGroup = { String applicationName ->
    return defaultAssignmentGroup
}




/****** Script principal *******/
logger.info("Entrée dans le script d'approvisionnement")
def nbOfSuccessfulRequest = 0
// Pour chaque demande d'approvisionnement, créer un élément de demande, une tâche pour chaque application et une tâche pour toutes les demandes écrites
provisioningRequests.each { def provisioningRequest ->

    logger.info("Traitement de la demande d'approvisionnement ID ${provisioningRequest.getRequestIdIfPresent()}")
    try {
        // Créer un élément de demande via l'API Order Item
        if(provisioningRequest.action == Action.IDENTITY_CREATION && provisioningRequest.hasTarget()) {
            // Les utilisateurs externes qui viennent d'être créés pourraient ne pas exister encore dans ServiceNow, tenter de les créer ici
            // S'ils existent déjà, cela retournera une erreur que nous ignorerons
            createServiceNowIdentity(provisioningRequest.target)
        }

        def orderItemResponse = callOrderItem(provisioningRequest)
        def requestId = processOrderItemResponse(orderItemResponse)

        // Récupérer le SysId RITM requis pour construire le lien vers la demande dans ServiceNow depuis RACM
        def getRITMResponse = callGetRITM(requestId)
        def (ritmSysID,ritmNumber) = processGetRITMResponse(getRITMResponse)
        def ticketUrl = buildTicketURL(ritmSysID)

        // Regrouper les demandes de compte par nom d'application (personnaliser via le hook getApplicationGroupName)
        def accountRequestsByApplication = provisioningRequest.accountRequests.groupBy({accReq ->
            return getApplicationGroupName(accReq.applicationName)
        })

        // Pour chaque demande de compte, créer une tâche associée à l'élément de demande
        accountRequestsByApplication.each { appName,accountRequests ->
            try {
                def createTaskResponse = callCreateTask(ritmSysID, appName, accountRequests, provisioningRequest)
                processCreateTaskResponse(createTaskResponse)

                // Aucune erreur survenue, compléter la demande de compte avec le numéro de demande et l'URL
                accountRequests.each {accountRequest ->
                    try {
                        api.completeAccountRequest(accountRequest, ritmNumber, ticketUrl)
                        nbOfSuccessfulRequest++
                    } catch (Exception e) {
                        logger.error("Une erreur s'est produite lors de la finalisation de la demande de compte avec l'ID : ${accountRequest.id}. La gestion de compte sera mise en statut d'erreur avec le message d'erreur", e)
                        api.putAccountRequestsInError(accountRequest, e)
                    }
                }
            } catch (Exception e) {
                logger.error("Une erreur s'est produite lors de la création de la tâche pour l'application avec l'ID : ${appName}. Les gestions de compte seront mises en statut d'erreur avec le message d'erreur", e)
                accountRequests.each { accountRequest -> api.putAccountRequestsInError(accountRequest, e) }
            }
        }

        if (provisioningRequest.writtenRequests.size() > 0) {
            // Créer une tâche pour les demandes écrites
            try {
                def createTaskResponse = callCreateTask(ritmSysID, provisioningRequest.writtenRequests, provisioningRequest)
                processCreateTaskResponse(createTaskResponse)
                // Aucune erreur survenue, compléter les demandes écrites avec le numéro de demande et l'URL
                api.completeWrittenRequests(provisioningRequest.writtenRequests, ritmNumber, ticketUrl)
                nbOfSuccessfulRequest += provisioningRequest.writtenRequests.size()
            } catch (Exception e) {
                logger.error("Une erreur s'est produite lors de la création de la tâche pour les demandes écrites dans la demande d'approvisionnement : ${provisioningRequest.getRequestIdIfPresent()}. Les gestions de demandes écrites seront mises en statut d'erreur avec le message d'erreur", e)
                // Mettre les demandes écrites en statut d'erreur afin qu'elles puissent être réessayées plus tard
                api.putWrittenRequestsInError(provisioningRequest.writtenRequests, e)
            }
        }

        logger.info("Demande d'approvisionnement ID : ${provisioningRequest.getRequestIdIfPresent()} traitée")
    } catch (Exception e) {
        logger.error("Une erreur s'est produite lors du traitement de la demande d'approvisionnement ID : ${provisioningRequest.getRequestIdIfPresent()}. Toutes les gestions de compte associées à cette demande d'approvisionnement seront mises en statut d'erreur avec le message d'erreur", e)
        // Mettre toutes les demandes de compte en statut d'erreur afin qu'elles puissent être réessayées plus tard
        api.putProvisioningRequestInError(provisioningRequest, e)
    }
}

logger.info("Sortie du script d'approvisionnement")
// Retourner le nombre de demandes approvisionnées avec succès à des fins de surveillance
return new ManualProvisioningOutcome()
        .setNbOfEntriesProcessed(nbOfSuccessfulRequest)

def createServiceNowIdentity(Identity target) {
    def url = "${serviceNowURL}/api/now/table/sys_user"
    def jsonBuilder = new JsonBuilder()

    jsonBuilder {
        user_name target.email
        email target.email
        first_name target.firstname
        last_name target.lastname
        employee_number "N/A"
        // Personnalisez ces champs en fonction du schéma de votre table utilisateur ServiceNow
        // u_division_code target.organization.name[-4..-1]  // Exemple : 4 derniers caractères du nom de l'organisation
        // title target.job.name
        source "RACM"
    }
    def body = jsonBuilder.toPrettyString()
    def requestBody = RequestBody.create(MediaType.parse("application/json"), body)
    def request = new Request.Builder()
            .header("Authorization", buildAuthHeader())
            .url(url)
            .post(requestBody)
            .build()

    logger.info("Appel de ${request} avec le corps : ${body}...")
    def response = httpClient.newCall(request).execute()
    if(response.code != 201 && response.code != 403) {
        // 201 créé signifie succès
        // 403 signifie que l'utilisateur existe déjà
        throw new Exception("Réponse non réussie reçue lors de la création de l'utilisateur. Code : ${response.code}, Corps : ${response.body}")
    }
}

Response callOrderItem(ProvisioningRequest provisioningRequest) {
    def url = "${serviceNowURL}/api/sn_sc/servicecatalog/items/${serviceNowCatalogItemId}/order_now"
    def jsonBuilder = new JsonBuilder()
    def (cat,subcat) = getCategoryAndSubCategory(provisioningRequest)
    def shortDescription = buildShortDescription(provisioningRequest,null)
    def assignmentGroup = getRequestItemAssignmentGroup(provisioningRequest)
    def openedBy = determineOpenedBy(provisioningRequest)
    def requestedFor = provisioningRequest.target.email
    if(provisioningRequest.action == Action.IDENTITY_TERMINATION) {
        // Pour les résiliations, utiliser openedBy au lieu de target pour éviter d'envoyer des notifications à l'utilisateur qui part
        requestedFor = openedBy
    }
    jsonBuilder {
        sysparm_quantity "1"
        variables {
            requested_for_id requestedFor
            opened_by_id openedBy
            racm_number provisioningRequest.requestId
            racm_url racmURL
            short_description shortDescription
            assignment_group assignmentGroup
            category cat
            subcategory subcat
        }
    }
    def body = jsonBuilder.toPrettyString()
    def requestBody = RequestBody.create(MediaType.parse("application/json"), body)
    def request = new Request.Builder()
            .header("Authorization", buildAuthHeader())
            .url(url)
            .post(requestBody)
            .build()

    logger.info("Appel de ${request} avec le corps : ${body}...")
    return httpClient.newCall(request).execute()
}

def processOrderItemResponse(Response response) {
    def statusCode = response.code()
    def body = response.body().string()
    if (response.isSuccessful()) {
        logger.info("Réponse réussie reçue. Code : ${statusCode}, Corps : ${body}")

        return extractRequestIdAndNumber(body)
    }
    else {
        throw new Exception("Réponse non réussie reçue. Code : ${statusCode}, Corps : ${body}")
    }
}

Response callGetRITM(String requestId) {
    def requestIdURl = "${serviceNowURL}/api/now/table/sc_req_item?sysparm_query=request.sys_id=${requestId}&sysparm_fields=number,sys_id,sys_class_name,request&sysparm_limit=1"
    def request = new Request.Builder()
            .header("Authorization", buildAuthHeader())
            .url(requestIdURl)
            .build()

    logger.info("Appel de ${request}...")
    return httpClient.newCall(request).execute()
}

def processGetRITMResponse(Response response) {
    def statusCode = response.code()
    def body = response.body().string()
    if (response.isSuccessful()) {
        logger.info("Réponse réussie reçue. Code : ${statusCode}, Corps : ${body}")

        // Extraire le sys_id et le numéro RITM
        return extractRITM(body)
    }
    else {
        throw new Exception("Réponse non réussie reçue. Code : ${statusCode}, Corps : ${body}")
    }
}

/**
 * Appeler l'endpoint Create Task pour une demande de compte
 *
 * @param ritmSysId
 * @param accountRequest
 * @param provisioningRequest
 * @return
 */
Response callCreateTask(String ritmSysId, String applicationName, List<AccountRequest> accountRequests, ProvisioningRequest provisioningRequest) {
    def url = "${serviceNowURL}/api/now/table/sc_task"
    def jsonBuilder = new JsonBuilder()
    jsonBuilder {
        request_item ritmSysId
        short_description buildShortDescription(provisioningRequest, applicationName)
        description buildCreateTaskDescription(accountRequests, provisioningRequest)
        assignment_group getTaskAssignmentGroup(applicationName)
    }
    def body = jsonBuilder.toPrettyString()
    def requestBody = RequestBody.create(MediaType.parse("application/json"), body)
    def request = new Request.Builder()
            .header("Authorization", buildAuthHeader())
            .url(url)
            .post(requestBody)
            .build()

    logger.info("Appel de ${request} avec le corps : ${body}...")

    return httpClient.newCall(request).execute()
}

/**
 * Appeler l'endpoint create task pour une liste de demandes écrites
 *
 * @param ritmSysId
 * @param accountRequest
 * @param provisioningRequest
 * @return
 */
Response callCreateTask(String ritmSysId, List<WrittenRequest> writtenRequests, ProvisioningRequest provisioningRequest) {
    def url = "${serviceNowURL}/api/now/table/sc_task"
    def jsonBuilder = new JsonBuilder()
    def assignmentGroup = getRequestItemAssignmentGroup(provisioningRequest)
    jsonBuilder {
        request_item ritmSysId
        short_description buildShortDescription(provisioningRequest,null)
        description buildWrittenTaskDescription(writtenRequests, provisioningRequest)
        assignment_group assignmentGroup
    }
    def body = jsonBuilder.toPrettyString()
    def requestBody = RequestBody.create(MediaType.parse("application/json"), body)
    def request = new Request.Builder()
            .header("Authorization", buildAuthHeader())
            .url(url)
            .post(requestBody)
            .build()

    logger.info("Appel de ${request} avec le corps : ${body}...")

    return httpClient.newCall(request).execute()
}

void processCreateTaskResponse(Response response) {
    def statusCode = response.code()
    def body = response.body().string()
    if (response.isSuccessful()) {
        logger.info("Réponse réussie reçue. Code : ${statusCode}, Corps : ${body}")
    }
    else {
        throw new Exception("Réponse non réussie reçue. Code : ${statusCode}, Corps : ${body}")
    }
}

String extractRequestIdAndNumber(String orderItemResponseBody) {

    def json = new JsonSlurper().parseText(orderItemResponseBody)
    return json.result.request_id
}

def extractRITM(String responseBody) {
    def json = new JsonSlurper().parseText(responseBody)
    return [json.result[0].sys_id, json.result[0].number]
}

def buildTicketURL(String ritmSysID) {
    return "${serviceNowURL}/sp?id=ticket&table=sc_req_item&sys_id=${ritmSysID}"
}

def buildAuthHeader() {
    def auth = "${serviceNowUser}:${serviceNowPassword}"
    def encodedString = Base64.getEncoder().encodeToString(auth.getBytes())
    return "Basic " + encodedString
}

/**
 * Construire la description courte pour les tâches ServiceNow
 *
 * @param provisioningRequest
 * @param applicationName
 */
def buildShortDescription(ProvisioningRequest provisioningRequest, String applicationName) {
    def shortDescription = ""
    def origin = provisioningRequest.origin
    def action = provisioningRequest.action
    def targetName = provisioningRequest.hasTarget() ? provisioningRequest.target?.fullName : "Générique"
    def appSuffix = applicationName != null ? " - ${applicationName}" : ""
    def requestIdSuffix = provisioningRequest.hasARequestId() ? " - ${provisioningRequest.requestId}" : ""

    switch (action) {
        case Action.ADD_ACCESSES_REQUEST:
            shortDescription = "Mise à jour - ${targetName}${appSuffix}${requestIdSuffix}"
            break
        case Action.REMOVE_ACCESSES_REQUEST:
            if (origin == Origin.CAMPAIGN) {
                shortDescription = "Révocation de campagne - ${targetName}${appSuffix}${requestIdSuffix}"
            } else {
                shortDescription = "Mise à jour - ${targetName}${appSuffix}${requestIdSuffix}"
            }
            break
        case Action.IDENTITY_CREATION:
            shortDescription = "Nouveau - ${targetName}${appSuffix}${requestIdSuffix}"
            break
        case Action.IDENTITY_TRANSFER:
            shortDescription = "Transfert - ${targetName}${appSuffix}${requestIdSuffix}"
            break
        case Action.IDENTITY_TERMINATION:
            if (origin == Origin.CAMPAIGN) {
                shortDescription = "Départ de campagne - ${targetName}${appSuffix}${requestIdSuffix}"
            } else {
                shortDescription = "Départ - ${targetName}${appSuffix}${requestIdSuffix}"
            }
            break
        case Action.UNDEFINED:
            shortDescription = "Non défini"
            break
    }
    logger.info("Description courte de la tâche est ${shortDescription}")

    return shortDescription
}

/**
 * Construire la description de tâche pour une demande de compte
 * Doit correspondre au modèle d'email de ticket réel (manualProvisioning_ticketingSystem.html)
 *
 * @param accountRequest
 * @param provisioningRequest
 * @return
 */
def buildCreateTaskDescription(List<AccountRequest> accountRequests, ProvisioningRequest provisioningRequest) {

    def target = provisioningRequest.target
    def requester = provisioningRequest.requester
    def taskDescription = """\
        ID de demande : ${provisioningRequest.id}
        ${provisioningRequest.hasARequestId() ? "Numéro de demande : ${provisioningRequest.requestId}" : ""}
        ${provisioningRequest.hasRequester() ? "Demandeur : ${requester.fullName} (${requester.email})" : "Demandeur : [Généré automatiquement selon les politiques]"}
        Utilisateur cible :

            ${target.fullName} ${utils.isNotEmpty(target.email) ? "(${target.email})" : ""}${utils.isEmpty(target.identifier4) ? "" : """
            ${target.identifier4}"""}${utils.isUndefined(target.organization) ? "" : """
            Organisation : ${target.organization.name}"""}

        Accès à accorder :
        """.stripIndent()

    accountRequests.each { accountRequest -> taskDescription += """\
        - ${getAccountRequestTypeLabel(accountRequest.type)} - ${accountRequest.applicationName}\
        ${utils.isEmpty(accountRequest.description) ? "" : """
        - ${accountRequest.description}"""}
            ${(accountRequest.groupRequests.collect { groupRequest -> buildGroupRequest(groupRequest) }).join("            \n")}
        ----------------------------------------------------------------------
    """.stripIndent()
    }
    logger.info("Description de la tâche est\n${taskDescription}")

    return taskDescription
}

/**
 * Construction de groupe extraite dans une méthode séparée pour permettre la division des groupes si la limite de taille de description serviceNow est atteinte
 *
 * @param groupRequest
 * @return
 */
String buildGroupRequest(GroupRequest groupRequest) {
    def groupRequestInfo = "    - ${getGroupRequestTypeLabel(groupRequest.type)} - ${groupRequest.group.name}"
    if (groupRequest.type == GroupRequest.Type.ASSIGNMENT) {
        if (groupRequest.endDate != null) {
            groupRequestInfo += "\n         Date de fin : " + utils.formatDate(groupRequest.endDate)
        }
        if (groupRequest.group.hasProvisioningProcedureWhenAdding) {
            groupRequestInfo += "\n         Instructions spéciales pour l'assignation : ${groupRequest.group.provisioningProcedureWhenAdding}"
        }
    }
    else if (groupRequest.type == GroupRequest.Type.UNASSIGNMENT){
        if (groupRequest.group.hasProvisioningProcedureWhenRemoving) {
            groupRequestInfo += "\n         Instructions spéciales pour la suppression : ${groupRequest.group.provisioningProcedureWhenRemoving}"
        }
    } else if (groupRequest.type == GroupRequest.Type.PROVISIONING_PROCEDURE_FOR_ASSIGNMENT) {
        if (groupRequest.endDate != null) {
            groupRequestInfo += "\n         Date de fin : " + utils.formatDate(groupRequest.endDate)
        }
        groupRequestInfo += "\n         ${groupRequest.group.provisioningProcedureWhenAdding}"
    }
    else if (groupRequest.type == GroupRequest.Type.PROVISIONING_PROCEDURE_FOR_UNASSIGNMENT){
        groupRequestInfo += "\n         ${groupRequest.group.provisioningProcedureWhenRemoving}"
    }
    return groupRequestInfo
}

def buildWrittenTaskDescription(List<WrittenRequest> writtenRequests, ProvisioningRequest provisioningRequest) {
    def target = provisioningRequest.target
    def requester = provisioningRequest.requester
    def taskDescription = """${provisioningRequest.hasRequester() ? """Demandé par : ${requester.fullName} (${requester.email})""" : "Demandé par : [Demande générée automatiquement]"}
${provisioningRequest.hasTarget() ? """
Utilisateur cible :
    
    ${target.fullName} ${utils.isNotEmpty(target.email) ? "(${target.email})" : ""}${utils.isEmpty(target.identifier4) ? "" : """
    ${target.identifier4}"""} ${utils.isUndefined(target.organization) ? "" : """
    Organisation : ${target.organization.name}"""}""" : """
Cette demande concerne un compte technique"""}

Demandes écrites à accorder :
${(writtenRequests.collect { writtenRequest -> buildWrittenRequest(writtenRequest) }).join("\n")}    
"""
    logger.info("Description de la tâche est\n ${taskDescription}")

    return taskDescription
}

def buildWrittenRequest(WrittenRequest writtenRequest) {
    return "    - Demande : ${writtenRequest.written}${writtenRequest.startDate != null ? ", à exécuter le : ${utils.formatDate(writtenRequest.startDate)}" : ""}${writtenRequest.endDate != null ? ", expiration : ${utils.formatDate(writtenRequest.endDate)}" : ""}"
}

def getCategoryAndSubCategory(ProvisioningRequest provisioningRequest) {
    if(provisioningRequest.origin == Origin.CAMPAIGN) {
        if(provisioningRequest.action == Action.IDENTITY_TERMINATION) {
            return ["campaign","remove_identity"]
        } else {
            return ["campaign","remove_access"]
        }
    } else {
        switch (provisioningRequest.action) {
            case Action.IDENTITY_CREATION:
                return ["add", "identity"]
            case Action.IDENTITY_TERMINATION:
                return ["terminaison", "remove_identity"]
            case Action.IDENTITY_TRANSFER:
                return ["transfer", "NA"]
            case Action.ADD_ACCESSES_REQUEST:
                return ["add", "access"]
            case Action.REMOVE_ACCESSES_REQUEST:
                return ["terminaison", "access"]
        }
    }

    logger.error("Impossible de déterminer la catégorie pour la demande ${provisioningRequest.requestIdIfPresent}")
    return ["NA","NA"]
}

def getAccountRequestTypeLabel(AccountRequest.Type type) {
    switch (type) {
        case AccountRequest.Type.CREATION:
            return "Création"
            break
        case AccountRequest.Type.MODIFICATION:
            return "Modification"
            break
        case AccountRequest.Type.TERMINATION:
            return "Résiliation"
            break
        case AccountRequest.Type.DELETION:
            return "Suppression"
            break
    }
}

def getGroupRequestTypeLabel(GroupRequest.Type type) {
    switch (type) {
        case GroupRequest.Type.ASSIGNMENT:
            return "Assignation"
            break
        case GroupRequest.Type.UNASSIGNMENT:
            return "Désassignation"
            break
        case GroupRequest.Type.PROVISIONING_PROCEDURE_FOR_ASSIGNMENT:
            return "Instructions supplémentaires pour l'assignation"
            break
        case GroupRequest.Type.PROVISIONING_PROCEDURE_FOR_UNASSIGNMENT:
            return "Instructions supplémentaires pour la désassignation"
            break
    }
}

def determineOpenedBy(ProvisioningRequest provisioningRequest)
{
    if(provisioningRequest.requester.id == 0) {
        return defaultSystemRequesterEmail
    }
    else {
        return provisioningRequest?.requester?.email ?: "[Aucun demandeur]"
    }
}