From 5b189276efcf02ebaeb49d85d2cd67c85853208c Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Mon, 13 May 2013 19:37:01 +0200 Subject: [PATCH] Normalize exceptions --- .../Authentication/Provider/Facebook.php | 7 +- .../Authentication/Provider/Github.php | 96 +++++++----- .../Authentication/Provider/GooglePlus.php | 62 ++++++-- .../Authentication/Provider/Linkedin.php | 99 ++++++++----- .../Provider/ProviderInterface.php | 12 +- .../Authentication/Provider/Twitter.php | 25 +++- .../Authentication/Provider/Viadeo.php | 138 +++++++++++------- .../Provider/ProviderTestCase.php | 6 +- .../Authentication/Provider/TwitterTest.php | 2 +- 9 files changed, 299 insertions(+), 148 deletions(-) diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Facebook.php b/lib/Alchemy/Phrasea/Authentication/Provider/Facebook.php index aa91461097..723c1743d7 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Facebook.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Facebook.php @@ -63,6 +63,9 @@ class Facebook extends AbstractProvider ))); } + /** + * {@inheritdoc} + */ public function logout() { $this->facebook->destroySession(); @@ -108,7 +111,7 @@ class Facebook extends AbstractProvider $identity->set(Identity::PROPERTY_USERNAME, $data['username']); } catch (\FacebookApiException $e) { - throw new RuntimeException('Unable to get profile informations', $e->getCode(), $e); + throw new NotAuthenticatedException('Unable to get profile informations', $e->getCode(), $e); } return $identity; @@ -130,7 +133,7 @@ class Facebook extends AbstractProvider public function getToken() { if (0 >= $this->facebook->getUser()) { - throw new RuntimeException('Provider has not authenticated'); + throw new NotAuthenticatedException('Provider has not authenticated'); } return new Token($this, $this->facebook->getUser()); diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Github.php b/lib/Alchemy/Phrasea/Authentication/Provider/Github.php index 7129b2a4c7..819234d2ee 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Github.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Github.php @@ -18,6 +18,7 @@ use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; use Alchemy\Phrasea\Exception\RuntimeException; use Guzzle\Http\Client as Guzzle; use Guzzle\Http\ClientInterface; +use Guzzle\Common\Exception\GuzzleException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\Generator\UrlGenerator; @@ -97,6 +98,9 @@ class Github extends AbstractProvider ), '', '&')); } + /** + * {@inheritdoc} + */ public function logout() { // GitHub does not support tokens revocation @@ -108,48 +112,64 @@ class Github extends AbstractProvider public function onCallback(Request $request) { if (!$this->session->has('github.provider.state')) { - throw new RuntimeException('Invalid state value ; CSRF try ?'); + throw new NotAuthenticatedException('No state value in session ; CSRF try ?'); } if ($request->query->get('state') !== $this->session->remove('github.provider.state')) { - throw new RuntimeException('Invalid state value ; CSRF try ?'); + throw new NotAuthenticatedException('Invalid state value ; CSRF try ?'); } - $guzzleRequest = $this->client->post('access_token'); + try { + $guzzleRequest = $this->client->post('access_token'); - $guzzleRequest->addPostFields(array( - 'code' => $request->query->get('code'), - 'redirect_uri' => $this->generator->generate( - 'login_authentication_provider_callback', - array('providerId' => $this->getId()), - UrlGenerator::ABSOLUTE_URL - ), - 'client_id' => $this->key, - 'client_secret' => $this->secret, - )); - $guzzleRequest->setHeader('Accept', 'application/json'); - $response = $guzzleRequest->send(); + $guzzleRequest->addPostFields(array( + 'code' => $request->query->get('code'), + 'redirect_uri' => $this->generator->generate( + 'login_authentication_provider_callback', + array('providerId' => $this->getId()), + UrlGenerator::ABSOLUTE_URL + ), + 'client_id' => $this->key, + 'client_secret' => $this->secret, + )); + $guzzleRequest->setHeader('Accept', 'application/json'); + $response = $guzzleRequest->send(); + } catch (GuzzleException $e) { + throw new NotAuthenticatedException('Guzzle error while authentication', $e->getCode(), $e); + } if (200 !== $response->getStatusCode()) { - throw new RuntimeException('Error while getting access_token'); + throw new NotAuthenticatedException('Error while getting access_token'); } - $data = json_decode($response->getBody(true), true); + $data = @json_decode($response->getBody(true), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Error while retrieving user info, unable to parse JSON.'); + } $this->session->remove('github.provider.state'); $this->session->set('github.provider.access_token', $data['access_token']); - $request = $this->client->get('https://api.github.com/user'); - $request->getQuery()->add('access_token', $data['access_token']); - $request->setHeader('Accept', 'application/json'); + try { + $request = $this->client->get('https://api.github.com/user'); + $request->getQuery()->add('access_token', $data['access_token']); + $request->setHeader('Accept', 'application/json'); - $response = $request->send(); - - if (200 !== $response->getStatusCode()) { - throw new RuntimeException('Error while retrieving user info'); + $response = $request->send(); + } catch (GuzzleException $e) { + throw new NotAuthenticatedException('Guzzle error while authentication', $e->getCode(), $e); } - $data = json_decode($response->getBody(true), true); + $data = @json_decode($response->getBody(true), true); + + if (200 !== $response->getStatusCode()) { + throw new NotAuthenticatedException('Error while retrieving user info, invalid status code.'); + } + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Error while retrieving user info, unable to parse JSON.'); + } $this->session->set('github.provider.id', $data['id']); } @@ -160,7 +180,7 @@ class Github extends AbstractProvider public function getToken() { if ('' === trim($this->session->get('github.provider.id'))) { - throw new RuntimeException('Github has not authenticated'); + throw new NotAuthenticatedException('Github has not authenticated'); } return new Token($this, $this->session->get('github.provider.id')); @@ -173,17 +193,25 @@ class Github extends AbstractProvider { $identity = new Identity(); - $request = $this->client->get('https://api.github.com/user'); - $request->getQuery()->add('access_token', $this->session->get('github.provider.access_token')); - $request->setHeader('Accept', 'application/json'); + try { + $request = $this->client->get('https://api.github.com/user'); + $request->getQuery()->add('access_token', $this->session->get('github.provider.access_token')); + $request->setHeader('Accept', 'application/json'); - $response = $request->send(); - - if (200 !== $response->getStatusCode()) { - throw new RuntimeException('Error while retrieving user info'); + $response = $request->send(); + } catch (GuzzleException $e) { + throw new NotAuthenticatedException('Error while retrieving user info', $e->getCode(), $e); } - $data = json_decode($response->getBody(true), true); + if (200 !== $response->getStatusCode()) { + throw new NotAuthenticatedException('Error while retrieving user info'); + } + + $data = @json_decode($response->getBody(true), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Error while parsing json'); + } list($firstname, $lastname) = explode(' ', $data['name'], 2); diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/GooglePlus.php b/lib/Alchemy/Phrasea/Authentication/Provider/GooglePlus.php index 40727f1be5..8f1d40ec6a 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/GooglePlus.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/GooglePlus.php @@ -18,6 +18,7 @@ use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; use Alchemy\Phrasea\Exception\RuntimeException; use Guzzle\Http\Client as Guzzle; use Guzzle\Http\ClientInterface; +use Guzzle\Common\Exception\GuzzleException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\Generator\UrlGenerator; @@ -147,9 +148,16 @@ class GooglePlus extends AbstractProvider return new RedirectResponse($this->client->createAuthUrl()); } + /** + * {@inheritdoc} + */ public function logout() { - $this->client->revokeToken(); + try { + $this->client->revokeToken(); + } catch (\Google_Exception $e) { + throw new RuntimeException('Unable to logout from Google+', $e->getCode(), $e); + } } /** @@ -158,17 +166,26 @@ class GooglePlus extends AbstractProvider public function onCallback(Request $request) { if (!$this->session->has('google-plus.provider.state')) { - throw new RuntimeException('Invalid state value ; CSRF try ?'); + throw new NotAuthenticatedException('No state value in session ; CSRF try ?'); } if ($request->query->get('state') !== $this->session->remove('google-plus.provider.state')) { - throw new RuntimeException('Invalid state value ; CSRF try ?'); + throw new NotAuthenticatedException('Invalid state value ; CSRF try ?'); } - $this->client->authenticate($request->query->get('code')); + try { + $this->client->authenticate($request->query->get('code')); - $token = json_decode($this->client->getAccessToken(), true); - $ticket = $this->client->verifyIdToken($token['id_token']); + $token = @json_decode($this->client->getAccessToken(), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Unable to parse Google+ JSON', $e->getCode(), $e); + } + + $ticket = $this->client->verifyIdToken($token['id_token']); + } catch (\Google_Exception $e) { + throw new NotAuthenticatedException('Unable to authenticate through Google+', $e->getCode(), $e); + } $this->session->set('google-plus.provider.token', json_encode($token)); $this->session->set('google-plus.provider.id', $ticket->getUserId()); @@ -180,7 +197,7 @@ class GooglePlus extends AbstractProvider public function getToken() { if (!ctype_digit($this->session->get('google-plus.provider.id'))) { - throw new RuntimeException('Google + has not authenticated'); + throw new NotAuthenticatedException('Google + has not authenticated'); } return new Token($this, $this->session->get('google-plus.provider.id')); @@ -193,24 +210,37 @@ class GooglePlus extends AbstractProvider { $identity = new Identity(); - $token = json_decode($this->session->get('google-plus.provider.token'), true); - $request = $this->guzzle->get(sprintf( - 'https://www.googleapis.com/oauth2/v1/tokeninfo?%s', - http_build_query(array('access_token' => $token['access_token']), '', '&') - )); - $response = $request->send(); + $token = @json_decode($this->session->get('google-plus.provider.token'), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Unable to parse Google+ JSON'); + } + + try { + $request = $this->guzzle->get(sprintf( + 'https://www.googleapis.com/oauth2/v1/tokeninfo?%s', + http_build_query(array('access_token' => $token['access_token']), '', '&') + )); + $response = $request->send(); + } catch (GuzzleException $e) { + throw new NotAuthenticatedException('Unable to retrieve Google+ tokeninfo', $e->getCode(), $e); + } if (200 !== $response->getStatusCode()) { - throw new RuntimeException('Error while retrieving user info'); + throw new NotAuthenticatedException('Error while retrieving user info'); } try{ $plusData = $this->plus->people->get('me'); } catch (\Google_Exception $e) { - throw new RuntimeException('Error while retrieving user info', $e->getCode(), $e); + throw new NotAuthenticatedException('Error while retrieving user info', $e->getCode(), $e); } - $data = json_decode($response->getBody(true), true); + $data = @json_decode($response->getBody(true), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Unable to parse Google+ JSON'); + } $identity->set(Identity::PROPERTY_EMAIL, $data['email']); diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Linkedin.php b/lib/Alchemy/Phrasea/Authentication/Provider/Linkedin.php index 660f51b732..b05d28c9a1 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Linkedin.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Linkedin.php @@ -18,6 +18,7 @@ use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; use Alchemy\Phrasea\Exception\RuntimeException; use Guzzle\Http\Client as Guzzle; use Guzzle\Http\ClientInterface; +use Guzzle\Common\Exception\GuzzleException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\Generator\UrlGenerator; @@ -98,6 +99,9 @@ class Linkedin extends AbstractProvider ), '', '&')); } + /** + * {@inheritdoc} + */ public function logout() { // LinkedIn does not provide Oauth2 token revocation @@ -109,46 +113,63 @@ class Linkedin extends AbstractProvider public function onCallback(Request $request) { if (!$this->session->has('linkedin.provider.state')) { - throw new RuntimeException('Invalid state value ; CSRF try ?'); + throw new NotAuthenticatedException('No state value ; CSRF try ?'); } if ($request->query->get('state') !== $this->session->remove('linkedin.provider.state')) { - throw new RuntimeException('Invalid state value ; CSRF try ?'); + throw new NotAuthenticatedException('Invalid state value ; CSRF try ?'); } - $guzzleRequest = $this->client->post('https://www.linkedin.com/uas/oauth2/accessToken?' . http_build_query(array( - 'grant_type' => 'authorization_code', - 'code' => $request->query->get('code'), - 'redirect_uri' => $this->generator->generate( - 'login_authentication_provider_callback', - array('providerId' => $this->getId()), - UrlGenerator::ABSOLUTE_URL - ), - 'client_id' => $this->key, - 'client_secret' => $this->secret, - ), '', '&')); - $response = $guzzleRequest->send(); + try { + $guzzleRequest = $this->client->post('https://www.linkedin.com/uas/oauth2/accessToken?' . http_build_query(array( + 'grant_type' => 'authorization_code', + 'code' => $request->query->get('code'), + 'redirect_uri' => $this->generator->generate( + 'login_authentication_provider_callback', + array('providerId' => $this->getId()), + UrlGenerator::ABSOLUTE_URL + ), + 'client_id' => $this->key, + 'client_secret' => $this->secret, + ), '', '&')); + $response = $guzzleRequest->send(); + } catch (GuzzleException $e) { + throw new NotAuthenticatedException('Unable to query LinkedIn access token', $e->getCode(), $e); + } if (200 !== $response->getStatusCode()) { - throw new RuntimeException('Error while getting access_token'); + throw new NotAuthenticatedException('Error while getting access_token'); + } + + $data = @json_decode($response->getBody(true), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Unable to parse LinkedIn JSON'); } - $data = json_decode($response->getBody(true), true); $this->session->remove('linkedin.provider.state'); $this->session->set('linkedin.provider.access_token', $data['access_token']); - $request = $this->client->get('https://api.linkedin.com/v1/people/~:(id,first-name,last-name,positions,industry,picture-url,email-address)'); - $request->getQuery() - ->add('oauth2_access_token', $data['access_token']) - ->add('format', 'json'); + try { + $request = $this->client->get('https://api.linkedin.com/v1/people/~:(id,first-name,last-name,positions,industry,picture-url,email-address)'); + $request->getQuery() + ->add('oauth2_access_token', $data['access_token']) + ->add('format', 'json'); - $response = $request->send(); - - if (200 !== $response->getStatusCode()) { - throw new RuntimeException('Error while retrieving user info'); + $response = $request->send(); + } catch (GuzzleException $e) { + throw new NotAuthenticatedException('Error while retrieving linkedin user informations.'); } - $data = json_decode($response->getBody(true), true); + if (200 !== $response->getStatusCode()) { + throw new NotAuthenticatedException('Error while retrieving user info'); + } + + $data = @json_decode($response->getBody(true), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Unable to parse LinkedIn JSON'); + } $this->session->set('linkedin.provider.id', $data['id']); } @@ -159,7 +180,7 @@ class Linkedin extends AbstractProvider public function getToken() { if ('' === trim($this->session->get('linkedin.provider.id'))) { - throw new RuntimeException('Linkedin has not authenticated'); + throw new NotAuthenticatedException('Linkedin has not authenticated'); } return new Token($this, $this->session->get('linkedin.provider.id')); @@ -172,18 +193,26 @@ class Linkedin extends AbstractProvider { $identity = new Identity(); - $request = $this->client->get('https://api.linkedin.com/v1/people/~:(id,first-name,last-name,positions,industry,picture-url;secure=true,email-address)'); - $request->getQuery() - ->add('oauth2_access_token', $this->session->get('linkedin.provider.access_token')) - ->add('format', 'json'); + try { + $request = $this->client->get('https://api.linkedin.com/v1/people/~:(id,first-name,last-name,positions,industry,picture-url;secure=true,email-address)'); + $request->getQuery() + ->add('oauth2_access_token', $this->session->get('linkedin.provider.access_token')) + ->add('format', 'json'); - $response = $request->send(); - - if (200 !== $response->getStatusCode()) { - throw new RuntimeException('Error while retrieving user info'); + $response = $request->send(); + } catch (GuzzleException $e) { + throw new NotAuthenticatedException('Unable to fetch LinkedIn identity', $e->getCode(), $e); } - $data = json_decode($response->getBody(true), true); + if (200 !== $response->getStatusCode()) { + throw new NotAuthenticatedException('Error while retrieving user info'); + } + + $data = @json_decode($response->getBody(true), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Unable to parse Linkedin JSON identity'); + } if (0 < $data['positions']['_total']) { $position = array_pop($data['positions']['values']); diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/ProviderInterface.php b/lib/Alchemy/Phrasea/Authentication/Provider/ProviderInterface.php index 5e7f97ac84..2e22ec0c16 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/ProviderInterface.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/ProviderInterface.php @@ -13,11 +13,13 @@ namespace Alchemy\Phrasea\Authentication\Provider; use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Authentication\Provider\Token\Identity; +use Alchemy\Phrasea\Authentication\Provider\Token\Token; +use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; +use Alchemy\Phrasea\Exception\RuntimeException; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Generator\UrlGenerator; use Symfony\Component\HttpFoundation\Session\SessionInterface; -use Alchemy\Phrasea\Authentication\Provider\Token\Token; interface ProviderInterface { @@ -48,6 +50,8 @@ interface ProviderInterface /** * Logout from the provider, removes the token if possible + * + * @throws RuntimeException In case logout fails. */ public function logout(); @@ -57,6 +61,8 @@ interface ProviderInterface * * @param Application $app * @param Request $request + * + * @throws NotAuthenticatedException In case the authentication failed. */ public function onCallback(Request $request); @@ -64,6 +70,8 @@ interface ProviderInterface * Returns the identity * * @return Identity + * + * @throws NotAuthenticatedException In case the provider is not connected */ public function getIdentity(); @@ -71,6 +79,8 @@ interface ProviderInterface * Returns a Token * * @return Token + * + * @throws NotAuthenticatedException In case the provider is not connected */ public function getToken(); diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Twitter.php b/lib/Alchemy/Phrasea/Authentication/Provider/Twitter.php index 45a3a76e58..eb2fcf5c0a 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Twitter.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Twitter.php @@ -84,7 +84,7 @@ class Twitter extends AbstractProvider ); if ($code != 200) { - throw new RuntimeException('Unable to request twitter token'); + throw new NotAuthenticatedException('Unable to request twitter token'); } $oauth = $this->twitter->extract_params($this->twitter->response['response']); @@ -98,6 +98,9 @@ class Twitter extends AbstractProvider )); } + /** + * {@inheritdoc} + */ public function logout() { // Twitter does no timplement token revocation @@ -120,7 +123,7 @@ class Twitter extends AbstractProvider ); if ($code != 200) { - throw new RuntimeException('Unable to get twitter access token'); + throw new NotAuthenticatedException('Unable to get twitter access token'); } $access_token = $this->twitter->extract_params($this->twitter->response['response']); @@ -135,10 +138,14 @@ class Twitter extends AbstractProvider ); if ($code != 200) { - throw new RuntimeException('Unable to get twitter credentials'); + throw new NotAuthenticatedException('Unable to get twitter credentials'); } - $resp = json_decode($this->twitter->response['response'], true); + $resp = @json_decode($this->twitter->response['response'], true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Unable to parse Twitter JSON response.'); + } $this->session->set('twitter.provider.id', $resp['id']); } @@ -149,7 +156,7 @@ class Twitter extends AbstractProvider public function getToken() { if (0 >= $this->session->get('twitter.provider.id')) { - throw new RuntimeException('Provider has not authenticated'); + throw new NotAuthenticatedException('Provider has not authenticated'); } return new Token($this, $this->session->get('twitter.provider.id')); @@ -170,10 +177,14 @@ class Twitter extends AbstractProvider ); if ($code != 200) { - throw new RuntimeException('Unable to retrieve twitter identity'); + throw new NotAuthenticatedException('Unable to retrieve twitter identity'); } - $resp = json_decode($this->twitter->response['response'], true); + $resp = @json_decode($this->twitter->response['response'], true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Unable to parse Twitter Identity JSON response.'); + } $identity = new Identity(); diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Viadeo.php b/lib/Alchemy/Phrasea/Authentication/Provider/Viadeo.php index 4b3e7494b5..9922c70b79 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Viadeo.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Viadeo.php @@ -11,12 +11,13 @@ namespace Alchemy\Phrasea\Authentication\Provider; -use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Authentication\Provider\Token\Token; use Alchemy\Phrasea\Authentication\Provider\Token\Identity; use Alchemy\Phrasea\Exception\RuntimeException; +use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; use Guzzle\Http\Client as Guzzle; use Guzzle\Http\ClientInterface; +use Guzzle\Common\Exception\GuzzleException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\Generator\UrlGenerator; @@ -96,18 +97,25 @@ class Viadeo extends AbstractProvider ))); } + /** + * {@inheritdoc} + */ public function logout() { - $request = $this->client->get('https://secure.viadeo.com/oauth-provider/revoke_access_token2'); - $request - ->getQuery() - ->add('access_token', $this->session->get('viadeo.provider.access_token')) - ->add('client_id', $this->key) - ->add('client_secret', $this->secret); + try { + $request = $this->client->get('https://secure.viadeo.com/oauth-provider/revoke_access_token2'); + $request + ->getQuery() + ->add('access_token', $this->session->get('viadeo.provider.access_token')) + ->add('client_id', $this->key) + ->add('client_secret', $this->secret); - $request->setHeader('Accept', 'application/json'); + $request->setHeader('Accept', 'application/json'); - $response = $request->send(); + $response = $request->send(); + } catch (GuzzleException $e) { + throw new RuntimeException('Unable to revoke token from Viadeo', $e->getCode(), $e); + } if (302 !== $response->getStatusCode()) { throw new RuntimeException('Error while revoking access token'); @@ -120,45 +128,61 @@ class Viadeo extends AbstractProvider public function onCallback(Request $request) { if (!$this->session->has('viadeo.provider.state')) { - throw new RuntimeException('Invalid state value ; CSRF try ?'); + throw new NotAuthenticatedException('No state value ; CSRF try ?'); } if ($request->query->get('state') !== $this->session->remove('viadeo.provider.state')) { - throw new RuntimeException('Invalid state value ; CSRF try ?'); + throw new NotAuthenticatedException('Invalid state value ; CSRF try ?'); } - $guzzleRequest = $this->client->post('https://secure.viadeo.com/oauth-provider/access_token2'); + try { + $guzzleRequest = $this->client->post('https://secure.viadeo.com/oauth-provider/access_token2'); - $guzzleRequest->addPostFields(array( - 'grant_type' => 'authorization_code', - 'code' => $request->query->get('code'), - 'redirect_uri' => $this->generator->generate('login_authentication_provider_callback', array('providerId' => $this->getId()), UrlGenerator::ABSOLUTE_URL), - 'client_id' => $this->key, - 'client_secret' => $this->secret, - )); - $guzzleRequest->setHeader('Accept', 'application/json'); - $response = $guzzleRequest->send(); + $guzzleRequest->addPostFields(array( + 'grant_type' => 'authorization_code', + 'code' => $request->query->get('code'), + 'redirect_uri' => $this->generator->generate('login_authentication_provider_callback', array('providerId' => $this->getId()), UrlGenerator::ABSOLUTE_URL), + 'client_id' => $this->key, + 'client_secret' => $this->secret, + )); + $guzzleRequest->setHeader('Accept', 'application/json'); + $response = $guzzleRequest->send(); + } catch (GuzzleException $e) { + throw new NotAuthenticatedException('Unable to retrieve viadeo access token', $e->getCode(), $e); + } if (200 !== $response->getStatusCode()) { - throw new RuntimeException('Error while getting access_token'); + throw new NotAuthenticatedException('Error while getting access_token'); } - $data = json_decode($response->getBody(true), true); + $data = @json_decode($response->getBody(true), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Unable to parse Viadeo access_token response.'); + } $this->session->remove('viadeo.provider.state'); $this->session->set('viadeo.provider.access_token', $data['access_token']); - $request = $this->client->get('https://api.viadeo.com/me?secure=true'); - $request->getQuery()->add('access_token', $data['access_token']); - $request->setHeader('Accept', 'application/json'); + try { + $request = $this->client->get('https://api.viadeo.com/me?secure=true'); + $request->getQuery()->add('access_token', $data['access_token']); + $request->setHeader('Accept', 'application/json'); - $response = $request->send(); - - if (200 !== $response->getStatusCode()) { - throw new RuntimeException('Error while retrieving user info'); + $response = $request->send(); + } catch (GuzzleException $e) { + throw new NotAuthenticatedException('Unable to retrieve viadeo user informations', $e->getCode(), $e); } - $data = json_decode($response->getBody(true), true); + if (200 !== $response->getStatusCode()) { + throw new NotAuthenticatedException('Error while retrieving user info'); + } + + $data = @json_decode($response->getBody(true), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Unable to parse Viadeo user informations response.'); + } $this->session->set('viadeo.provider.id', $data['id']); } @@ -169,7 +193,7 @@ class Viadeo extends AbstractProvider public function getToken() { if ('' === trim($this->session->get('viadeo.provider.id'))) { - throw new RuntimeException('Viadeo has not authenticated'); + throw new NotAuthenticatedException('Viadeo has not authenticated'); } return new Token($this, $this->session->get('viadeo.provider.id')); @@ -182,18 +206,26 @@ class Viadeo extends AbstractProvider { $identity = new Identity(); - $request = $this->client->get('https://api.viadeo.com/me?secure=true'); - $request->getQuery() - ->add('access_token', $this->session->get('viadeo.provider.access_token')); - $request->setHeader('Accept', 'application/json'); + try { + $request = $this->client->get('https://api.viadeo.com/me?secure=true'); + $request->getQuery() + ->add('access_token', $this->session->get('viadeo.provider.access_token')); + $request->setHeader('Accept', 'application/json'); - $response = $request->send(); - - if (200 !== $response->getStatusCode()) { - throw new RuntimeException('Error while retrieving user info'); + $response = $request->send(); + } catch (GuzzleException $e) { + throw new NotAuthenticatedException('Unable to retrieve Viadeo identity'); } - $data = json_decode($response->getBody(true), true); + if (200 !== $response->getStatusCode()) { + throw new NotAuthenticatedException('Error while retrieving user info'); + } + + $data = @json_decode($response->getBody(true), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Unable to parse Viadeo identity.'); + } $identity->set(Identity::PROPERTY_FIRSTNAME, $data['first_name']); $identity->set(Identity::PROPERTY_ID, $data['id']); @@ -201,17 +233,25 @@ class Viadeo extends AbstractProvider $identity->set(Identity::PROPERTY_LASTNAME, $data['last_name']); $identity->set(Identity::PROPERTY_USERNAME, $data['nickname']); - $request = $this->client->get('https://api.viadeo.com/me/career?secure=true'); - $request->getQuery()->add('access_token', $this->session->get('viadeo.provider.access_token')); - $request->setHeader('Accept', 'application/json'); + try { + $request = $this->client->get('https://api.viadeo.com/me/career?secure=true'); + $request->getQuery()->add('access_token', $this->session->get('viadeo.provider.access_token')); + $request->setHeader('Accept', 'application/json'); - $response = $request->send(); - - if (200 !== $response->getStatusCode()) { - throw new RuntimeException('Error while retrieving company info'); + $response = $request->send(); + } catch (GuzzleException $e) { + throw new NotAuthenticatedException('Unable to retrieve Viadeo career information.'); } - $data = json_decode($response->getBody(true), true); + if (200 !== $response->getStatusCode()) { + throw new NotAuthenticatedException('Error while retrieving company info'); + } + + $data = @json_decode($response->getBody(true), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new NotAuthenticatedException('Unable to parse Viadeo career informations.'); + } if (0 < count($data['data'])) { $job = array_shift($data['data']); diff --git a/tests/Alchemy/Tests/Phrasea/Authentication/Provider/ProviderTestCase.php b/tests/Alchemy/Tests/Phrasea/Authentication/Provider/ProviderTestCase.php index 36c7f3c030..a8a96b7c8a 100644 --- a/tests/Alchemy/Tests/Phrasea/Authentication/Provider/ProviderTestCase.php +++ b/tests/Alchemy/Tests/Phrasea/Authentication/Provider/ProviderTestCase.php @@ -90,7 +90,7 @@ abstract class ProviderTestCase extends \PHPUnit_Framework_TestCase /** * @dataProvider provideDataForFailingCallback - * @expectedException Alchemy\Phrasea\Exception\RuntimeException + * @expectedException Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException */ public function testOnCallbackWithFailure($provider, $request) { @@ -109,7 +109,7 @@ abstract class ProviderTestCase extends \PHPUnit_Framework_TestCase } /** - * @expectedException Alchemy\Phrasea\Exception\RuntimeException + * @expectedException Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException */ public function testGetTokenWhenNotAuthenticated() { @@ -129,7 +129,7 @@ abstract class ProviderTestCase extends \PHPUnit_Framework_TestCase } /** - * @expectedException Alchemy\Phrasea\Exception\RuntimeException + * @expectedException Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException */ public function testGetIdentityWhenNotAuthenticated() { diff --git a/tests/Alchemy/Tests/Phrasea/Authentication/Provider/TwitterTest.php b/tests/Alchemy/Tests/Phrasea/Authentication/Provider/TwitterTest.php index d9c080af5f..241513d13a 100644 --- a/tests/Alchemy/Tests/Phrasea/Authentication/Provider/TwitterTest.php +++ b/tests/Alchemy/Tests/Phrasea/Authentication/Provider/TwitterTest.php @@ -31,7 +31,7 @@ class TwitterTest extends ProviderTestCase } /** - * @expectedException Alchemy\Phrasea\Exception\RuntimeException + * @expectedException Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException */ public function testAuthenticateWithFailure() {