diff --git a/README.md b/README.md
index f46d0e74..5342d198 100644
--- a/README.md
+++ b/README.md
@@ -79,6 +79,7 @@ Included service implementations
- Reddit
- RunKeeper
- Salesforce
+ - Slack
- SoundCloud
- Spotify
- Strava
diff --git a/examples/provider/slack.php b/examples/provider/slack.php
new file mode 100644
index 00000000..b25ec5f3
--- /dev/null
+++ b/examples/provider/slack.php
@@ -0,0 +1,54 @@
+getContent();
+} elseif (!empty($_GET['key']) && !empty($_GET['secret']) && $_GET['oauth'] !== 'redirect') {
+ echo $helper->getHeader();
+ try {
+ $credentials = new Credentials($_GET['key'], $_GET['secret'], $helper->getCurrentUrl());
+ $slack =new Slack($credentials, $client, $storage, array(Slack::SCOPE_ID_BASIC, Slack::SCOPE_ID_EMAIL));
+
+ echo 'get access token';
+ } catch (\Exception $exception) {
+ $helper->getErrorMessage($exception);
+ }
+ echo $helper->getFooter();
+} elseif (!empty($_GET['code'])) {
+ $credentials = new Credentials($_GET['key'], $_GET['secret'], $helper->getCurrentUrl());
+ $slack =new Slack($credentials, $client, $storage, array(Slack::SCOPE_ID_BASIC, Slack::SCOPE_ID_EMAIL));
+
+ echo $helper->getHeader();
+ try {
+ $token = $slack->requestAccessToken($_GET['code']);
+ echo 'access token: ' . $token->getAccessToken() . '
';
+
+ $result = json_decode($slack->request('users.identity'), true);
+ // Show some of the resultant data
+ echo 'Your slack name is ' . $result['user']['name'] . ' and your email is ' . $result['user']['email'] . '
';
+ echo 'You logged in with slack workspace id = ' . $result['team']['id'] . '
';
+ echo "Full response :
" . json_encode($result, JSON_PRETTY_PRINT) . ''; + + } catch (TokenResponseException $exception) { + $helper->getErrorMessage($exception); + } + echo $helper->getFooter(); +} diff --git a/src/OAuth/OAuth2/Service/Slack.php b/src/OAuth/OAuth2/Service/Slack.php new file mode 100644 index 00000000..0733aa96 --- /dev/null +++ b/src/OAuth/OAuth2/Service/Slack.php @@ -0,0 +1,96 @@ + + * @link https://api.slack.com/#read_the_docs + */ +class Slack extends AbstractService +{ + // Basic + const SCOPE_ID_EMAIL = 'identity.email'; + const SCOPE_ID_BASIC = 'identity.basic'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, $stateParameterInAutUrl = true); + + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://slack.com/api/'); + } + } + + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()){ + // replace scope by user_scope + // this is a bit ugly, but still looks better than overriding the whole function :) + return str_replace('&scope=','&scope=&user_scope=',parent::getAuthorizationUri($additionalParameters)); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://slack.com/oauth/v2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://slack.com/api/oauth.v2.access'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['authed_user']['access_token']); + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + + unset($data['authed_user']['access_token']); + + $token->setExtraParams(array($data['authed_user'])); + return $token; + } + +} diff --git a/tests/Unit/OAuth2/Service/SlackTest.php b/tests/Unit/OAuth2/Service/SlackTest.php new file mode 100644 index 00000000..df0aaf13 --- /dev/null +++ b/tests/Unit/OAuth2/Service/SlackTest.php @@ -0,0 +1,198 @@ +createMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->createMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->createMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + self::assertInstanceOf('\\OAuth\\OAuth2\\Service\\ServiceInterface', $service); + } + + /** + * @covers \OAuth\OAuth2\Service\Slack::__construct + */ + public function testConstructCorrectInstanceWithoutCustomUri(): void + { + $service = new Slack( + $this->createMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->createMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->createMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + self::assertInstanceOf('\\OAuth\\OAuth2\\Service\\AbstractService', $service); + } + + /** + * @covers \OAuth\OAuth2\Service\Slack::__construct + */ + public function testConstructCorrectInstanceWithCustomUri(): void + { + $service = new Slack( + $this->createMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->createMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->createMock('\\OAuth\\Common\\Storage\\TokenStorageInterface'), + [], + $this->createMock('\\OAuth\\Common\\Http\\Uri\\UriInterface') + ); + + self::assertInstanceOf('\\OAuth\\OAuth2\\Service\\AbstractService', $service); + } + + /** + * @covers \OAuth\OAuth2\Service\Slack::__construct + * @covers \OAuth\OAuth2\Service\Slack::getAuthorizationEndpoint + */ + public function testGetAuthorizationEndpoint(): void + { + $service = new Slack( + $this->createMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->createMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->createMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + self::assertSame( + 'https://slack.com/oauth/v2/authorize', + $service->getAuthorizationEndpoint()->getAbsoluteUri() + ); + } + + /** + * @covers \OAuth\OAuth2\Service\Slack::__construct + * @covers \OAuth\OAuth2\Service\Slack::getAccessTokenEndpoint + */ + public function testGetAccessTokenEndpoint(): void + { + $service = new Slack( + $this->createMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->createMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->createMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + self::assertSame( + 'https://slack.com/api/oauth.v2.access', + $service->getAccessTokenEndpoint()->getAbsoluteUri() + ); + } + + /** + * @covers \OAuth\OAuth2\Service\Slack::__construct + * @covers \OAuth\OAuth2\Service\Slack::getAuthorizationMethod + */ + public function testGetAuthorizationMethod(): void + { + $client = $this->createMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects(self::once())->method('retrieveResponse')->willReturnArgument(2); + + $token = $this->createMock('\\OAuth\\OAuth2\\Token\\TokenInterface'); + $token->expects(self::once())->method('getEndOfLife')->willReturn(TokenInterface::EOL_NEVER_EXPIRES); + $token->expects(self::once())->method('getAccessToken')->willReturn('foo'); + + $storage = $this->createMock('\\OAuth\\Common\\Storage\\TokenStorageInterface'); + $storage->expects(self::once())->method('retrieveAccessToken')->willReturn($token); + + $service = new Slack( + $this->createMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $storage + ); + + $headers = $service->request('https://pieterhordijk.com/my/awesome/path'); + self::assertArrayHasKey('Authorization', $headers); + self::assertTrue(in_array('Bearer foo', $headers, true)); + } + + /** + * @covers \OAuth\OAuth2\Service\Slack::__construct + * @covers \OAuth\OAuth2\Service\Slack::parseAccessTokenResponse + */ + public function testParseAccessTokenResponseThrowsExceptionOnNulledResponse(): void + { + $client = $this->createMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects(self::once())->method('retrieveResponse')->willReturn(null); + + $service = new Slack( + $this->createMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $this->createMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->expectException('\\OAuth\\Common\\Http\\Exception\\TokenResponseException'); + + $service->requestAccessToken('foo'); + } + + /** + * @covers \OAuth\OAuth2\Service\Slack::__construct + * @covers \OAuth\OAuth2\Service\Slack::parseAccessTokenResponse + */ + public function testParseAccessTokenResponseThrowsExceptionOnErrorDescription(): void + { + $client = $this->createMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects(self::once())->method('retrieveResponse')->willReturn('error_description=some_error'); + + $service = new Slack( + $this->createMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $this->createMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->expectException('\\OAuth\\Common\\Http\\Exception\\TokenResponseException'); + + $service->requestAccessToken('foo'); + } + + /** + * @covers \OAuth\OAuth2\Service\Slack::__construct + * @covers \OAuth\OAuth2\Service\Slack::parseAccessTokenResponse + */ + public function testParseAccessTokenResponseThrowsExceptionOnError(): void + { + $client = $this->createMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects(self::once())->method('retrieveResponse')->willReturn('error=some_error'); + + $service = new Slack( + $this->createMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $this->createMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->expectException('\\OAuth\\Common\\Http\\Exception\\TokenResponseException'); + + $service->requestAccessToken('foo'); + } + + /** + * @covers \OAuth\OAuth2\Service\Slack::__construct + * @covers \OAuth\OAuth2\Service\Slack::parseAccessTokenResponse + */ + public function testParseAccessTokenResponseValidWithoutRefreshToken(): void + { + + $client = $this->createMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects(self::once())->method('retrieveResponse')->willReturn('{"access_token":"foo","expires_in":"bar"}'); + + $service = new Slack( + $this->createMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $this->createMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + self::assertInstanceOf('\\OAuth\\OAuth2\\Token\\StdOAuth2Token', $service->requestAccessToken('foo')); + + } +}