From 8abce5ab85f65b7a37be16d3e2ed6aadb4f61c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 9 May 2022 13:36:59 +0200 Subject: [PATCH] fix cs + read remote calendar based on user access --- .../AzureGetMachineAccessTokenCommand.php | 1 - .../Command/MapUserCalendarCommand.php | 13 +++- .../RemoteCalendarConnectAzureController.php | 2 +- .../RemoteCalendarProxyController.php | 8 +-- .../Connector/MSGraph/MSGraphClient.php | 6 +- .../Connector/MSGraph/MSGraphTokenStorage.php | 7 +- .../Connector/MSGraph/MachineHttpClient.php | 23 +++++-- .../Connector/MSGraph/MachineTokenStorage.php | 4 +- .../Connector/MSGraph/MapCalendarToUser.php | 36 ++++++---- .../MSGraph/RemoteEventConverter.php | 34 +++++++++- .../Connector/MSGraph/UserHttpClient.php | 23 +++++-- .../MSGraphRemoteCalendarConnector.php | 48 +++++++------- .../RemoteCalendarCompilerPass.php | 1 - .../Synchro/Model/RemoteEvent.php | 1 - src/Bundle/ChillMainBundle/Entity/User.php | 14 ++-- .../Repository/UserRepository.php | 66 +++++++++---------- .../migrations/Version20220506223243.php | 17 +++-- 17 files changed, 189 insertions(+), 115 deletions(-) diff --git a/src/Bundle/ChillCalendarBundle/Command/AzureGetMachineAccessTokenCommand.php b/src/Bundle/ChillCalendarBundle/Command/AzureGetMachineAccessTokenCommand.php index e854fe17d..226edbc23 100644 --- a/src/Bundle/ChillCalendarBundle/Command/AzureGetMachineAccessTokenCommand.php +++ b/src/Bundle/ChillCalendarBundle/Command/AzureGetMachineAccessTokenCommand.php @@ -15,7 +15,6 @@ use Chill\CalendarBundle\Synchro\Connector\MSGraph\MachineTokenStorage; use KnpU\OAuth2ClientBundle\Client\ClientRegistry; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; use TheNetworg\OAuth2\Client\Provider\Azure; diff --git a/src/Bundle/ChillCalendarBundle/Command/MapUserCalendarCommand.php b/src/Bundle/ChillCalendarBundle/Command/MapUserCalendarCommand.php index cd6c61d54..43d4ccfa7 100644 --- a/src/Bundle/ChillCalendarBundle/Command/MapUserCalendarCommand.php +++ b/src/Bundle/ChillCalendarBundle/Command/MapUserCalendarCommand.php @@ -1,10 +1,17 @@ mapCalendarToUser->writeMetadata($user); - $offset++; + ++$offset; } $this->em->flush(); diff --git a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php index 43520035d..4ddba6f5c 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php @@ -45,7 +45,7 @@ class RemoteCalendarConnectAzureController return $this->clientRegistry ->getClient('azure') // key used in config/packages/knpu_oauth2_client.yaml ->redirect([ - 'https://graph.microsoft.com/.default' + 'https://graph.microsoft.com/.default', 'offline_access', ]); } diff --git a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarProxyController.php b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarProxyController.php index e05189a75..d8453696b 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarProxyController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarProxyController.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Controller; -use Chill\CalendarBundle\Synchro\Connector\MSGraph\RemoteEventConverter; use Chill\CalendarBundle\Synchro\Connector\RemoteCalendarConnectorInterface; use Chill\MainBundle\Entity\User; use DateTimeImmutable; @@ -31,7 +30,6 @@ class RemoteCalendarProxyController private SerializerInterface $serializer; - public function __construct(RemoteCalendarConnectorInterface $remoteCalendarConnector, SerializerInterface $serializer) { $this->remoteCalendarConnector = $remoteCalendarConnector; @@ -45,8 +43,9 @@ class RemoteCalendarProxyController { if ($request->query->has('startDate')) { $startDate = DateTimeImmutable::createFromFormat('Y-m-d', $request->query->get('startDate')); + if (false === $startDate) { - throw new BadRequestHttpException("startDate on bad format"); + throw new BadRequestHttpException('startDate on bad format'); } } else { throw new BadRequestHttpException('startDate not provided'); @@ -54,8 +53,9 @@ class RemoteCalendarProxyController if ($request->query->has('endDate')) { $endDate = DateTimeImmutable::createFromFormat('Y-m-d', $request->query->get('endDate')); + if (false === $endDate) { - throw new BadRequestHttpException("endDate on bad format"); + throw new BadRequestHttpException('endDate on bad format'); } } else { throw new BadRequestHttpException('endDate not provided'); diff --git a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MSGraphClient.php b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MSGraphClient.php index d43156828..58baf73fb 100644 --- a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MSGraphClient.php +++ b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MSGraphClient.php @@ -16,12 +16,12 @@ use TheNetworg\OAuth2\Client\Provider\Azure; class MSGraphClient { + private MachineTokenStorage $machineTokenStorage; + private Azure $provider; private MSGraphTokenStorage $tokenStorage; - private MachineTokenStorage $machineTokenStorage; - /** * @param mixed $calendar * @@ -38,6 +38,4 @@ class MSGraphClient return $response; } - - } diff --git a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MSGraphTokenStorage.php b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MSGraphTokenStorage.php index abc1c6495..94cb917f7 100644 --- a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MSGraphTokenStorage.php +++ b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MSGraphTokenStorage.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Synchro\Connector\MSGraph; +use LogicException; use Symfony\Component\HttpFoundation\Session\SessionInterface; use TheNetworg\OAuth2\Client\Provider\Azure; use TheNetworg\OAuth2\Client\Token\AccessToken; @@ -19,10 +20,10 @@ class MSGraphTokenStorage { public const MS_GRAPH_ACCESS_TOKEN = 'msgraph_access_token'; - private SessionInterface $session; - private Azure $azure; + private SessionInterface $session; + public function __construct(Azure $azure, SessionInterface $session) { $this->azure = $azure; @@ -35,7 +36,7 @@ class MSGraphTokenStorage $token = $this->session->get(self::MS_GRAPH_ACCESS_TOKEN, null); if (null === $token) { - throw new \LogicException('unexisting token'); + throw new LogicException('unexisting token'); } if ($token->hasExpired()) { diff --git a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MachineHttpClient.php b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MachineHttpClient.php index cbadc8409..09645a970 100644 --- a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MachineHttpClient.php +++ b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MachineHttpClient.php @@ -1,21 +1,30 @@ decoratedClient->request($method, $url, $options); } - public function stream($responses, float $timeout = null): ResponseStreamInterface + public function stream($responses, ?float $timeout = null): ResponseStreamInterface { return $this->decoratedClient->stream($responses, $timeout); } diff --git a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MachineTokenStorage.php b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MachineTokenStorage.php index b74a4967b..4d9cef091 100644 --- a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MachineTokenStorage.php +++ b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MachineTokenStorage.php @@ -20,11 +20,11 @@ class MachineTokenStorage { private const KEY = 'msgraph_access_token'; - private ChillRedis $chillRedis; + private ?AccessTokenInterface $accessToken = null; private Azure $azure; - private ?AccessTokenInterface $accessToken = null; + private ChillRedis $chillRedis; public function __construct(Azure $azure, ChillRedis $chillRedis) { diff --git a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MapCalendarToUser.php b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MapCalendarToUser.php index 3cb6934c9..ec4b9a882 100644 --- a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MapCalendarToUser.php +++ b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/MapCalendarToUser.php @@ -1,5 +1,14 @@ remoteCalendarConnector = $remoteCalendarConnector; $this->logger = $logger; } + public function getCalendarId(User $user): ?string + { + if (null === $mskey = ($user->getAttributes()[self::METADATA_KEY] ?? null)) { + return null; + } + + return $msKey['defaultCalendarId'] ?? null; + } + public function writeMetadata(User $user): User { if (null === $userData = $this->remoteCalendarConnector->getUserByEmail($user->getEmailCanonical())) { $this->logger->warning('[MapCalendarToUser] could find user on msgraph', ['userId' => $user->getId(), 'email' => $user->getEmailCanonical()]); + return $this->writeNullData($user); } if (null === $defaultCalendar = $this->remoteCalendarConnector->getDefaultUserCalendar($userData['id'])) { $this->logger->warning('[MapCalendarToUser] could find default calendar', ['userId' => $user->getId(), 'email' => $user->getEmailCanonical()]); + return $this->writeNullData($user); } @@ -46,14 +66,4 @@ class MapCalendarToUser { return $user->unsetAttribute(self::METADATA_KEY); } - - public function getCalendarId(User $user): ?string - { - if (null === $mskey = ($user->getAttributes()[self::METADATA_KEY] ?? null)) { - return null; - } - - return $msKey['defaultCalendarId'] ?? null; - } - } diff --git a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/RemoteEventConverter.php b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/RemoteEventConverter.php index d01a93e5f..092191336 100644 --- a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/RemoteEventConverter.php +++ b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/RemoteEventConverter.php @@ -1,19 +1,49 @@ defaultDateTimeZone = (new DateTimeImmutable())->getTimezone(); + $this->remoteDateTimeZone = new DateTimeZone('UTC'); + } + public function convertToRemote(array $event): RemoteEvent { + $startDate = + DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['start']['dateTime']) + ->setTimezone($this->defaultDateTimeZone); + $endDate = + DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['end']['dateTime'], $this->remoteDateTimeZone) + ->setTimezone($this->defaultDateTimeZone); + return new RemoteEvent( $event['id'], $event['subject'], '', - \DateTimeImmutable::createFromFormat('Y-m-dTH:i:s.u', $event['start']['dateTime'], new \DateTimeZone('UTC')), - \DateTimeImmutable::createFromFormat('Y-m-dTH:i:s.u', $event['end']['dateTime'], new \DateTimeZone('UTC')) + $startDate, + $endDate ); } } diff --git a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/UserHttpClient.php b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/UserHttpClient.php index 65b6d9086..6daf6fb16 100644 --- a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/UserHttpClient.php +++ b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraph/UserHttpClient.php @@ -1,20 +1,30 @@ decoratedClient->request($method, $url, $options); } - public function stream($responses, float $timeout = null): ResponseStreamInterface + public function stream($responses, ?float $timeout = null): ResponseStreamInterface { return $this->decoratedClient->stream($responses, $timeout); } - } diff --git a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraphRemoteCalendarConnector.php b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraphRemoteCalendarConnector.php index 1d36dfc95..6c659fec4 100644 --- a/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraphRemoteCalendarConnector.php +++ b/src/Bundle/ChillCalendarBundle/Synchro/Connector/MSGraphRemoteCalendarConnector.php @@ -12,7 +12,6 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Synchro\Connector; use Chill\CalendarBundle\Synchro\Connector\MSGraph\MachineHttpClient; -use Chill\CalendarBundle\Synchro\Connector\MSGraph\MSGraphClient; use Chill\CalendarBundle\Synchro\Connector\MSGraph\MSGraphTokenStorage; use Chill\CalendarBundle\Synchro\Connector\MSGraph\RemoteEventConverter; use Chill\CalendarBundle\Synchro\Connector\MSGraph\UserHttpClient; @@ -21,19 +20,18 @@ use DateTimeImmutable; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use function Amp\Iterator\toArray; class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface { private MachineHttpClient $machineHttpClient; - private UserHttpClient $userHttpClient; + private RemoteEventConverter $remoteEventConverter; private MSGraphTokenStorage $tokenStorage; private UrlGeneratorInterface $urlGenerator; - private RemoteEventConverter $remoteEventConverter; + private UserHttpClient $userHttpClient; public function __construct( MachineHttpClient $machineHttpClient, @@ -49,12 +47,30 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface $this->userHttpClient = $userHttpClient; } + public function getDefaultUserCalendar(string $idOrUserPrincipalName): ?array + { + $value = $this->machineHttpClient->request('GET', "users/{$idOrUserPrincipalName}/calendars", [ + 'query' => ['$filter' => 'isDefaultCalendar eq true'], + ])->toArray()['value']; + + return $value[0] ?? null; + } + public function getMakeReadyResponse(string $returnPath): Response { return new RedirectResponse($this->urlGenerator ->generate('chill_calendar_remote_connect_azure', ['returnPath' => $returnPath])); } + public function getUserByEmail(string $email): ?array + { + $value = $this->machineHttpClient->request('GET', 'users', [ + 'query' => ['$filter' => "mail eq '{$email}'"], + ])->toArray()['value']; + + return $value[0] ?? null; + } + public function isReady(): bool { return $this->tokenStorage->hasToken(); @@ -69,29 +85,11 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface 'query' => [ 'startDateTime' => $startDate->format(DateTimeImmutable::ATOM), 'endDateTime' => $endDate->format(DateTimeImmutable::ATOM), - '$select' => 'id,subject,start,end' - ] + '$select' => 'id,subject,start,end', + ], ] )->toArray(); - return array_map(function($item) { return $this->remoteEventConverter->convertToRemote($item);}, $bareEvents['value']); - } - - public function getUserByEmail(string $email): ?array - { - $value = $this->machineHttpClient->request('GET', 'users', [ - 'query' => ['$filter' => "mail eq '${email}'"], - ])->toArray()['value']; - - return $value[0] ?? null; - } - - public function getDefaultUserCalendar(string $idOrUserPrincipalName): ?array - { - $value = $this->machineHttpClient->request('GET', "users/$idOrUserPrincipalName/calendars", [ - 'query' => ['$filter' => 'isDefaultCalendar eq true'], - ])->toArray()['value']; - - return $value[0] ?? null; + return array_map(function ($item) { return $this->remoteEventConverter->convertToRemote($item); }, $bareEvents['value']); } } diff --git a/src/Bundle/ChillCalendarBundle/Synchro/DependencyInjection/RemoteCalendarCompilerPass.php b/src/Bundle/ChillCalendarBundle/Synchro/DependencyInjection/RemoteCalendarCompilerPass.php index dfabf0dbb..5a7449673 100644 --- a/src/Bundle/ChillCalendarBundle/Synchro/DependencyInjection/RemoteCalendarCompilerPass.php +++ b/src/Bundle/ChillCalendarBundle/Synchro/DependencyInjection/RemoteCalendarCompilerPass.php @@ -15,7 +15,6 @@ use Chill\CalendarBundle\Synchro\Connector\MSGraphRemoteCalendarConnector; use Chill\CalendarBundle\Synchro\Connector\NullRemoteCalendarConnector; use Chill\CalendarBundle\Synchro\Connector\RemoteCalendarConnectorInterface; use RuntimeException; -use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use TheNetworg\OAuth2\Client\Provider\Azure; diff --git a/src/Bundle/ChillCalendarBundle/Synchro/Model/RemoteEvent.php b/src/Bundle/ChillCalendarBundle/Synchro/Model/RemoteEvent.php index 4db50574d..b25ee0e6e 100644 --- a/src/Bundle/ChillCalendarBundle/Synchro/Model/RemoteEvent.php +++ b/src/Bundle/ChillCalendarBundle/Synchro/Model/RemoteEvent.php @@ -16,7 +16,6 @@ use Symfony\Component\Serializer\Annotation as Serializer; class RemoteEvent { - public string $description; /** diff --git a/src/Bundle/ChillMainBundle/Entity/User.php b/src/Bundle/ChillMainBundle/Entity/User.php index d044b0b15..120fdd181 100644 --- a/src/Bundle/ChillMainBundle/Entity/User.php +++ b/src/Bundle/ChillMainBundle/Entity/User.php @@ -361,13 +361,6 @@ class User implements AdvancedUserInterface return $this; } - public function unsetAttribute($key): self - { - unset($this->attributes[$key]); - - return $this; - } - public function setCurrentLocation(?Location $currentLocation): User { $this->currentLocation = $currentLocation; @@ -494,4 +487,11 @@ class User implements AdvancedUserInterface return $this; } + + public function unsetAttribute($key): self + { + unset($this->attributes[$key]); + + return $this; + } } diff --git a/src/Bundle/ChillMainBundle/Repository/UserRepository.php b/src/Bundle/ChillMainBundle/Repository/UserRepository.php index de8512b1c..aff3109c6 100644 --- a/src/Bundle/ChillMainBundle/Repository/UserRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/UserRepository.php @@ -44,6 +44,16 @@ final class UserRepository implements ObjectRepository return $this->countBy(['enabled' => true]); } + public function countByNotHavingAttribute(string $key): int + { + $rsm = new ResultSetMapping(); + $rsm->addScalarResult('count', 'count'); + + $sql = 'SELECT count(*) FROM users u WHERE NOT attributes ?? :key OR attributes IS NULL AND enabled IS TRUE'; + + return $this->entityManager->createNativeQuery($sql, $rsm)->setParameter(':key', $key)->getSingleScalarResult(); + } + public function countByUsernameOrEmail(string $pattern): int { $qb = $this->queryByUsernameOrEmail($pattern); @@ -85,6 +95,29 @@ final class UserRepository implements ObjectRepository return $this->findBy(['enabled' => true], $orderBy, $limit, $offset); } + /** + * Find users which does not have a key on attribute column. + * + * @return array|User[] + */ + public function findByNotHavingAttribute(string $key, ?int $limit = null, ?int $offset = null): array + { + $rsm = new ResultSetMappingBuilder($this->entityManager); + $rsm->addRootEntityFromClassMetadata(User::class, 'u'); + + $sql = 'SELECT ' . $rsm->generateSelectClause() . ' FROM users u WHERE NOT attributes ?? :key OR attributes IS NULL AND enabled IS TRUE'; + + if (null !== $limit) { + $sql .= " LIMIT {$limit}"; + } + + if (null !== $offset) { + $sql .= " OFFSET {$offset}"; + } + + return $this->entityManager->createNativeQuery($sql, $rsm)->setParameter(':key', $key)->getResult(); + } + public function findByUsernameOrEmail(string $pattern) { $qb = $this->queryByUsernameOrEmail($pattern); @@ -159,39 +192,6 @@ final class UserRepository implements ObjectRepository return $qb->getQuery()->getResult(); } - /** - * Find users which does not have a key on attribute column - * - * @return array|User[] - */ - public function findByNotHavingAttribute(string $key, ?int $limit = null, ?int $offset = null): array - { - $rsm = new ResultSetMappingBuilder($this->entityManager); - $rsm->addRootEntityFromClassMetadata(User::class, 'u'); - - $sql = "SELECT ".$rsm->generateSelectClause()." FROM users u WHERE NOT attributes ?? :key OR attributes IS NULL AND enabled IS TRUE"; - - if (null !== $limit) { - $sql .= " LIMIT $limit"; - } - - if (null !== $offset) { - $sql .= " OFFSET $offset"; - } - - return $this->entityManager->createNativeQuery($sql, $rsm)->setParameter(':key', $key)->getResult(); - } - - public function countByNotHavingAttribute(string $key): int - { - $rsm = new ResultSetMapping(); - $rsm->addScalarResult('count', 'count'); - - $sql = "SELECT count(*) FROM users u WHERE NOT attributes ?? :key OR attributes IS NULL AND enabled IS TRUE"; - - return $this->entityManager->createNativeQuery($sql, $rsm)->setParameter(':key', $key)->getSingleScalarResult(); - } - public function getClassName() { return User::class; diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220506223243.php b/src/Bundle/ChillMainBundle/migrations/Version20220506223243.php index e362cc665..c85c4aa35 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20220506223243.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20220506223243.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE users ALTER attributes DROP NOT NULL'); + } + public function getDescription(): string { return 'Force user attribute to be an array'; @@ -20,9 +32,4 @@ final class Version20220506223243 extends AbstractMigration $this->addSql('ALTER TABLE users ALTER attributes SET NOT NULL'); $this->addSql('ALTER TABLE users ALTER attributes SET DEFAULT \'{}\'::jsonb'); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE users ALTER attributes DROP NOT NULL'); - } }