From 324394c64e5013877dd333a51c9743c63ac88349 Mon Sep 17 00:00:00 2001
From: Tobias Gross-Vogt <tgros@tugraz.at>
Date: Mon, 2 May 2022 11:10:49 +0200
Subject: [PATCH] implemented local data mechanism

---
 composer.json                             |   6 +-
 composer.lock                             |  32 ++++--
 src/Event/PersonFromUserItemPostEvent.php |  16 ++-
 src/Service/LDAPApi.php                   | 120 ++++++++++++----------
 src/Service/LDAPPersonProvider.php        |  14 ++-
 5 files changed, 119 insertions(+), 69 deletions(-)

diff --git a/composer.json b/composer.json
index 999ef90..9143d30 100644
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,8 @@
         "ext-simplexml": "*",
         "adldap2/adldap2": "^10.3",
         "dbp/relay-auth-bundle": "^0.1.6",
-        "dbp/relay-base-person-bundle": "^0.2.0",
+        "dbp/relay-base-person-bundle": "dev-main as 0.2.0",
+        "dbp/relay-core-bundle": "^0.1.35",
         "guzzlehttp/guzzle": "^7.3",
         "league/uri": "^6.5",
         "symfony/event-dispatcher": "^5.4",
@@ -40,6 +41,9 @@
         "sort-packages": true,
         "platform": {
             "php": "7.3"
+        },
+        "allow-plugins": {
+            "composer/package-versions-deprecated": true
         }
     },
     "scripts": {
diff --git a/composer.lock b/composer.lock
index 62d3688..a5b90aa 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "660bb35831e9baee96c0c873db1f624a",
+    "content-hash": "486c7399159622dbac4ab15d95ea77b6",
     "packages": [
         {
             "name": "adldap2/adldap2",
@@ -346,15 +346,15 @@
         },
         {
             "name": "dbp/relay-base-person-bundle",
-            "version": "v0.2.0",
+            "version": "dev-main",
             "source": {
                 "type": "git",
                 "url": "https://gitlab.tugraz.at/dbp/relay/dbp-relay-base-person-bundle",
-                "reference": "9793a5e69a4a4b4ae76f0eacc1510ad33e5ec1a3"
+                "reference": "b304723071831c2e5e01ffbd254106fc868e825f"
             },
             "require": {
                 "api-platform/core": "^2.6.3",
-                "dbp/relay-core-bundle": "^0.1.25",
+                "dbp/relay-core-bundle": "^0.1.35",
                 "ext-json": "*",
                 "guzzlehttp/guzzle": "^7.0",
                 "nelmio/cors-bundle": "^2.1.0",
@@ -381,6 +381,7 @@
                 "symfony/phpunit-bridge": "^5.2",
                 "vimeo/psalm": "^4.4"
             },
+            "default-branch": true,
             "type": "symfony-bundle",
             "autoload": {
                 "psr-4": {
@@ -391,15 +392,15 @@
             "license": [
                 "AGPL-3.0-or-later"
             ],
-            "time": "2022-03-17T14:27:32+00:00"
+            "time": "2022-05-02T07:24:36+00:00"
         },
         {
             "name": "dbp/relay-core-bundle",
-            "version": "v0.1.34",
+            "version": "v0.1.35",
             "source": {
                 "type": "git",
                 "url": "https://gitlab.tugraz.at/dbp/relay/dbp-relay-core-bundle",
-                "reference": "d957d248aff513901e89d6eeb7dc8d0e3f68ea63"
+                "reference": "79d1b52f65989ee2285a9219c4a552f2cbc9bab2"
             },
             "require": {
                 "api-platform/core": "^2.6.6",
@@ -459,7 +460,7 @@
                 "AGPL-3.0-or-later"
             ],
             "description": "The core bundle of the Relay API gateway",
-            "time": "2022-04-26T12:25:46+00:00"
+            "time": "2022-05-02T07:07:30+00:00"
         },
         {
             "name": "doctrine/annotations",
@@ -11344,9 +11345,18 @@
             "time": "2015-12-17T08:42:14+00:00"
         }
     ],
-    "aliases": [],
+    "aliases": [
+        {
+            "package": "dbp/relay-base-person-bundle",
+            "version": "dev-main",
+            "alias": "0.2.0",
+            "alias_normalized": "0.2.0.0"
+        }
+    ],
     "minimum-stability": "stable",
-    "stability-flags": [],
+    "stability-flags": {
+        "dbp/relay-base-person-bundle": 20
+    },
     "prefer-stable": false,
     "prefer-lowest": false,
     "platform": {
@@ -11358,5 +11368,5 @@
     "platform-overrides": {
         "php": "7.3"
     },
-    "plugin-api-version": "2.3.0"
+    "plugin-api-version": "2.2.0"
 }
diff --git a/src/Event/PersonFromUserItemPostEvent.php b/src/Event/PersonFromUserItemPostEvent.php
index a45ff92..52e7494 100644
--- a/src/Event/PersonFromUserItemPostEvent.php
+++ b/src/Event/PersonFromUserItemPostEvent.php
@@ -5,9 +5,9 @@ declare(strict_types=1);
 namespace Dbp\Relay\BasePersonConnectorLdapBundle\Event;
 
 use Dbp\Relay\BasePersonBundle\Entity\Person;
-use Symfony\Contracts\EventDispatcher\Event;
+use Dbp\Relay\CoreBundle\LocalData\LocalDataAwareEvent;
 
-class PersonFromUserItemPostEvent extends Event
+class PersonFromUserItemPostEvent extends LocalDataAwareEvent
 {
     public const NAME = 'dbp.relay.base_person_connector_ldap_bundle.person_from_user_item.post';
 
@@ -17,6 +17,8 @@ class PersonFromUserItemPostEvent extends Event
 
     public function __construct(array $attributes, Person $person, bool $full)
     {
+        parent::__construct($person);
+
         $this->attributes = $attributes;
         $this->person = $person;
         $this->full = $full;
@@ -41,4 +43,14 @@ class PersonFromUserItemPostEvent extends Event
     {
         $this->person = $person;
     }
+
+    public function getEntity(): Person
+    {
+        return $this->person;
+    }
+
+    public function getSourceData(): array
+    {
+        return $this->attributes;
+    }
 }
diff --git a/src/Service/LDAPApi.php b/src/Service/LDAPApi.php
index 07d2371..4197f4e 100644
--- a/src/Service/LDAPApi.php
+++ b/src/Service/LDAPApi.php
@@ -16,11 +16,12 @@ use Adldap\Models\User;
 use Adldap\Query\Builder;
 use Dbp\Relay\BasePersonBundle\Entity\Person;
 use Dbp\Relay\BasePersonConnectorLdapBundle\Event\PersonFromUserItemPostEvent;
-use Dbp\Relay\BasePersonConnectorLdapBundle\Event\PersonFromUserItemPreEvent;
 use Dbp\Relay\BasePersonConnectorLdapBundle\Event\PersonUserItemPreEvent;
 use Dbp\Relay\CoreBundle\API\UserSessionInterface;
 use Dbp\Relay\CoreBundle\Exception\ApiError;
 use Dbp\Relay\CoreBundle\Helpers\Tools as CoreTools;
+use Dbp\Relay\CoreBundle\LocalData\LocalData;
+use Dbp\Relay\CoreBundle\LocalData\LocalDataAwareEventDispatcher;
 use Psr\Cache\CacheItemPoolInterface;
 use Psr\Container\ContainerInterface;
 use Psr\Log\LoggerAwareInterface;
@@ -37,20 +38,18 @@ class LDAPApi implements LoggerAwareInterface, ServiceSubscriberInterface
 
     private $PAGESIZE = 50;
 
-    /**
-     * @var Adldap
-     */
-    private $ad;
+    /** @var ProviderInterface|null */
+    private $provider;
 
+    /** @var CacheItemPoolInterface|null */
     private $cachePool;
 
+    /** @var CacheItemPoolInterface|null */
     private $personCache;
 
     private $cacheTTL;
 
-    /**
-     * @var Person|null
-     */
+    /** @var Person|null */
     private $currentPerson;
 
     private $providerConfig;
@@ -72,14 +71,18 @@ class LDAPApi implements LoggerAwareInterface, ServiceSubscriberInterface
     /** @var EventDispatcherInterface */
     private $dispatcher;
 
+    /** @var LocalDataAwareEventDispatcher */
+    private $localDataAwareEventDispatcher;
+
     public function __construct(ContainerInterface $locator, EventDispatcherInterface $dispatcher)
     {
-        $this->ad = new Adldap();
+        $this->provider = null;
         $this->cacheTTL = 0;
         $this->currentPerson = null;
         $this->locator = $locator;
         $this->deploymentEnv = 'production';
         $this->dispatcher = $dispatcher;
+        $this->localDataAwareEventDispatcher = new LocalDataAwareEventDispatcher(Person::class, $dispatcher);
     }
 
     public function setConfig(array $config)
@@ -168,15 +171,20 @@ class LDAPApi implements LoggerAwareInterface, ServiceSubscriberInterface
         if ($this->logger !== null) {
             Adldap::setLogger($this->logger);
         }
-        $ad = new Adldap();
-        $ad->addProvider($this->providerConfig);
-        $provider = $ad->connect();
-        assert($provider instanceof Provider);
-        if ($this->cachePool !== null) {
-            $provider->setCache(new Psr16Cache($this->cachePool));
+
+        if ($this->provider === null) {
+            $ad = new Adldap();
+            $ad->addProvider($this->providerConfig);
+
+            $this->provider = $ad->connect();
+            assert($this->provider instanceof Provider);
+
+            if ($this->cachePool !== null) {
+                $this->provider->setCache(new Psr16Cache($this->cachePool));
+            }
         }
 
-        return $provider;
+        return $this->provider;
     }
 
     private function getCachedBuilder(ProviderInterface $provider): Builder
@@ -185,9 +193,7 @@ class LDAPApi implements LoggerAwareInterface, ServiceSubscriberInterface
         // return $provider->search()->cache($until=$this->cacheTTL);
         // We depend on the default TTL of the cache for now...
 
-        /**
-         * @var Builder $builder
-         */
+        /** @var Builder $builder */
         $builder = $provider->search()->cache();
 
         return $builder;
@@ -220,6 +226,8 @@ class LDAPApi implements LoggerAwareInterface, ServiceSubscriberInterface
 
     public function getPersons(array $filters): array
     {
+        $this->localDataAwareEventDispatcher->initRequestedLocalDataAttributes(LocalData::getIncludeParameter($filters));
+
         $persons = [];
         $items = $this->getPeopleUserItems($filters);
         foreach ($items as $item) {
@@ -230,7 +238,7 @@ class LDAPApi implements LoggerAwareInterface, ServiceSubscriberInterface
         return $persons;
     }
 
-    public function getPersonUserItem(string $identifier): ?User
+    private function getPersonUserItem(string $identifier): ?User
     {
         $preEvent = new PersonUserItemPreEvent($identifier);
         $this->dispatcher->dispatch($preEvent, PersonUserItemPreEvent::NAME);
@@ -295,21 +303,25 @@ class LDAPApi implements LoggerAwareInterface, ServiceSubscriberInterface
         }
 
         $postEvent = new PersonFromUserItemPostEvent($attributes, $person, $full);
-        $this->dispatcher->dispatch($postEvent, PersonFromUserItemPostEvent::NAME);
+        $this->localDataAwareEventDispatcher->dispatch($postEvent, PersonFromUserItemPostEvent::NAME);
 
         return $postEvent->getPerson();
     }
 
-    public function getPerson(string $id): Person
+    public function getPerson(string $id, array $options = []): Person
     {
-        $id = str_replace('/people/', '', $id);
+        $this->localDataAwareEventDispatcher->initRequestedLocalDataAttributes(LocalData::getIncludeParameter($options));
+
+        // extract username in case $id is an iri, e.g. /base/people/{user}
+        $parts = explode('/', $id);
+        $id = $parts[count($parts) - 1];
 
         $session = $this->getUserSession();
         $currentIdentifier = $session->getUserIdentifier();
 
         if ($currentIdentifier !== null && $currentIdentifier === $id) {
-            // fast path: getCurrentPerson() does some caching
-            $person = $this->getCurrentPerson();
+            // fast path
+            $person = $this->getCurrentPersonCached(true);
             assert($person !== null);
         } else {
             $user = $this->getPersonUserItem($id);
@@ -324,63 +336,65 @@ class LDAPApi implements LoggerAwareInterface, ServiceSubscriberInterface
         return $this->locator->get(UserSessionInterface::class);
     }
 
-    public function getCurrentPersonCached(): Person
+    /**
+     * @thorws NotFoundHttpException
+     */
+    private function getCurrentPersonCached(bool $checkLocalDataAttributes): ?Person
     {
         $session = $this->getUserSession();
         $currentIdentifier = $session->getUserIdentifier();
-        assert($currentIdentifier !== null);
+        if ($currentIdentifier === null) {
+            return null;
+        }
+
+        $forceCreation = false;
+
+        if ($this->currentPerson) {
+            if ($this->currentPerson->getIdentifier() === $currentIdentifier) {
+                if (!$checkLocalDataAttributes || $this->localDataAwareEventDispatcher->checkRequestedAttributesIdentitcal($this->currentPerson)) {
+                    return $this->currentPerson;
+                } else {
+                    // cache a new instance of Person because the cached instance's local data attributes do not match the requested attributes
+                    $forceCreation = true;
+                }
+            }
+            $this->currentPerson = null;
+        }
 
         $cache = $this->personCache;
         $cacheKey = $session->getSessionCacheKey().'-'.$currentIdentifier;
         // make sure the cache is longer than the session, so just double it.
         $cacheTTL = $session->getSessionTTL() * 2;
+        $person = null;
 
         $item = $cache->getItem($cacheKey);
-
-        if ($item->isHit()) {
+        if (!$forceCreation && $item->isHit()) {
             $person = $item->get();
-            if ($person === null) {
-                throw new NotFoundHttpException();
-            }
-
-            return $person;
         } else {
             try {
                 $user = $this->getPersonUserItem($currentIdentifier);
                 $person = $this->personFromUserItem($user, true);
             } catch (NotFoundHttpException $e) {
-                $person = null;
             }
             $item->set($person);
             $item->expiresAfter($cacheTTL);
             $cache->save($item);
-            if ($person === null) {
-                throw new NotFoundHttpException();
-            }
-
-            return $person;
         }
-    }
 
-    public function getCurrentPerson(): ?Person
-    {
-        $session = $this->getUserSession();
-        $currentIdentifier = $session->getUserIdentifier();
-        if ($currentIdentifier === null) {
-            return null;
-        }
-
-        if ($this->currentPerson !== null && $this->currentPerson->getIdentifier() !== $currentIdentifier) {
-            $this->currentPerson = null;
-        }
+        $this->currentPerson = $person;
 
         if ($this->currentPerson === null) {
-            $this->currentPerson = $this->getCurrentPersonCached();
+            throw new NotFoundHttpException();
         }
 
         return $this->currentPerson;
     }
 
+    public function getCurrentPerson(): ?Person
+    {
+        return $this->getCurrentPersonCached(false);
+    }
+
     public static function getSubscribedServices(): array
     {
         return [
diff --git a/src/Service/LDAPPersonProvider.php b/src/Service/LDAPPersonProvider.php
index 20557e6..cff8c87 100644
--- a/src/Service/LDAPPersonProvider.php
+++ b/src/Service/LDAPPersonProvider.php
@@ -9,6 +9,7 @@ use Dbp\Relay\BasePersonBundle\Entity\Person;
 
 class LDAPPersonProvider implements PersonProviderInterface
 {
+    /** @var LDAPApi */
     private $ldapApi;
 
     public function __construct(LDAPApi $ldapApi)
@@ -16,16 +17,25 @@ class LDAPPersonProvider implements PersonProviderInterface
         $this->ldapApi = $ldapApi;
     }
 
+    /**
+     * @param array $filters $filters['search'] can be a string to search for people (e.g. part of the name)
+     *
+     * @return Person[]
+     */
     public function getPersons(array $filters): array
     {
         return $this->ldapApi->getPersons($filters);
     }
 
-    public function getPerson(string $id): Person
+    public function getPerson(string $id, array $options = []): Person
     {
-        return $this->ldapApi->getPerson($id);
+        return $this->ldapApi->getPerson($id, $options);
     }
 
+    /**
+     * Returns the Person matching the current user. Or null if there is no associated person
+     * like when the client is another server.
+     */
     public function getCurrentPerson(): ?Person
     {
         return $this->ldapApi->getCurrentPerson();
-- 
GitLab