diff --git a/README.md b/README.md index aa5f3f84..0733056f 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Included service implementations - Buffer - Dailymotion - Delicious + - Deezer - DeviantArt - Dropbox - Eve Online diff --git a/examples/deezer.php b/examples/deezer.php new file mode 100644 index 00000000..4e4bf47a --- /dev/null +++ b/examples/deezer.php @@ -0,0 +1,52 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://developers.deezer.com/api/ + */ + +use OAuth\OAuth2\Service\Deezer; +use OAuth\Common\Storage\Session; +use OAuth\Common\Consumer\Credentials; +use OAuth\Common\Http\Client\CurlClient; + +/** + * Bootstrap the example + */ +require_once __DIR__ . '/bootstrap.php'; + +// Session storage +$storage = new Session(); + +// Setup the credentials for the requests +$credentials = new Credentials( + $servicesCredentials['deezer']['key'], + $servicesCredentials['deezer']['secret'], + $currentUri->getAbsoluteUri() // Deezer require Https callback's url +); +$serviceFactory->setHttpClient(new CurlClient); +// Instantiate the Deezer service using the credentials, http client and storage mechanism for the token +/** @var $deezerService Deezer */ +$deezerService = $serviceFactory->createService('deezer', $credentials, $storage, [Deezer::SCOPE_BASIC_ACCESS, Deezer::SCOPE_OFFLINE_ACCESS, Deezer::SCOPE_EMAIL, Deezer::SCOPE_DELETE_LIBRARY]); + +if (!empty($_GET['code'])) { + // retrieve the CSRF state parameter + $state = isset($_GET['state']) ? $_GET['state'] : null; + // This was a callback request from deezer, get the token + $token = $deezerService->requestAccessToken($_GET['code'], $state); + // Show some of the resultant data + $result = json_decode($deezerService->request('user/me'), true); + echo 'Hello ' . ucfirst($result['name']) + . ' your Deezer Id is ' . $result['id']; + echo '
'; + +} elseif (!empty($_GET['go']) && $_GET['go'] === 'go') { + $url = $deezerService->getAuthorizationUri(); + header('Location: ' . $url); +} else { + $url = $currentUri->getRelativeUri() . '?go=go'; + echo "Login with Deezer!"; +} diff --git a/examples/init.example.php b/examples/init.example.php index 257913dc..969c4850 100644 --- a/examples/init.example.php +++ b/examples/init.example.php @@ -47,6 +47,10 @@ 'key' => '', 'secret' => '', ), + 'deezer' => array( + 'key' => '', + 'secret' => '', + ), 'deviantart' => array( 'key' => '', 'secret' => '', diff --git a/src/OAuth/OAuth2/Service/Deezer.php b/src/OAuth/OAuth2/Service/Deezer.php new file mode 100644 index 00000000..3e3965be --- /dev/null +++ b/src/OAuth/OAuth2/Service/Deezer.php @@ -0,0 +1,121 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://developers.deezer.com/api/ + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Deezer service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://developers.deezer.com/api/ + */ +class Deezer extends AbstractService +{ + /** + * Defined scopes + * http://developers.deezer.com/api/permissions + */ + const SCOPE_BASIC_ACCESS = 'basic_access'; // Access users basic information + const SCOPE_EMAIL = 'email'; // Get the user's email + const SCOPE_OFFLINE_ACCESS = 'offline_access'; // Access user data any time + const SCOPE_MANAGE_LIBRARY = 'manage_library'; // Manage users' library + const SCOPE_MANAGE_COMMUNITY = 'manage_community'; // Manage users' friends + const SCOPE_DELETE_LIBRARY = 'delete_library'; // Delete library items + const SCOPE_LISTENING_HISTORY = 'listening_history'; // Access the user's listening history + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.deezer.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://connect.deezer.com/oauth/auth.php'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://connect.deezer.com/oauth/access_token.php'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + if (null === $data || !is_array($data) || empty($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } elseif (isset($data['error_reason'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error_reason'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires']); + + // I hope one day Deezer add a refresh token :) + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/tests/Unit/OAuth2/Service/DeezerTest.php b/tests/Unit/OAuth2/Service/DeezerTest.php new file mode 100644 index 00000000..272212a7 --- /dev/null +++ b/tests/Unit/OAuth2/Service/DeezerTest.php @@ -0,0 +1,175 @@ +getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->assertInstanceOf('\\OAuth\\OAuth2\\Service\\ServiceInterface', $service); + } + + /** + * @covers OAuth\OAuth2\Service\Deezer::__construct + */ + public function testConstructCorrectInstanceWithoutCustomUri() + { + $service = new Deezer( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->assertInstanceOf('\\OAuth\\OAuth2\\Service\\AbstractService', $service); + } + + /** + * @covers OAuth\OAuth2\Service\Deezer::__construct + */ + public function testConstructCorrectInstanceWithCustomUri() + { + $service = new Deezer( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface'), + array(), + $this->getMock('\\OAuth\\Common\\Http\\Uri\\UriInterface') + ); + + $this->assertInstanceOf('\\OAuth\\OAuth2\\Service\\AbstractService', $service); + } + + /** + * @covers OAuth\OAuth2\Service\Deezer::__construct + * @covers OAuth\OAuth2\Service\Deezer::getAuthorizationEndpoint + */ + public function testGetAuthorizationEndpoint() + { + $service = new Deezer( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->assertSame( + 'https://connect.deezer.com/oauth/auth.php', + $service->getAuthorizationEndpoint()->getAbsoluteUri() + ); + } + + /** + * @covers OAuth\OAuth2\Service\Deezer::__construct + * @covers OAuth\OAuth2\Service\Deezer::getAccessTokenEndpoint + */ + public function testGetAccessTokenEndpoint() + { + $service = new Deezer( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->assertSame( + 'https://connect.deezer.com/oauth/access_token.php', + $service->getAccessTokenEndpoint()->getAbsoluteUri() + ); + } + + /** + * @covers OAuth\OAuth2\Service\Deezer::__construct + * @covers OAuth\OAuth2\Service\Deezer::getAuthorizationMethod + */ + public function testGetAuthorizationMethod() + { + $client = $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects($this->once())->method('retrieveResponse')->will($this->returnArgument(0)); + + $token = $this->getMock('\\OAuth\\OAuth2\\Token\\TokenInterface'); + $token->expects($this->once())->method('getEndOfLife')->will($this->returnValue(TokenInterface::EOL_NEVER_EXPIRES)); + $token->expects($this->once())->method('getAccessToken')->will($this->returnValue('foo')); + + $storage = $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface'); + $storage->expects($this->once())->method('retrieveAccessToken')->will($this->returnValue($token)); + + $service = new Deezer( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $storage + ); + + $uri = $service->request('https://pieterhordijk.com/my/awesome/path'); + $absoluteUri = parse_url($uri->getAbsoluteUri()); + + $this->assertSame('access_token=foo', $absoluteUri['query']); + } + + /** + * @covers OAuth\OAuth2\Service\Deezer::__construct + * @covers OAuth\OAuth2\Service\Deezer::parseAccessTokenResponse + */ + public function testParseAccessTokenResponseThrowsExceptionOnNulledResponse() + { + $client = $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects($this->once())->method('retrieveResponse')->will($this->returnValue(null)); + + $service = new Deezer( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->setExpectedException('\\OAuth\\Common\\Http\\Exception\\TokenResponseException'); + + $service->requestAccessToken('foo'); + } + + /** + * @covers OAuth\OAuth2\Service\Deezer::__construct + * @covers OAuth\OAuth2\Service\Deezer::parseAccessTokenResponse + */ + public function testParseAccessTokenResponseThrowsExceptionOnError() + { + $client = $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects($this->once())->method('retrieveResponse')->will($this->returnValue('error_reason=user_denied')); + + $service = new Deezer( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->setExpectedException('\\OAuth\\Common\\Http\\Exception\\TokenResponseException'); + + $service->requestAccessToken('foo'); + } + + /** + * @covers OAuth\OAuth2\Service\Deezer::__construct + * @covers OAuth\OAuth2\Service\Deezer::parseAccessTokenResponse + */ + public function testParseAccessTokenResponseValid() + { + $client = $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects($this->once())->method('retrieveResponse')->will($this->returnValue('access_token=foo&expires=bar')); + + $service = new Deezer( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->assertInstanceOf('\\OAuth\\OAuth2\\Token\\StdOAuth2Token', $service->requestAccessToken('foo')); + } +}