From 7eb45eecec15882638a0cf1346c1154e3823e322 Mon Sep 17 00:00:00 2001
From: Damien <damien.burel@maarch.org>
Date: Fri, 6 Dec 2019 16:51:20 +0100
Subject: [PATCH] FEAT #8789 TIME 0:30 M2M Annuaries

---
 .../xml/m2m_config.xml.default                |  37 ++
 rest/index.php                                |   1 +
 sql/data_en.sql                               |   1 +
 sql/data_fr.sql                               |   2 +
 .../entity/controllers/EntityController.php   |  32 +-
 .../controllers/AnnuaryController.php         | 369 ++++++++++++++++++
 src/core/lang/lang-en.php                     |   6 +
 src/core/lang/lang-fr.php                     |   6 +
 src/core/lang/lang-nl.php                     |   6 +
 .../entities-administration.component.html    |  13 +-
 .../entities-administration.component.ts      |  34 +-
 11 files changed, 495 insertions(+), 12 deletions(-)
 create mode 100644 apps/maarch_entreprise/xml/m2m_config.xml.default
 create mode 100644 src/app/external/messageExchange/controllers/AnnuaryController.php

diff --git a/apps/maarch_entreprise/xml/m2m_config.xml.default b/apps/maarch_entreprise/xml/m2m_config.xml.default
new file mode 100644
index 00000000000..49e8643ed83
--- /dev/null
+++ b/apps/maarch_entreprise/xml/m2m_config.xml.default
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ROOT>
+    <res_letterbox>
+        <type_id>101</type_id>
+        <status>NUMQUAL</status>
+        <priority>poiuytre1357nbvc</priority>
+    </res_letterbox>
+    <res_attachments>
+        <attachment_type>simple_attachment</attachment_type>
+    </res_attachments>
+    <contacts_v2>
+        <contact_type>100</contact_type>
+    </contacts_v2>
+    <contact_addresses>
+        <contact_purpose_id>1</contact_purpose_id>
+    </contact_addresses>
+    <basketRedirection_afterUpload>NumericBasket</basketRedirection_afterUpload><!--basketId-->
+    <m2m_communication>https://cchaplin:maarch@demo.maarchcourrier.com</m2m_communication><!--moyen de communication de l'instance : email ou uri -->
+    <annuaries>
+        <enabled>false</enabled>
+        <organization>organization</organization>
+        <annuary>
+            <uri>1.1.1.1</uri>
+            <baseDN>base</baseDN>
+            <login>Administrateur</login>
+            <password>ThePassword</password>
+            <ssl>false</ssl>
+        </annuary>
+        <annuary>
+            <uri>2.2.2.2</uri>
+            <baseDN>base</baseDN>
+            <login>Administrateur</login>
+            <password>ThePassword</password>
+            <ssl>false</ssl>
+        </annuary>
+    </annuaries>
+</ROOT>
diff --git a/rest/index.php b/rest/index.php
index 6c92c578a01..b788511537f 100755
--- a/rest/index.php
+++ b/rest/index.php
@@ -186,6 +186,7 @@ $app->get('/entities/{id}/details', \Entity\controllers\EntityController::class
 $app->get('/entities/{id}/users', \Entity\controllers\EntityController::class . ':getUsersById');
 $app->put('/entities/{id}/reassign/{newEntityId}', \Entity\controllers\EntityController::class . ':reassignEntity');
 $app->put('/entities/{id}/status', \Entity\controllers\EntityController::class . ':updateStatus');
+$app->put('/entities/{id}/annuaries', \MessageExchange\controllers\AnnuaryController::class . ':updateEntityToOrganization');
 $app->get('/entityTypes', \Entity\controllers\EntityController::class . ':getTypes');
 $app->post('/entitySeparators', \Entity\controllers\EntitySeparatorController::class . ':create');
 
diff --git a/sql/data_en.sql b/sql/data_en.sql
index 0b4d8c0fe79..f9f5be28566 100644
--- a/sql/data_en.sql
+++ b/sql/data_en.sql
@@ -1579,6 +1579,7 @@ INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_val
 INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (4, 'initiator', TRUE, '""', 'process');
 INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (4, 'destination', TRUE, '""', 'process');
 
+INSERT INTO parameters (id, description, param_value_string) VALUES ('siret', 'SIRET company number', '45239273100025');
 
 --Inscrire ici les clauses de conversion spécifiques en cas de reprise
 --Update res_letterbox set status='VAL' where res_id=108;
diff --git a/sql/data_fr.sql b/sql/data_fr.sql
index cd03c39410d..8342b1aca8c 100755
--- a/sql/data_fr.sql
+++ b/sql/data_fr.sql
@@ -1688,3 +1688,5 @@ INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_val
 INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (4, 'getRecipients', FALSE, null, 'contact');
 INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (4, 'initiator', TRUE, null, 'process');
 INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (4, 'destination', TRUE, null, 'process');
+
+INSERT INTO parameters (id, description, param_value_string) VALUES ('siret', 'Numéro SIRET de l''entreprise', '45239273100025');
diff --git a/src/app/entity/controllers/EntityController.php b/src/app/entity/controllers/EntityController.php
index f68bc21af2e..a53d3684063 100755
--- a/src/app/entity/controllers/EntityController.php
+++ b/src/app/entity/controllers/EntityController.php
@@ -21,6 +21,8 @@ use Entity\models\ListTemplateModel;
 use Group\controllers\PrivilegeController;
 use Group\models\GroupModel;
 use History\controllers\HistoryController;
+use MessageExchange\controllers\AnnuaryController;
+use Parameter\models\ParameterModel;
 use Resource\models\ResModel;
 use Respect\Validation\Validator;
 use Slim\Http\Request;
@@ -158,6 +160,8 @@ class EntityController
         $entity['redirects'] = count($redirects);
         $entity['canAdminUsers'] = PrivilegeController::hasPrivilege(['privilegeId' => 'admin_users', 'userId' => $GLOBALS['id']]);
         $entity['canAdminTemplates'] = PrivilegeController::hasPrivilege(['privilegeId' => 'admin_templates', 'userId' => $GLOBALS['id']]);
+        $siret = ParameterModel::getById(['id' => 'siret', 'select' => ['param_value_string']]);
+        $entity['canSynchronizeSiret'] = !empty($siret['param_value_string']);
 
         return $response->withJson(['entity' => $entity]);
     }
@@ -301,7 +305,7 @@ class EntityController
             return $response->withStatus(403)->withJson(['errors' => 'Service forbidden']);
         }
 
-        $entity = EntityModel::getByEntityId(['entityId' => $aArgs['id'], 'select' => ['id']]);
+        $entity = EntityModel::getByEntityId(['entityId' => $aArgs['id'], 'select' => ['id', 'business_id']]);
         if (empty($entity)) {
             return $response->withStatus(400)->withJson(['errors' => 'Entity not found']);
         }
@@ -325,6 +329,15 @@ class EntityController
             return $response->withStatus(400)->withJson(['errors' => 'Entity is still used']);
         }
 
+        $entities = [];
+        if (!empty($entity['business_id'])) {
+            $control = AnnuaryController::deleteEntityToOrganization(['entityId' => $aArgs['id']]);
+            if (!empty($control['errors'])) {
+                return $response->withStatus(400)->withJson(['errors' => $control['errors']]);
+            }
+            $entities['deleted'] = $control['deleted'];
+        }
+
         ListTemplateModel::delete(['where' => ['object_id = ?'], 'data' => [$aArgs['id']]]);
         GroupModel::update([
             'postSet'   => ['indexation_parameters' => "jsonb_set(indexation_parameters, '{entities}', (indexation_parameters->'entities') - '{$entity['id']}')"],
@@ -342,7 +355,8 @@ class EntityController
             'eventId'   => 'entitySuppression',
         ]);
 
-        return $response->withJson(['entities' => EntityModel::getAllowedEntitiesByUserId(['userId' => $GLOBALS['userId']])]);
+        $entities['entities'] = EntityModel::getAllowedEntitiesByUserId(['userId' => $GLOBALS['userId']]);
+        return $response->withJson($entities);
     }
 
     public function reassignEntity(Request $request, Response $response, array $aArgs)
@@ -351,7 +365,7 @@ class EntityController
             return $response->withStatus(403)->withJson(['errors' => 'Service forbidden']);
         }
 
-        $dyingEntity = EntityModel::getByEntityId(['entityId' => $aArgs['id'], 'select' => ['parent_entity_id', 'id']]);
+        $dyingEntity = EntityModel::getByEntityId(['entityId' => $aArgs['id'], 'select' => ['id', 'parent_entity_id', 'business_id']]);
         $successorEntity = EntityModel::getByEntityId(['entityId' => $aArgs['newEntityId'], 'select' => [1]]);
         if (empty($dyingEntity) || empty($successorEntity)) {
             return $response->withStatus(400)->withJson(['errors' => 'Entity does not exist']);
@@ -363,6 +377,15 @@ class EntityController
             }
         }
 
+        $entities = [];
+        if (!empty($dyingEntity['business_id'])) {
+            $control = AnnuaryController::deleteEntityToOrganization(['entityId' => $aArgs['id']]);
+            if (!empty($control['errors'])) {
+                return $response->withStatus(400)->withJson(['errors' => $control['errors']]);
+            }
+            $entities['deleted'] = $control['deleted'];
+        }
+
         //Documents
         ResModel::update(['set' => ['destination' => $aArgs['newEntityId']], 'where' => ['destination = ?', 'status != ?'], 'data' => [$aArgs['id'], 'DEL']]);
 
@@ -422,7 +445,8 @@ class EntityController
             'eventId'   => 'entitySuppression',
         ]);
 
-        return $response->withJson(['entities' => EntityModel::getAllowedEntitiesByUserId(['userId' => $GLOBALS['userId']])]);
+        $entities['entities'] = EntityModel::getAllowedEntitiesByUserId(['userId' => $GLOBALS['userId']]);
+        return $response->withJson($entities);
     }
 
     public function updateStatus(Request $request, Response $response, array $aArgs)
diff --git a/src/app/external/messageExchange/controllers/AnnuaryController.php b/src/app/external/messageExchange/controllers/AnnuaryController.php
new file mode 100644
index 00000000000..7867b2de3b9
--- /dev/null
+++ b/src/app/external/messageExchange/controllers/AnnuaryController.php
@@ -0,0 +1,369 @@
+<?php
+
+/**
+* Copyright Maarch since 2008 under licence GPLv3.
+* See LICENCE.txt file at the root folder for more details.
+* This file is part of Maarch software.
+*
+*/
+
+/**
+* @brief Annuary Controller
+* @author dev@maarch.org
+*/
+
+namespace MessageExchange\controllers;
+
+use Entity\models\EntityModel;
+use Group\controllers\PrivilegeController;
+use Parameter\models\ParameterModel;
+use Slim\Http\Request;
+use Slim\Http\Response;
+use SrcCore\models\CoreConfigModel;
+
+class AnnuaryController
+{
+    public static function updateEntityToOrganization(Request $request, Response $response, array $args)
+    {
+        if (!PrivilegeController::hasPrivilege(['privilegeId' => 'manage_entities', 'userId' => $GLOBALS['id']])) {
+            return $response->withStatus(403)->withJson(['errors' => 'Service forbidden']);
+        }
+
+        $entity = EntityModel::getById(['id' => $args['id'], 'select' => ['entity_id', 'entity_label']]);
+        if (empty($entity)) {
+            return $response->withStatus(400)->withJson(['errors' => 'Entity does not exist']);
+        }
+
+        $siret = ParameterModel::getById(['id' => 'siret', 'select' => ['param_value_string']]);
+        if (empty($siret['param_value_string'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Parameter siret does not exist', 'lang' => 'invalidToken']);
+        }
+
+        $entitySiret = "{$siret['param_value_string']}/{$entity['entity_id']}";
+
+        EntityModel::update(['set' => ['business_id' => $entitySiret], 'where' => ['id = ?'], 'data' => [$args['id']]]);
+
+        $control = AnnuaryController::getAnnuaries();
+        if (empty($control['annuaries'])) {
+            if (isset($control['errors'])) {
+                return $response->withStatus(400)->withJson(['errors' => $control['errors']]);
+            } else {
+                return $response->withJson(['entitySiret' => $entitySiret]);
+            }
+        }
+        $organization = $control['organization'];
+        $communicationMeans = $control['communicationMeans'];
+        $annuaries = $control['annuaries'];
+
+        foreach ($annuaries as $annuary) {
+            $ldap = @ldap_connect($annuary['uri']);
+            if ($ldap === false) {
+                continue;
+            }
+            ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
+            ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
+            ldap_set_option($ldap, LDAP_OPT_NETWORK_TIMEOUT, 5);
+
+            $search = @ldap_search($ldap, "{$annuary['baseDN']}", "(ou={$organization})", ['dn']);
+            if ($search === false) {
+                continue;
+            }
+
+            $authenticated = @ldap_bind($ldap, $annuary['login'], $annuary['password']);
+            if (!$authenticated) {
+                return $response->withStatus(400)->withJson(['errors' => 'Ldap authentication failed : ' . ldap_error($ldap)]);
+            }
+
+            $entries = ldap_get_entries($ldap, $search);
+            if ($entries['count'] == 0) {
+                $info = [];
+                $info['ou'] = $organization;
+                $info['destinationIndicator'] = $siret['param_value_string'];
+                $info['objectclass'] = ['organizationalUnit', 'top', 'labeledURIObject'];
+                if (!empty($communicationMeans['url'])) {
+                    $info['labeledURI'] = rtrim($communicationMeans['url'], '/');
+                }
+                if (!empty($communicationMeans['email'])) {
+                    $info['postOfficeBox'] = $communicationMeans['email'];
+                }
+
+                $added = @ldap_add($ldap, "ou={$organization},{$annuary['baseDN']}", $info);
+                if (!$added) {
+                    return $response->withStatus(400)->withJson(['errors' => 'Ldap add failed : ' . ldap_error($ldap)]);
+                }
+            }
+
+            $search = @ldap_search($ldap, "ou={$organization},{$annuary['baseDN']}", "(initials={$entity['entity_id']})", ['dn']);
+            if ($search === false) {
+                return $response->withStatus(400)->withJson(['errors' => 'Ldap search failed : ' . ldap_error($ldap)]);
+            }
+            $entries = ldap_get_entries($ldap, $search);
+
+            if ($entries['count'] > 0) {
+                $renamed = @ldap_rename($ldap, $entries[0]['dn'], "cn={$entity['entity_label']}", "ou={$organization},{$annuary['baseDN']}", true);
+                if (!$renamed) {
+                    return $response->withStatus(400)->withJson(['errors' => 'Ldap rename failed : ' . ldap_error($ldap)]);
+                }
+
+                $replaced = @ldap_mod_replace($ldap, "cn={$entity['entity_label']},ou={$organization},{$annuary['baseDN']}", ['sn' => $entity['entity_label']]);
+                if (!$replaced) {
+                    return $response->withStatus(400)->withJson(['errors' => 'Ldap replace failed : ' . ldap_error($ldap)]);
+                }
+            } else {
+                $info = [];
+                $info['cn'] = $entity['entity_label'];
+                $info['sn'] = $entity['entity_label'];
+                $info['initials'] = $entity['entity_id'];
+                $info['objectclass'] = ['top', 'inetOrgPerson'];
+
+                $added = @ldap_add($ldap, "cn={$entity['entity_label']},ou={$organization},{$annuary['baseDN']}", $info);
+                if (!$added) {
+                    return $response->withStatus(400)->withJson(['errors' => 'Ldap add failed : ' . ldap_error($ldap)]);
+                }
+            }
+
+            break;
+        }
+
+        return $response->withJson(['entitySiret' => $entitySiret, 'synchronized' => !empty($authenticated)]);
+    }
+
+    public static function deleteEntityToOrganization(array $args)
+    {
+        $control = AnnuaryController::getAnnuaries();
+        if (!isset($control['annuaries'])) {
+            return $control;
+        }
+        $organization = $control['organization'];
+        $annuaries = $control['annuaries'];
+
+        foreach ($annuaries as $annuary) {
+            $ldap = @ldap_connect($annuary['uri']);
+            if ($ldap === false) {
+                continue;
+            }
+            ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
+            ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
+            ldap_set_option($ldap, LDAP_OPT_NETWORK_TIMEOUT, 5);
+
+            $search = @ldap_search($ldap, "ou={$organization},{$annuary['baseDN']}", "(initials={$args['entityId']})", ['dn']);
+            if ($search === false) {
+                continue;
+            }
+            $entries = ldap_get_entries($ldap, $search);
+            if ($entries['count'] == 0) {
+                return ['success' => 'Entity does not exist in annuary'];
+            }
+
+            $authenticated = @ldap_bind($ldap, $annuary['login'], $annuary['password']);
+            if (!$authenticated) {
+                return ['errors' => 'Ldap authentication failed : ' . ldap_error($ldap)];
+            }
+            $deleted = @ldap_delete($ldap, $entries[0]['dn']);
+            if (!$deleted) {
+                return ['errors' => 'Ldap delete failed : ' . ldap_error($ldap)];
+            }
+
+            break;
+        }
+
+        return ['deleted' => !empty($deleted)];
+    }
+
+    public static function getAnnuaries()
+    {
+        $loadedXml = CoreConfigModel::getXmlLoaded(['path' => 'apps/maarch_entreprise/xml/m2m_config.xml']);
+
+        if (!$loadedXml) {
+            return ['success' => 'M2M is disabled'];
+        }
+        if (empty($loadedXml->annuaries) || $loadedXml->annuaries->enabled == 'false') {
+            return ['success' => 'Annuary is disabled'];
+        }
+        $organization = (string)$loadedXml->annuaries->organization;
+        if (empty($organization)) {
+            return ['errors' => 'Tag organization is empty'];
+        }
+        $annuaries = [];
+        foreach ($loadedXml->annuaries->annuary as $annuary) {
+            $uri = ((string)$annuary->ssl === 'true' ? "LDAPS://{$annuary->uri}" : (string)$annuary->uri);
+
+            $annuaries[] = [
+                'uri'       => $uri,
+                'baseDN'    => (string)$annuary->baseDN,
+                'login'     => (string)$annuary->login,
+                'password'  => (string)$annuary->password,
+                'ssl'       => (string)$annuary->ssl,
+            ];
+        }
+
+        $rawCommunicationMeans = (string)$loadedXml->m2m_communication;
+        if (empty($rawCommunicationMeans)) {
+            return ['errors' => 'Tag m2m_communication is empty'];
+        }
+        $communicationMeans = [];
+        $rawCommunicationMeans = explode(',', $rawCommunicationMeans);
+        foreach ($rawCommunicationMeans as $value) {
+            if (filter_var($value, FILTER_VALIDATE_EMAIL)) {
+                $communicationMeans['email'] = $value;
+            } elseif (filter_var($value, FILTER_VALIDATE_URL)) {
+                $communicationMeans['url'] = $value;
+            }
+        }
+        if (empty($communicationMeans)) {
+            return ['errors' => 'No communication means found'];
+        }
+
+        return ['annuaries' => $annuaries, 'organization' => $organization, 'communicationMeans' => $communicationMeans];
+    }
+
+    public static function addContact(array $args)
+    {
+        $control = AnnuaryController::getAnnuaries();
+        if (empty($control['annuaries'])) {
+            return ['errors' => _M2M_ANNUARY_IS_NOT_SET];
+        }
+
+        $annuaries          = $control['annuaries'];
+        $organization       = $args['ouName'];
+        $communicationMeans = $args['communicationValue'];
+        $serviceName        = $args['serviceName'];
+
+        $m2mId              = $args['m2mId'];
+        $businessId         = explode("/", $m2mId);
+        $siret              = $businessId[0];
+        $entityId           = $businessId[1];
+
+        foreach ($annuaries as $annuary) {
+            $ldap = @ldap_connect($annuary['uri']);
+            if ($ldap === false) {
+                continue;
+            }
+            ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
+            ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
+            ldap_set_option($ldap, LDAP_OPT_NETWORK_TIMEOUT, 5);
+
+            $authenticated = @ldap_bind($ldap, $annuary['login'], $annuary['password']);
+            if (!$authenticated) {
+                return ['errors' => _M2M_LDAP_AUTHENTICATION_FAILED . ' : ' . ldap_error($ldap)];
+            }
+            
+            $search  = @ldap_search($ldap, "{$annuary['baseDN']}", "(destinationIndicator={$siret})", ['ou']);
+            $entries = ldap_get_entries($ldap, $search);
+
+            if ($entries['count'] > 0) {
+                $search = @ldap_search($ldap, "ou={$entries[0]['ou'][0]},{$annuary['baseDN']}", "(initials={$entityId})", ['ou', 'entryUUID']);
+                $entries = ldap_get_entries($ldap, $search);
+                if ($entries['count'] > 0) {
+                    return ['entryUUID' => $entries[0]['entryuuid'][0]];
+                }
+            } else {
+                $info = [];
+                $info['ou'] = $organization;
+                $info['destinationIndicator'] = $siret;
+                if (filter_var($communicationMeans, FILTER_VALIDATE_EMAIL)) {
+                    $info['postOfficeBox'] = $communicationMeans;
+                } else {
+                    $info['labeledURI'] = $communicationMeans;
+                }
+                
+                $info['objectclass'] = ['organizationalUnit', 'top', 'labeledURIObject'];
+
+                $added = @ldap_add($ldap, "ou={$organization},{$annuary['baseDN']}", $info);
+                if (!$added) {
+                    return ['errors' => _M2M_LDAP_ADD_FAILED . ' : ' . ldap_error($ldap)];
+                }
+            }
+            $info = [];
+            $info['cn'] = $serviceName;
+            $info['sn'] = $serviceName;
+            $info['initials'] = $entityId;
+            $info['objectclass'] = ['top', 'inetOrgPerson'];
+
+            $added = @ldap_add($ldap, "cn={$serviceName},ou={$organization},{$annuary['baseDN']}", $info);
+            if (!$added) {
+                return ['errors' => _M2M_LDAP_ADD_FAILED . ' : ' . ldap_error($ldap)];
+            }
+
+            $search  = @ldap_search($ldap, "ou={$organization},{$annuary['baseDN']}", "(initials={$entityId})", ['entryUUID']);
+            $entries = ldap_get_entries($ldap, $search);
+            return ['entryUUID' => $entries[0]['entryuuid'][0]];
+
+            break;
+        }
+
+        return ['errors' => _NO_M2M_ANNUARY_AVAILABLE];
+    }
+
+    public static function isSiretNumber(array $args)
+    {
+        if (strlen($args['siret']) != 14) {
+            return false;
+        }
+        if (!is_numeric($args['siret'])) {
+            return false;
+        }
+
+        // on prend chaque chiffre un par un
+        // si son index (position dans la chaîne en commence à 0 au premier caractère) est pair
+        // on double sa valeur et si cette dernière est supérieure à 9, on lui retranche 9
+        // on ajoute cette valeur à la somme totale
+
+        $sum = 0;
+        for ($index = 0; $index < 14; $index ++) {
+            $number = (int) $args['siret'][$index];
+            if (($index % 2) == 0) {
+                if (($number *= 2) > 9) {
+                    $number -= 9;
+                }
+            }
+            $sum += $number;
+        }
+
+        // le numéro est valide si la somme des chiffres est multiple de 10
+        if (($sum % 10) != 0) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    public static function getByUui(array $args)
+    {
+        $control = AnnuaryController::getAnnuaries();
+        if (!isset($control['annuaries'])) {
+            return $control;
+        }
+        $annuaries = $control['annuaries'];
+
+        foreach ($annuaries as $annuary) {
+            $ldap = @ldap_connect($annuary['uri']);
+            if ($ldap === false) {
+                continue;
+            }
+            ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
+            ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
+            ldap_set_option($ldap, LDAP_OPT_NETWORK_TIMEOUT, 10);
+
+            $search = @ldap_search($ldap, "{$annuary['baseDN']}", "(entryUUID={$args['contactUuid']})", ['dn', 'initials', 'entryDN']);
+            if ($search === false) {
+                continue;
+            }
+            $entries = ldap_get_entries($ldap, $search);
+            if ($entries['count'] > 0) {
+                $departmentDestinationIndicator = $entries[0]['initials'][0];
+                $entryDn  = $entries[0]['entrydn'][0];
+                $pathDn   = explode(',', $entryDn);
+                $parentOu = $pathDn[1];
+                $search   = @ldap_search($ldap, "{$annuary['baseDN']}", "({$parentOu})", ['dn', 'destinationIndicator', 'postOfficeBox', 'labeledURI']);
+                $entries  = ldap_get_entries($ldap, $search);
+
+                return ['mail' => $entries[0]['postofficebox'][0], 'url' => $entries[0]['labeleduri'][0], 'businessId' => $entries[0]['destinationindicator'][0] . '/' . $departmentDestinationIndicator];
+            }
+
+            break;
+        }
+
+        return ['errors' => 'No annuary found or UUID does not exist'];
+    }
+}
diff --git a/src/core/lang/lang-en.php b/src/core/lang/lang-en.php
index 258c10da7fe..cb8e55579a5 100755
--- a/src/core/lang/lang-en.php
+++ b/src/core/lang/lang-en.php
@@ -432,4 +432,10 @@ define("_NOTIFICATIONS_USER_CREATION_BODY", "Welcome,<br/><br/>You have now an a
 define("_NOTIFICATIONS_USER_CREATION_FOOTER", "<br/><br/>This message is sent automatically as a result of an administrator action.<br/>Please do not answer.<br/><br/>For any questions, please contact your software administrator");
 define("_CLICK_HERE", "Click-here");
 
+define("_M2M_ANNUARY_IS_NOT_SET", "M2M annuary is not set");
+define("_M2M_LDAP_AUTHENTICATION_FAILED", "M2M annuary authentication failed");
+define("_M2M_LDAP_ADD_FAILED", "Problem while adding contact in M2M annuary");
+define("_NO_M2M_ANNUARY_AVAILABLE", "No M2M annuary available");
+define("_CANNOT_SYNCHRONIZE_M2M_ANNUARY", "Contact can not be added in M2M annuary because one of these field is empty : Society, communication means, department");
+
 define("_DATE_LOCALE", "en_US.utf8");
diff --git a/src/core/lang/lang-fr.php b/src/core/lang/lang-fr.php
index 7b2be6c8db0..8a9e4d4bac7 100755
--- a/src/core/lang/lang-fr.php
+++ b/src/core/lang/lang-fr.php
@@ -431,4 +431,10 @@ define("_NOTIFICATIONS_USER_CREATION_BODY", "Bienvenue,<br/><br/>Vous disposez m
 define("_CLICK_HERE", "Cliquez-ici pour définir votre mot de passe");
 define("_NOTIFICATIONS_USER_CREATION_FOOTER", "<br/><br/>Ce message est envoyé automatiquement à la suite d'une action de l'administrateur.<br/>Merci de ne pas y répondre.<br/><br/>Pour toutes questions, merci de contacter l'administrateur technique de la solution.");
 
+define("_M2M_ANNUARY_IS_NOT_SET", "Aucun annuaire M2M paramétré");
+define("_M2M_LDAP_AUTHENTICATION_FAILED", "Connexion impossible à l'annuaire M2M");
+define("_M2M_LDAP_ADD_FAILED", "Problème lors de l'ajout du contact dans l'annuaire M2M");
+define("_NO_M2M_ANNUARY_AVAILABLE", "Aucun annuaire M2M disponible");
+define("_CANNOT_SYNCHRONIZE_M2M_ANNUARY", "Le contact ne peut pas être ajouté dans l'annuaire car un des champs suivants est manquant : Société, moyen de communication, service");
+
 define("_DATE_LOCALE", "fr_FR.utf8");
diff --git a/src/core/lang/lang-nl.php b/src/core/lang/lang-nl.php
index 3a26a60a607..3bc0af25dfe 100755
--- a/src/core/lang/lang-nl.php
+++ b/src/core/lang/lang-nl.php
@@ -437,4 +437,10 @@ define("_NOTIFICATIONS_USER_CREATION_BODY", "Welcome,<br/><br/>You have now an a
 define("_NOTIFICATIONS_USER_CREATION_FOOTER", "<br/><br/>This message is sent automatically as a result of an administrator action.<br/>Please do not answer.<br/><br/>For any questions, please contact your software administrator_TO_TRANSLATE");
 define("_CLICK_HERE", "Click-here_TO_TRANSLATE");
 
+define("_M2M_ANNUARY_IS_NOT_SET", "M2M annuary is not set");//TO TRANSLATE
+define("_M2M_LDAP_AUTHENTICATION_FAILED", "M2M annuary authentication failed");//TO TRANSLATE
+define("_M2M_LDAP_ADD_FAILED", "Problem while adding contact in M2M annuary");//TO TRANSLATE
+define("_NO_M2M_ANNUARY_AVAILABLE", "No M2M annuary available");//TO TRANSLATE
+define("_CANNOT_SYNCHRONIZE_M2M_ANNUARY", "Contact can not be added in M2M annuary because one of these field is empty : Society, communication means, department");//TO TRANSLATE
+
 define("_DATE_LOCALE", "en_US.utf8");
diff --git a/src/frontend/app/administration/entity/entities-administration.component.html b/src/frontend/app/administration/entity/entities-administration.component.html
index a2487facfa9..cda8cf59d25 100755
--- a/src/frontend/app/administration/entity/entities-administration.component.html
+++ b/src/frontend/app/administration/entity/entities-administration.component.html
@@ -172,11 +172,16 @@
                                             </mat-form-field>
                                         </div>
                                     </div>
-                                    <div class="form-group">
+                                    <div class="form-group" *ngIf="!creationMode">
                                         <div class="col-sm-12">
                                             <mat-form-field>
-                                                <input matInput [(ngModel)]="currentEntity.business_id" name="business_id" id="business_id" title="{{lang.siretCode}}" type="text"
-                                                    placeholder="{{lang.siretCode}}" maxlength="255">
+                                                <input *ngIf="!currentEntity.canSynchronizeSiret" matInput value="Paramètre SIRET manquant" title="{{lang.siretCode}}" type="text"
+                                                    placeholder="{{lang.siretCode}}" maxlength="255" disabled>
+                                                <input *ngIf="currentEntity.canSynchronizeSiret" matInput [(ngModel)]="currentEntity.business_id" name="business_id" id="business_id" title="{{lang.siretCode}}" type="text"
+                                                    placeholder="{{lang.siretCode}}" maxlength="255" disabled>
+                                                <button *ngIf="currentEntity.canSynchronizeSiret" color="primary" mat-icon-button matSuffix title="Générer un numéro SIRET" (click)="addEntityToAnnuary()">
+                                                    <mat-icon class="fas fa-compress-arrows-alt"></mat-icon>
+                                                </button>
                                             </mat-form-field>
                                         </div>
                                     </div>
@@ -325,4 +330,4 @@
             </mat-nav-list>
         </mat-sidenav>
     </mat-sidenav-container>
-</div>
\ No newline at end of file
+</div>
diff --git a/src/frontend/app/administration/entity/entities-administration.component.ts b/src/frontend/app/administration/entity/entities-administration.component.ts
index 9f8bd9abd87..165f478cb33 100755
--- a/src/frontend/app/administration/entity/entities-administration.component.ts
+++ b/src/frontend/app/administration/entity/entities-administration.component.ts
@@ -390,8 +390,12 @@ export class EntitiesAdministrationComponent implements OnInit {
                             $j('#jstree').jstree(true).settings.core.data = this.entities;
                             $j('#jstree').jstree("refresh");
                             this.sidenavRight.close();
-                            this.notify.success(this.lang.entityDeleted);
 
+                            if (typeof data['deleted'] !== "undefined" && !data['deleted']) {
+                                this.notify.success("Entité supprimée mais l'annuaire est injoignable, l'entité est possiblement toujours présente dedans");
+                            } else {
+                                this.notify.success(this.lang.entityDeleted);
+                            }
                         }, (err) => {
                             this.notify.error(err.error.errors);
                         });
@@ -432,8 +436,12 @@ export class EntitiesAdministrationComponent implements OnInit {
                         $j('#jstree').jstree(true).settings.core.data = this.entities;
                         $j('#jstree').jstree("refresh");
                         this.sidenavRight.close();
-                        this.notify.success(this.lang.entityDeleted);
-                    }, (err) => {
+                        if (typeof data['deleted'] !== "undefined" && !data['deleted']) {
+                            this.notify.success("Entité supprimée mais l'annuaire est injoignable, l'entité est possiblement toujours présente dedans");
+                        } else {
+                            this.notify.success(this.lang.entityDeleted);
+                        }
+                    }, (err: any) => {
                         this.notify.error(err.error.errors);
                     });
             }
@@ -708,7 +716,7 @@ export class EntitiesAdministrationComponent implements OnInit {
                     user_id : newUser.otherInfo,
                     firstname : displayName[0],
                     lastname : displayName[1]
-                }
+                };
                 this.currentEntity.users.push(user);
                 this.dataSourceUsers = new MatTableDataSource(this.currentEntity.users);
                 this.dataSourceUsers.paginator = this.paginatorUsers;
@@ -724,6 +732,24 @@ export class EntitiesAdministrationComponent implements OnInit {
             this.router.navigate(['/administration/templates/' + templateId]);
         }
     }
+
+    addEntityToAnnuary() {
+        this.http.put("../../rest/entities/" + this.currentEntity.id + "/annuaries", this.currentEntity)
+            .subscribe((data: any) => {
+                this.currentEntity.business_id = data['entitySiret'];
+                if (typeof data['synchronized'] === "undefined") {
+                    this.notify.success("Numéro SIRET généré");
+                } else {
+                    if (data['synchronized']) {
+                        this.notify.success("Numéro SIRET généré et synchronisation annuaire effectuée");
+                    } else {
+                        this.notify.success("Numéro SIRET généré mais l'annuaire est injoignable");
+                    }
+                }
+            }, (err: any) => {
+                this.notify.handleErrors(err);
+            });
+    }
 }
 @Component({
     templateUrl: "entities-administration-redirect-modal.component.html"
-- 
GitLab