X-Git-Url: https://git.rapsys.eu/airbundle/blobdiff_plain/0d068fd3e188d5f80f718e1003bf7937ebd13049..2884b5c8b4514b0e1c0dd0449db2af5be32382b6:/Controller/UserController.php

diff --git a/Controller/UserController.php b/Controller/UserController.php
index 510ae33..5010e64 100644
--- a/Controller/UserController.php
+++ b/Controller/UserController.php
@@ -11,10 +11,27 @@
 
 namespace Rapsys\AirBundle\Controller;
 
-use Symfony\Contracts\Cache\ItemInterface;
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\Persistence\ManagerRegistry;
+
+use Google\Client;
+
+use Psr\Container\ContainerInterface;
+use Psr\Log\LoggerInterface;
+
+use Symfony\Bundle\SecurityBundle\Security;
+use Symfony\Component\Form\FormFactoryInterface;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Mailer\MailerInterface;
+use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Symfony\Component\Routing\RouterInterface;
+use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Cache\ItemInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 use Rapsys\UserBundle\Controller\UserController as BaseUserController;
 
@@ -23,14 +40,42 @@ use Rapsys\AirBundle\Entity\GoogleCalendar;
 use Rapsys\AirBundle\Entity\GoogleToken;
 use Rapsys\AirBundle\Entity\User;
 
+use Rapsys\PackBundle\Util\SluggerUtil;
+
+use Twig\Environment;
+
 /**
  * {@inheritdoc}
  */
 class UserController extends BaseUserController {
 	/**
-	 * Set google client scopes
+	 * {@inheritdoc}
+	 *
+	 * @param CacheInterface $cache The cache instance
+	 * @param AuthorizationCheckerInterface $checker The checker instance
+	 * @param ContainerInterface $container The container instance
+	 * @param ManagerRegistry $doctrine The doctrine instance
+	 * @param FormFactoryInterface $factory The factory instance
+	 * @param UserPasswordHasherInterface $hasher The password hasher instance
+	 * @param LoggerInterface $logger The logger instance
+	 * @param MailerInterface $mailer The mailer instance
+	 * @param EntityManagerInterface $manager The manager instance
+	 * @param RouterInterface $router The router instance
+	 * @param Security $security The security instance
+	 * @param SluggerUtil $slugger The slugger instance
+	 * @param RequestStack $stack The stack instance
+	 * @param TranslatorInterface $translator The translator instance
+	 * @param Environment $twig The twig environment instance
+	 * @param Client $google The google client instance
+	 * @param integer $limit The page limit
 	 */
-	const googleScopes = [\Google\Service\Calendar::CALENDAR_EVENTS, \Google\Service\Calendar::CALENDAR, \Google\Service\Oauth2::USERINFO_EMAIL];
+	public function __construct(protected CacheInterface $cache, protected AuthorizationCheckerInterface $checker, protected ContainerInterface $container, protected ManagerRegistry $doctrine, protected FormFactoryInterface $factory, protected UserPasswordHasherInterface $hasher, protected LoggerInterface $logger, protected MailerInterface $mailer, protected EntityManagerInterface $manager, protected RouterInterface $router, protected Security $security, protected SluggerUtil $slugger, protected RequestStack $stack, protected TranslatorInterface $translator, protected Environment $twig, protected Client $google, protected int $limit = 5) {
+		//Call parent constructor
+		parent::__construct($this->cache, $this->checker, $this->container, $this->doctrine, $this->factory, $this->hasher, $this->logger, $this->mailer, $this->manager, $this->router, $this->security, $this->slugger, $this->stack, $this->translator, $this->twig, $this->limit);
+
+		//Replace google client redirect uri
+		$this->google->setRedirectUri($this->router->generate($this->google->getRedirectUri(), [], UrlGeneratorInterface::ABSOLUTE_URL));
+	}
 
 	/**
 	 * {@inheritdoc}
@@ -154,21 +199,8 @@ class UserController extends BaseUserController {
 				]
 			];
 
-			//Get google client
-			$googleClient = new \Google\Client(
-				[
-					'application_name' => $request->server->get('GOOGLE_PROJECT'),
-					'client_id' => $request->server->get('GOOGLE_CLIENT'),
-					'client_secret' => $request->server->get('GOOGLE_SECRET'),
-					'redirect_uri' => $this->generateUrl('rapsys_air_google_callback', [], UrlGeneratorInterface::ABSOLUTE_URL),
-					'scopes' => self::googleScopes,
-					'access_type' => 'offline',
-					'login_hint' => $user->getMail(),
-					//XXX: see https://stackoverflow.com/questions/10827920/not-receiving-google-oauth-refresh-token
-					#'approval_prompt' => 'force'
-					'prompt' => 'consent'
-				]
-			);
+			//Set login hint
+			$this->google->setLoginHint($user->getMail());
 
 			//With user tokens
 			if (!($googleTokens = $user->getGoogleTokens())->isEmpty()) {
@@ -177,10 +209,10 @@ class UserController extends BaseUserController {
 				foreach($googleTokens as $googleToken) {
 					//Clear client cache before changing access token
 					//TODO: set a per token cache ?
-					$googleClient->getCache()->clear();
+					$this->google->getCache()->clear();
 
 					//Set access token
-					$googleClient->setAccessToken(
+					$this->google->setAccessToken(
 						[
 							'access_token' => $googleToken->getAccess(),
 							'refresh_token' => $googleToken->getRefresh(),
@@ -190,9 +222,9 @@ class UserController extends BaseUserController {
 					);
 
 					//With expired token
-					if ($googleClient->isAccessTokenExpired()) {
+					if ($this->google->isAccessTokenExpired()) {
 						//Refresh token
-						if (($refresh = $googleClient->getRefreshToken()) && ($token = $googleClient->fetchAccessTokenWithRefreshToken($refresh)) && empty($token['error'])) {
+						if (($refresh = $this->google->getRefreshToken()) && ($token = $this->google->fetchAccessTokenWithRefreshToken($refresh)) && empty($token['error'])) {
 							//Set access token
 							$googleToken->setAccess($token['access_token']);
 
@@ -218,12 +250,39 @@ class UserController extends BaseUserController {
 								)
 							);
 
-							//Remove user token
+							//Set calendar mails
+							$cmails = [];
+
+							//Iterate on each google token calendars
+							foreach($googleToken->getGoogleCalendars() as $googleCalendar) {
+								//Add calendar mail
+								$cmails[] = $googleCalendar->getMail();
+
+								//Remove google token calendar
+								$this->manager->remove($googleCalendar);
+							}
+
+							//Log unlinked google token infos
+							$this->logger->emergency(
+								$this->translator->trans(
+									'expired: mail=%mail% gmail=%gmail% cmails=%cmails% locale=%locale%',
+									[
+										'%mail%' => $googleToken->getUser()->getMail(),
+										'%gmail%' => $googleToken->getMail(),
+										'%cmails' => implode(',', $cmails),
+										'%locale%' => $request->getLocale()
+									]
+								)
+							);
+
+							//Remove google token
 							$this->manager->remove($googleToken);
 
 							//Flush to delete it
 							$this->manager->flush();
 
+							//TODO: warn user by mail ?
+
 							//Skip to next token
 							continue;
 						}
@@ -232,26 +291,26 @@ class UserController extends BaseUserController {
 					//XXX: TODO: remove DEBUG
 					#$this->cache->delete('user.edit.calendar.'.$this->slugger->short($googleToken->getMail()));
 
-					//Get calendars
-					$calendars = $this->cache->get(
-						//Set key to user.edit.$mail
-						($calendarKey = 'user.edit.calendar.'.($googleShortMail = $this->slugger->short($googleMail = $googleToken->getMail()))),
-						//Fetch mail calendar list
-						function (ItemInterface $item) use ($googleClient): array {
-							//Expire after 1h
-							$item->expiresAfter(3600);
+					//Retrieve calendar
+					try {
+						//Get calendars
+						$calendars = $this->cache->get(
+							//Set key to user.edit.$mail
+							($calendarKey = 'user.edit.calendar.'.($googleShortMail = $this->slugger->short($googleMail = $googleToken->getMail()))),
+							//Fetch mail calendar list
+							function (ItemInterface $item): array {
+								//Expire after 1h
+								$item->expiresAfter(3600);
 
-							//Get google calendar service
-							$service = new \Google\Service\Calendar($googleClient);
+								//Get google calendar service
+								$service = new \Google\Service\Calendar($this->google);
 
-							//Init calendars
-							$calendars = [];
+								//Init calendars
+								$calendars = [];
 
-							//Init counter
-							$count = 0;
+								//Init counter
+								$count = 0;
 
-							//Retrieve calendar
-							try {
 								//Set page token
 								$pageToken = null;
 
@@ -276,16 +335,65 @@ class UserController extends BaseUserController {
 										}
 									}
 								} while ($pageToken = $calendarList->getNextPageToken());
-							//Catch exception
-							} catch(\Google\Service\Exception $e) {
-								//Throw error
-								throw new \LogicException('Calendar list failed', 0, $e);
+
+								//Cache calendars
+								return $calendars;
+							}
+						);
+					//Catch exception
+					} catch(\Google\Service\Exception $e) {
+						//With 401 or code
+						//XXX: see https://cloud.google.com/apis/design/errors
+						if ($e->getCode() == 401 || $e->getCode() == 403) {
+							//Add error in flash message
+							$this->addFlash(
+								'error',
+								$this->translator->trans(
+									'Unable to list calendars: %error%',
+									['%error%' => $e->getMessage()]
+								)
+							);
+
+							//Set calendar mails
+							$cmails = [];
+
+							//Iterate on each google token calendars
+							foreach($googleToken->getGoogleCalendars() as $googleCalendar) {
+								//Add calendar mail
+								$cmails[] = $googleCalendar->getMail();
+
+								//Remove google token calendar
+								$this->manager->remove($googleCalendar);
 							}
 
-							//Cache calendars
-							return $calendars;
+							//Log unlinked google token infos
+							$this->logger->emergency(
+								$this->translator->trans(
+									'denied: mail=%mail% gmail=%gmail% cmails=%cmails% locale=%locale%',
+									[
+										'%mail%' => $googleToken->getUser()->getMail(),
+										'%gmail%' => $googleToken->getMail(),
+										'%cmails' => implode(',', $cmails),
+										'%locale%' => $request->getLocale()
+									]
+								)
+							);
+
+							//Remove google token
+							$this->manager->remove($googleToken);
+
+							//Flush to delete it
+							$this->manager->flush();
+
+							//TODO: warn user by mail ?
+
+							//Skip to next token
+							continue;
 						}
-					);
+
+						//Throw error
+						throw new \LogicException('Calendar list failed', 0, $e);
+					}
 
 					//Set formData array
 					$formData = ['calendar' => []];
@@ -308,12 +416,8 @@ class UserController extends BaseUserController {
 						}
 					}
 
-					//XXX: TODO: remove DEBUG
-					#header('Content-Type: text/plain');
-
-					//TODO: add feature to filter synchronized data (OrganizerId/DanceId)
 					//TODO: add feature for alerts (-30min/-1h) ?
-					//[Direct link to calendar ?][Direct link to calendar settings ?][Alerts][Remove]
+					//TODO: [Direct link to calendar ?][Direct link to calendar settings ?][Alerts][Remove]
 
 					//Create the CalendarType form and give the proper parameters
 					$form = $this->factory->createNamed('calendar_'.$googleShortMail, 'Rapsys\AirBundle\Form\CalendarType', $formData, [
@@ -346,7 +450,7 @@ class UserController extends BaseUserController {
 							//Add button
 							} elseif ($clicked == 'add') {
 								//Get google calendar service
-								$service = new \Google\Service\Calendar($googleClient);
+								$service = new \Google\Service\Calendar($this->google);
 
 								//Add calendar
 								try {
@@ -374,7 +478,7 @@ class UserController extends BaseUserController {
 							//Delete button
 							} elseif ($clicked == 'delete') {
 								//Get google calendar service
-								$service = new \Google\Service\Calendar($googleClient);
+								$service = new \Google\Service\Calendar($this->google);
 
 								//Remove calendar
 								try {
@@ -415,12 +519,12 @@ class UserController extends BaseUserController {
 								$this->manager->flush();
 
 								//Revoke access token
-								$googleClient->revokeToken($googleToken->getAccess());
+								$this->google->revokeToken($googleToken->getAccess());
 
 								//With refresh token
 								if ($refresh = $googleToken->getRefresh()) {
 									//Revoke refresh token
-									$googleClient->revokeToken($googleToken->getRefresh());
+									$this->google->revokeToken($googleToken->getRefresh());
 								}
 
 								//Remove calendar key
@@ -474,7 +578,7 @@ class UserController extends BaseUserController {
 			}
 
 			//Add google calendar auth url
-			$this->config['edit']['view']['context']['calendar']['link'] = $googleClient->createAuthUrl();
+			$this->config['edit']['view']['context']['calendar']['link'] = $this->google->createAuthUrl();
 		}
 
 		//With post method
@@ -536,29 +640,20 @@ class UserController extends BaseUserController {
 		}
 
 		//Without user
-		if (empty($user = $this->getUser())) {
+		if (empty($user = $this->security->getUser())) {
 			throw new \LogicException('User is empty');
 		}
 
-		//Get google client
-		$googleClient = new \Google\Client(
-			[
-				'application_name' => $request->server->get('GOOGLE_PROJECT'),
-				'client_id' => $request->server->get('GOOGLE_CLIENT'),
-				'client_secret' => $request->server->get('GOOGLE_SECRET'),
-				'redirect_uri' => $this->generateUrl('rapsys_air_google_callback', [], UrlGeneratorInterface::ABSOLUTE_URL),
-				'scopes' => self::googleScopes,
-				'access_type' => 'offline',
-				'login_hint' => $user->getMail(),
-				#'approval_prompt' => 'force'
-				'prompt' => 'consent'
-			]
-		);
+		//Set google client login hint
+		$this->google->setLoginHint($user->getMail());
+
+		//Set google client scopes
+		$googleScopes = [\Google\Service\Calendar::CALENDAR_EVENTS, \Google\Service\Calendar::CALENDAR, \Google\Service\Oauth2::USERINFO_EMAIL];
 
 		//Protect to extract failure
 		try {
 			//Authenticate with code
-			if (!empty($token = $googleClient->authenticate($code))) {
+			if (!empty($token = $this->google->authenticate($code))) {
 				//With error
 				if (!empty($token['error'])) {
 					throw new \LogicException('Client authenticate failed: '.str_replace('_', ' ', $token['error']));
@@ -572,12 +667,12 @@ class UserController extends BaseUserController {
 				} elseif (empty($token['scope'])) {
 					throw new \LogicException('Scope in is empty');
 				//Without valid scope
-				} elseif (array_intersect(self::googleScopes, explode(' ', $token['scope'])) != self::googleScopes) {
+				} elseif (array_intersect($googleScopes, explode(' ', $token['scope'])) != $googleScopes) {
 					throw new \LogicException('Scope in is not valid');
 				}
 
 				//Get Oauth2 object
-				$oauth2 = new \Google\Service\Oauth2($googleClient);
+				$oauth2 = new \Google\Service\Oauth2($this->google);
 
 				//Protect user info get call
 				try {
@@ -598,8 +693,7 @@ class UserController extends BaseUserController {
 							if ($i->getMail() == $userInfo['email']) {
 								return $i;
 							}
-						},
-						(object)[]
+						}
 					)
 				) {
 					//Set mail
@@ -640,6 +734,6 @@ class UserController extends BaseUserController {
 		}
 
 		//Redirect to user
-		return $this->redirectToRoute('rapsys_user_edit', ['mail' => $short = $this->slugger->short($user->getMail()), 'hash' => $this->slugger->hash($short)]);
+		return $this->redirectToRoute('rapsysuser_edit', ['mail' => $short = $this->slugger->short($user->getMail()), 'hash' => $this->slugger->hash($short)]);
 	}
 }