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'));
+ }
+}