diff --git a/composer.json b/composer.json index 999ef904967e4df0dc8aabf8aec21c4844744afa..9143d3053663167f7bd77d846a1aa41e98416b0e 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 62d3688b5afebd69026d261b79877ef14fd2c697..a5b90aaf0aceb2019ab23f243512f349312851dd 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 a45ff926a84d256a94319a7c8821b74db5dad0e4..52e7494d31b31ff4abd50d164ae2955be55d4d19 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 07d23719888840cf27fad122a0acb61739cbafe1..4197f4ee0b68589a893c6fd86a02c42d05acea2d 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 20557e6c742432f6bac101039ee62cdfd4f144ad..cff8c87921832189b1bd99ce916e2588988802b0 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();