From ff67cd5ac64e78c15f4cd0fd7580b7b4f9c2a331 Mon Sep 17 00:00:00 2001 From: aina-esokia Date: Tue, 29 May 2018 17:47:19 +0400 Subject: [PATCH] port authorize with providers --- .../Authentication/Provider/Facebook.php | 6 +- .../Authentication/Provider/Github.php | 6 +- .../Authentication/Provider/GooglePlus.php | 20 +-- .../Authentication/Provider/Linkedin.php | 6 +- .../Provider/ProviderInterface.php | 4 +- .../Authentication/Provider/Twitter.php | 6 +- .../Authentication/Provider/Viadeo.php | 6 +- .../Controller/Api/OAuth2Controller.php | 134 ++++++++++++++++++ .../Phrasea/ControllerProvider/Api/OAuth2.php | 22 +++ .../api/auth/end_user_authorization.html.twig | 20 +++ 10 files changed, 210 insertions(+), 20 deletions(-) diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Facebook.php b/lib/Alchemy/Phrasea/Authentication/Provider/Facebook.php index ceec5d4dd6..ed938e5352 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Facebook.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Facebook.php @@ -50,13 +50,15 @@ class Facebook extends AbstractProvider /** * {@inheritdoc} */ - public function authenticate() + public function authenticate(array $params = array()) { + $params = array_merge(['providerId' => $this->getId()], $params); + return new RedirectResponse( $this->facebook->getRedirectLoginHelper()->getLoginUrl( $this->generator->generate( 'login_authentication_provider_callback', - ['providerId' => $this->getId()], + $params, UrlGenerator::ABSOLUTE_URL ), ['email'] diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Github.php b/lib/Alchemy/Phrasea/Authentication/Provider/Github.php index dc3d272d39..64a8907bb5 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Github.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Github.php @@ -78,8 +78,10 @@ class Github extends AbstractProvider /** * {@inheritdoc} */ - public function authenticate() + public function authenticate(array $params = array()) { + $params = array_merge(['providerId' => $this->getId()], $params); + $state = $this->createState(); $this->session->set('github.provider.state', $state); @@ -90,7 +92,7 @@ class Github extends AbstractProvider 'state' => $state, 'redirect_uri' => $this->generator->generate( 'login_authentication_provider_callback', - ['providerId' => $this->getId()], + $params, UrlGenerator::ABSOLUTE_URL ), ], '', '&')); diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/GooglePlus.php b/lib/Alchemy/Phrasea/Authentication/Provider/GooglePlus.php index 7d65dff966..074cbcfb04 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/GooglePlus.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/GooglePlus.php @@ -41,14 +41,6 @@ class GooglePlus extends AbstractProvider 'https://www.googleapis.com/auth/userinfo.profile', ]); - $this->client->setRedirectUri( - $this->generator->generate( - 'login_authentication_provider_callback', [ - 'providerId' => $this->getId(), - ], UrlGenerator::ABSOLUTE_URL - ) - ); - $this->client->setApprovalPrompt("auto"); if ($this->session->has('google-plus.provider.token')) { @@ -115,8 +107,18 @@ class GooglePlus extends AbstractProvider /** * {@inheritdoc} */ - public function authenticate() + public function authenticate(array $params = array()) { + $params = array_merge(['providerId' => $this->getId()], $params); + + $this->client->setRedirectUri( + $this->generator->generate( + 'login_authentication_provider_callback', + $params, + UrlGenerator::ABSOLUTE_URL + ) + ); + $state = $this->createState(); $this->session->set('google-plus.provider.state', $state); diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Linkedin.php b/lib/Alchemy/Phrasea/Authentication/Provider/Linkedin.php index 1f759f36a5..0b3c5eb60c 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Linkedin.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Linkedin.php @@ -78,8 +78,10 @@ class Linkedin extends AbstractProvider /** * {@inheritdoc} */ - public function authenticate() + public function authenticate(array $params = array()) { + $params = array_merge(['providerId' => $this->getId()], $params); + $state = $this->createState(); $this->session->set('linkedin.provider.state', $state); @@ -91,7 +93,7 @@ class Linkedin extends AbstractProvider 'state' => $state, 'redirect_uri' => $this->generator->generate( 'login_authentication_provider_callback', - ['providerId' => $this->getId()], + $params, UrlGenerator::ABSOLUTE_URL ), ], '', '&')); diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/ProviderInterface.php b/lib/Alchemy/Phrasea/Authentication/Provider/ProviderInterface.php index 0478f4b25b..9178fe0aa6 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/ProviderInterface.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/ProviderInterface.php @@ -43,9 +43,11 @@ interface ProviderInterface /** * Redirects to the actual authentication provider * + * @param array $params + * * @return RedirectResponse */ - public function authenticate(); + public function authenticate(array $params); /** * Logout from the provider, removes the token if possible diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Twitter.php b/lib/Alchemy/Phrasea/Authentication/Provider/Twitter.php index fa31bef815..ff3efe744c 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Twitter.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Twitter.php @@ -69,14 +69,16 @@ class Twitter extends AbstractProvider /** * {@inheritdoc} */ - public function authenticate() + public function authenticate(array $params = array()) { + $params = array_merge(['providerId' => $this->getId()], $params); + $code = $this->twitter->request( 'POST', $this->twitter->url('oauth/request_token', ''), ['oauth_callback' => $this->generator->generate( 'login_authentication_provider_callback', - ['providerId' => $this->getId()], + $params, UrlGenerator::ABSOLUTE_URL )] ); diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Viadeo.php b/lib/Alchemy/Phrasea/Authentication/Provider/Viadeo.php index c9e25ebd82..93af8800ad 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Viadeo.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Viadeo.php @@ -79,8 +79,10 @@ class Viadeo extends AbstractProvider /** * {@inheritdoc} */ - public function authenticate() + public function authenticate(array $params = array()) { + $params = array_merge(['providerId' => $this->getId()], $params); + $state = $this->createState(); $this->session->set('viadeo.provider.state', $state); @@ -91,7 +93,7 @@ class Viadeo extends AbstractProvider 'response_type' => 'code', 'redirect_uri' => $this->generator->generate( 'login_authentication_provider_callback', - ['providerId' => $this->getId()], + $params, UrlGenerator::ABSOLUTE_URL ), ])); diff --git a/lib/Alchemy/Phrasea/Controller/Api/OAuth2Controller.php b/lib/Alchemy/Phrasea/Controller/Api/OAuth2Controller.php index a7f52bd4f6..5e131418ee 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/OAuth2Controller.php +++ b/lib/Alchemy/Phrasea/Controller/Api/OAuth2Controller.php @@ -167,6 +167,103 @@ class OAuth2Controller extends Controller return ''; } + public function authorizeWithProviderAction(Request $request, $providerId) + { + $context = new Context(Context::CONTEXT_OAUTH2_NATIVE); + $this->dispatch(PhraseaEvents::PRE_AUTHENTICATE, new PreAuthenticate($request, $context)); + + //Check for auth params, send error or redirect if not valid + $params = $this->oAuth2Adapter->getAuthorizationRequestParameters($request); + + /** @var ApiApplicationRepository $appRepository */ + $appRepository = $this->app['repo.api-applications']; + if (null === $client = $appRepository->findByClientId($params['client_id'])) { + throw new NotFoundHttpException(sprintf('Application with client id %s could not be found', $params['client_id'])); + } + + $provider = $this->findProvider($providerId); + + return $provider->authenticate($request->query->all()); + } + + public function authorizeCallbackAction(Request $request, $providerId) + { + $context = new Context(Context::CONTEXT_OAUTH2_NATIVE); + $provider = $this->findProvider($providerId); + $params = $this->oAuth2Adapter->getAuthorizationRequestParameters($request); + + // triggers what's necessary + try { + $provider->onCallback($request); + $token = $provider->getToken(); + } catch (NotAuthenticatedException $e) { + $this->getSession()->getFlashBag()->add('error', $this->app->trans('Unable to authenticate with %provider_name%', ['%provider_name%' => $provider->getName()])); + + return $this->app->redirectPath('oauth2_authorize', array_merge(array('error' => 'login'), $params)); + } + + $userAuthProvider = $this->getUserAuthProviderRepository() + ->findWithProviderAndId($token->getProvider()->getId(), $token->getId()); + + if($userAuthProvider == null){ + unset($params['state']); + + return $this->app->redirectPath('oauth2_authorize', array_merge(array('error' => 'login'), $params)); + } + + try { + $user = $this->getAuthenticationSuggestionFinder()->find($token); + } catch (NotAuthenticatedException $e) { + $this->app->addFlash('error', $this->app->trans('Unable to retrieve provider identity')); + + return $this->app->redirectPath('oauth2_authorize', array_merge(array('error' => 'login'), $params)); + } + + $this->getAuthenticator()->openAccount($userAuthProvider->getUser()); + $event = new PostAuthenticate($request, new Response(), $user, $context); + $this->dispatch(PhraseaEvents::POST_AUTHENTICATE, $event); + + /** @var ApiApplicationRepository $appRepository */ + $appRepository = $this->app['repo.api-applications']; + if (null === $client = $appRepository->findByClientId($params['client_id'])) { + throw new NotFoundHttpException(sprintf('Application with client id %s could not be found', $params['client_id'])); + } + + $this->oAuth2Adapter->setClient($client); + + //check if current client is already authorized by current user + $clients = $appRepository->findAuthorizedAppsByUser($this->getAuthenticatedUser()); + $appAuthorized = false; + + foreach ($clients as $authClient) { + if ($client->getClientId() == $authClient->getClientId()) { + $appAuthorized = true; + break; + } + } + + $account = $this->oAuth2Adapter->updateAccount($this->getAuthenticatedUser()); + + $params['account_id'] = $account->getId(); + + //if native app show template + if ($this->oAuth2Adapter->isNativeApp($params['redirect_uri'])) { + $params = $this->oAuth2Adapter->finishNativeClientAuthorization($appAuthorized, $params); + + $r = new Response($this->render("api/auth/native_app_access_token.html.twig", $params)); + $r->headers->set('Content-Type', 'text/html'); + + return $r; + } + + $this->oAuth2Adapter->finishClientAuthorization($appAuthorized, $params); + + // As OAuth2 library already outputs response content, we need to send an empty + // response to avoid breaking silex controller + return ''; + + } + /** * TOKEN ENDPOINT * Token endpoint - used to exchange an authorization grant for an access token. @@ -206,4 +303,41 @@ class OAuth2Controller extends Controller { return $this->app['manipulator.api-account']; } + + /** + * @param string $providerId + * @return ProviderInterface + */ + private function findProvider($providerId) + { + try { + return $this->getAuthenticationProviders()->get($providerId); + } catch (InvalidArgumentException $e) { + throw new NotFoundHttpException('The requested provider does not exist'); + } + } + + /** + * @return ProvidersCollection + */ + private function getAuthenticationProviders() + { + return $this->app['authentication.providers']; + } + + /** + * @return UsrAuthProviderRepository + */ + private function getUserAuthProviderRepository() + { + return $this->app['repo.usr-auth-providers']; + } + + /** + * @return SuggestionFinder + */ + private function getAuthenticationSuggestionFinder() + { + return $this->app['authentication.suggestion-finder']; + } } diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Api/OAuth2.php b/lib/Alchemy/Phrasea/ControllerProvider/Api/OAuth2.php index d22c385d88..1eb3cd5ade 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Api/OAuth2.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Api/OAuth2.php @@ -18,9 +18,12 @@ use Silex\Application; use Silex\ControllerCollection; use Silex\ControllerProviderInterface; use Silex\ServiceProviderInterface; +use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait; class OAuth2 extends Api implements ControllerProviderInterface, ServiceProviderInterface { + use ControllerProviderTrait; + public function register(Application $app) { $app['controller.oauth2'] = $app->share(function (PhraseaApplication $app) { @@ -35,6 +38,16 @@ class OAuth2 extends Api implements ControllerProviderInterface, ServiceProvider public function connect(Application $app) { + $firewall = $this->getFirewall($app); + + $requireUnauthenticated = function () use ($firewall) { + if (null !== $response = $firewall->requireNotAuthenticated()) { + return $response; + } + + return null; + }; + if (! $this->isApiEnabled($app)) { return $app['controllers_factory']; } @@ -48,6 +61,15 @@ class OAuth2 extends Api implements ControllerProviderInterface, ServiceProvider $controllers->post('/token', 'controller.oauth2:tokenAction'); + $controllers->get('/provider/{providerId}/authorize/', 'controller.oauth2:authorizeWithProviderAction') + ->before($requireUnauthenticated) + ->bind('oauth2_provider_authorize'); + + // AuthProviders callbacks + $controllers->get('/provider/{providerId}/callback/', 'controller.oauth2:authorizeCallbackAction') + ->before($requireUnauthenticated) + ->bind('login_authentication_provider_callback'); + return $controllers; } } diff --git a/templates/web/api/auth/end_user_authorization.html.twig b/templates/web/api/auth/end_user_authorization.html.twig index 494d42ecf9..7c34d3dd60 100644 --- a/templates/web/api/auth/end_user_authorization.html.twig +++ b/templates/web/api/auth/end_user_authorization.html.twig @@ -50,6 +50,26 @@ +
+
+
+ {{ "Or login with" | trans }} +
+
+
+
+
    + {% for provider in app['authentication.providers'] %} +
  • + + + +
  • + {% endfor %} +
+
+
+

{{ 'Problemes de connexion ?' | trans }}