Appliquer de la gouvernance sur la création de groupes Microsoft Teams avec Flow, Forms et Microsoft Graph
Par défaut dans Office 365 tous les utilisateurs ont la possibilité de créer une équipe Teams, ce qui est à la fois fort pratique pour ne pas brider la productivité des collaborateurs et source d’angoisse pour les personnes en charge de la gouvernance de l’information dans l’organisation. Dans cet article je vais vous expliquer comment bloquer la création en libre-service et mettre en place un workflow de demande de création d’équipe Teams en utilisant les outils natifs à disposition.
⚠ IMPORTANT : A l’heure où j’écris ces lignes (Octobre 2019) certaines des fonctions du Microsoft Graph que j’utilise sont encore en preview (bêta). Il est fortement déconseillé de les implémenter dans un environnement de production. Cet article apour objectif d’illustrer un cas d’usage avec les fonctions à venir.
Prérequis
L’utilisateur qui va créer et administrer le flow aura besoin des éléments suivants :
- Une licence “Microsoft Teams”, elle est incluse dans les licences de types Office 365 E1, E3, E5, Business Essential/Premium, etc…
- Une licences “Microsoft Flow Premium Plan 1” pour utiliser le connecteur “HTTP Request”. Attention, cette licence n’est pas incluse dans les licences de base Office 365 E1, E3, E5, Business Essential/Premium, etc…
- Pouvoir déclarer une application sur le tenant et déléguer certains droits sur l’API Graph (voir plus loin)
Overview du processus souhaité
Je vais décrire un scénario volontairement simple dans le cadre de cet article : une validation à une étape et l’application de quelques options de paramétrage.

Lors de la création de l’équipe je vais inclure certaines options comme le fait de créer une équipe publique ou privée et laisser l’option d’autoriser ou non l’ajout d’externes (Guests).
La création de la demande
Comme point de départ je vais prendre un formulaire Microsoft Forms. Rien de bien sorcier, quelques champs obligatoires pour collecter les informations nécessaires à la demande. Cf. capture.

Créer une identité applicative
Pour interagir avec le Microsoft Graph nous allons avoir besoin d’une identité applicative et lui déléguer le droit d’appeler les méthodes du Graph dont nous aurons besoin.
Déclarer l’application
Rendez-vous dans le portail AzureAD à l’adresse suivante : https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade

Ajoutez une nouvelle application via le bouton “+ New Registration”.

Donnez un nom, choisissez “… organizational directory only…” puis valider avec le bouton “Register”.
Déléguer les droits à l’application
Navigez vers https://portal.azure.com/#blade/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/AllApps/menuId/

Rechercher l’application que vous venez de créer puis ouvrez là.

Dans l’onglet “Overview” notez le “Application (client) ID” et le “Directory (tenant) ID” dans un coin pour plus tard.

Dans la partie “API permissions” ajoutez les permissions suivantes :
- Directoy.Read.All
- Directory.ReadWrite.All
- Group.Read.All
- Group.ReadWrite.All
- User.Read.All

Pour valider la délégation utilisez le bouton “Grant admin consent…”. ⚠IMPORTANT : Il sera demandé de se connecter avec un compte ayant les droits suffisant pour compléter cette étape.
L’application à maintenant les droits pour utiliser le Graph.
Récupérer le Client & Secret
Il nous reste à générer des clés d’accès pour cette application.

Toujours sur l’écran de l’application, dans la sections “Certificates & secrets” générez une clé secrete et notez la dans un coin.
Traiter la demande avec Microsoft Flow
Rendez-vous dans Microsoft Flow : https://flow.microsoft.com/

Créer un nouveau Flow en partant du model vierge.

Donner un nom et sélectionnez le déclencheur “Forms” : “When a new response is sumitted”.
Le déclencheur
La première étape consiste à écouter les soumissions de notre formulaire. Choisir l’événement “Forms” correspondant et l’attacher au formulaire de demande. Le Flow sera maintenant déclenché à chaque fois qu’une personne soumet le formulaire.

Les variables
Pour des raisons de commodité je vais utiliser des variables pour stocker les informations de l’application que j’ai créée plus haut, je les utiliserais dans les étapes suivantes. J’utilise des actions du type “Initialize variable”.

Récupérer les valeurs saisies dans Forms
Je vais maintenant avoir besoin de récupérer les valeurs saisies dans la demande Forms. Cette routine est assez classique, j’ajoute une action “Get response details” qui génère les actions imbriquées suivantes, il reste à fournir quelques valeurs cf. capture.

❗ NOTE : Toutes les actions qui suivent devront être dans l’action “Apply to each” pour avoir accès aux valeurs du formulaire.
Demander l’approbation
J‘utilise une action “Get user profile” pour récupérer le nom du demandeur pour pouvoir l’insérer dans le mail d’approbation.
La demande d’approbation est faite avec l’action “Start and wait for an approval”. Dans mon exemple j’ai choisi de mettre en dur l’adresse de mon approbateur (Assigned to), mais on peut très bien imaginer une suite d’action Flow pour calculer cette valeur (manager direct, responsable de service…).

Pour traiter la réponse on pose une action “Condition” avec comme assertion “Outcome = Approve”.

Envoyer un mail pour notifier le refus
Dans la branche “If no” j’ajoute une action “Send an email notification” pour avertir le demandeur du refus.

Récupérer l‘Access Token OAuth
Dans la branche “if yes”, avant de pouvoir requêter le Microsoft Graph nous allons d’abord récupérer un token d’accès pour l’application que nous avons créée plus haut. Pour cela j’utilise une action de type “HTTP Request” dont voici le paramétrage.

Documentation : https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
Peek Code :
{
“inputs”: {
“method”: “POST”,
“uri”: “https://login.microsoftonline.com/@{variables('TenantId')}/oauth2/v2.0/token",
“headers”: {
“Content-Type”: “application/x-www-form-urlencoded”
},
“body”: “client_id=@{variables(‘ClientId’)}&client_secret=@{variables(‘ClientSecret’)}&grant_type=client_credentials&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default”
}}
Pour lire la réponse de la requête je vais “Parser” le résultat à l’aide d’une action du type “Parse JSON” en lui donnant le schéma attendu pour que Flow comprenne comment lire les valeurs. Il y a de nombreuses valeurs dans la réponse mais je ne m’intéresse ici qu’à “access_token” qui va nous servir ensuite à dialoguer avec Microsoft Graph.

Schema :
{
“type”: “object”,
“properties”: {
“access_token”: {
“type”: “string”
}}}
Créer l’équipe Teams
Nous voici à l’étape de création de l’équipe. Notez qu’à ce stade on n’utilise que le nom et la description, et la visibilité “Privé” par défaut, les autres fonctions seront appliquées plus tard avec une autre API. J’utilise l’Id du demandeur pour définir le propriétaire de l’équipe.

Peek Code :
{
“inputs”: {
“method”: “POST”,
“uri”: “https://graph.microsoft.com/beta/teams",
“headers”: {
“Content-Type”: “application/json”,
“Authorization”: “Bearer @{body(‘Parse_Access_Token’)?[‘access_token’]}”
},
“body”: {
“template@odata.bind”: “https://graph.microsoft.com/beta/teamsTemplates('standard')",
“displayName”: “@{body(‘Get_response_details’)?[‘re57b9f0894524ef3b6b3620c30f82dfd’]}”,
“description”: “@{body(‘Get_response_details’)?[‘rff93071e70434b54920de2f7452dc07a’]}”,
“visibility”: “Private”,
“owners@odata.bind”: [
“https://graph.microsoft.com/beta/users('@{body('Get_user_profile_(V2)')?['id']}')"
]}}}
Documentation : https://docs.microsoft.com/en-us/graph/api/team-post?view=graph-rest-beta
Je récupère ensuite l’id du groupe fraîchement créé en “Parsant” la réponse. L’id se trouve dans “Location” au format “/teams(‘<groud id>’)/operations(‘<autre guid>’)”, il faudra le formater pour extraire l’id qui nous intéresse.

Voici l’expression Flow peu élégante pour extraire l’id :
substring(replace(body(‘Parse_Location’)?[‘Location’],’/teams(‘’’,’’),0,indexOf(replace(body(‘Parse_Location’)?[‘Location’],’/teams(‘’’,’’),’’’’))
J’ajoute ensuite un délai de 60 secondes pour laisser le temps au back office de finir de provisionner correctement notre groupe.

Définir le niveau de visibilité
Dans l’étape de création, le groupe a été créé avec la visibilité “privé” pour suivre une approche “least privilege strategy” en cas d’échec de changement cette valeur dans la suite du flow. Le passage dans cette étape ne se fait donc que si l’utilisateur demande que le groupe soit publique.
Notre première action est donc un test pour le cas où l’on souhaite un groupe “publique” en utilisant la valeur saisie du formulaire.

Dans la branche “If no” je ne met rien.
Dans la branche “If yes” je configure un nouvel appel HTTP, notez que “Outputs” correspond à notre Group ID.

Peek Code :
{
“inputs”: {
“method”: “PATCH”,
“uri”: “https://graph.microsoft.com/v1.0/groups/@{outputs('Format_GroupId')}",
“headers”: {
“Authorization”: “Bearer @{body(‘Parse_Access_Token’)?[‘access_token’]}”,
“Content-type”: “application/json”
},
“body”: {
“visibility”: “Public”
}}}
Documentation : https://docs.microsoft.com/en-us/graph/api/group-update?view=graph-rest-1.0&tabs=http
Définir l’autorisation d’inviter des d’externes
Idem que pour le chapitre précédent on commence par poser une condition selon la saisie du formulaire.

Dans mes deux branches j’appelle la même API de gestion des options du groupe et passe la valeur “True” ou “False” selon le choix de l’utilisateur.

Peek Code (branche “If yes”) :
{
“inputs”: {
“method”: “POST”,
“uri”: “https://graph.microsoft.com/v1.0/groups/@{outputs('Format_GroupId')}/settings",
“headers”: {
“Authorization”: “Bearer @{body(‘Parse_Access_Token’)?[‘access_token’]}”,
“Content-type”: “application/json”
},
“body”: {
“displayName”: “Group.Unified.Guest”,
“templateId”: “08d542b9–071f-4e16–94b0–74abb372e3d9”,
“values”: [{
“name”: “AllowToAddGuests”,
“value”: “True”
}]
}}}
Documentation : https://docs.microsoft.com/en-us/graph/api/groupsetting-post-groupsettings?view=graph-rest-1.0&tabs=http
Notifier le demandeur du succès
Puis finalement on peut notifier le demandeur du succès de sa demande et de la création.

Bloquer la création d’équipe Teams en libre service sur le tenant
⚠ Avant toute chose il faut savoir que chaque équipe Teams est techniquement un groupe Office 365. La seule option (à ma connaissance) pour bloquer la création d’équipe Teams consiste à bloquer la création de groupes Office 365. A l’heure où j’écris ces lignes il n’est pas techniquement possible d’appliquer cette limitation uniquement pour Teams, ce qui veut dire que changer ce paramètre va également bloquer la création de groupes depuis les workloads suivants :
- Outlook
- SharePoint
- Yammer
- Microsoft Teams
- StaffHub
- Planner
- PowerBI
- Roadmap
Nous allons limiter la création de groupes Office 365 aux seuls membres d’un groupe de sécurité AD et aux administrateurs des services. Pour effectuer ce changement il faudra s’armer de PowerShell et de la version preview du module AzureAD (A l’heure où j’écris ces lignes, oct. 2019, la version GA ne contient pas encore les commandes que nous allons utiliser). J’ai pour ma part fait ces changements avec la version 2.0.2.53 en preview du module AzureADPreview.

On commence par créer un groupe de sécurité dans AzureAD : https://aad.portal.azure.com/#blade/Microsoft_AAD_IAM/GroupsManagementMenuBlade/AllGroups
J’ai choisi nommer le mien “AllowGroupCreation” :

Depuis une console PowerShell lancez le script suivant (en modifiant préalablement le GroupName) :
$GroupName = “AllowGroupCreation”
$AllowGroupCreation = “False”Connect-AzureAD
$settingsObjectID = (Get-AzureADDirectorySetting | Where-object -Property Displayname -Value “Group.Unified” -EQ).id
if(!$settingsObjectID)
{
$template = Get-AzureADDirectorySettingTemplate | Where-object {$_.displayname -eq “group.unified”}
$settingsCopy = $template.CreateDirectorySetting()
New-AzureADDirectorySetting -DirectorySetting $settingsCopy
$settingsObjectID = (Get-AzureADDirectorySetting | Where-object -Property Displayname -Value “Group.Unified” -EQ).id
}$settingsCopy = Get-AzureADDirectorySetting -Id $settingsObjectID
$settingsCopy[“EnableGroupCreation”] = $AllowGroupCreationif($GroupName)
{
$settingsCopy[“GroupCreationAllowedGroupId”] = (Get-AzureADGroup -SearchString $GroupName).objectid
}Set-AzureADDirectorySetting -Id $settingsObjectID -DirectorySetting $settingsCopy
(Get-AzureADDirectorySetting -Id $settingsObjectID).Values

En sortie “EnableGroupCreation” sera à “False” et “GroupCreationAllowedGroupId” correspond à l’id du groupe de sécurité autorisé à créer des groupes Office 365.

Maintenant en se connectant à teams avec un utilisateur qui ne fait pas partie de ce groupe de sécurité, on ne vois plus le bouton de création d’équipe :

Documentation : https://docs.microsoft.com/en-us/office365/admin/create-groups/manage-creation-of-groups?view=o365-worldwide
Conclusion
Nous avons maintenant bloqué la création de groupes Office 365 pour tous les utilisateurs “ordinaire” du tenant et mis à disposition un formulaire de demande de création d’équipe teams. Voici comment se déroule l’expérience utilisateur :
Création de la demande
L’utilisateur créé une demande depuis Forms.

Demande d’approbation
Voici à quoi ressemble la demande d’approbation reçu par mail :

L’approbateur peut saisir son commentaire directement depuis le mail :

Le corp de mail une fois approuvé :

Le mail de confirmation
Le contenu du mail de confirmation envoyé au demandeur :

L’équipe Teams
Le groupe fraîchement créé avec la visibilité demandé.

La gestion des invités externes à l’organisation (Guests)
Si j’ouvre le site SharePoint de l’équipe et cherche à ajouter un membre, le sélecteur me proposera uniquement les utilisateurs internes à mon organisation :

A titre de comparaison voici le même menu lorsque invitation de “Guests” est autorisé :

Ressources
Cet article m’a été inspiré par ce webcast : https://techcommunity.microsoft.com/t5/Healthcare-and-Life-Sciences/Automating-Selective-Guest-Access-in-Microsoft-Teams/ba-p/698138