Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Slack OAuth2 v2 #571

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ Included service implementations
- Reddit
- RunKeeper
- Salesforce
- Slack
- SoundCloud
- Spotify
- Strava
Expand Down
54 changes: 54 additions & 0 deletions examples/provider/slack.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

use OAuth\Common\Consumer\Credentials;
use OAuth\Common\Http\Client\CurlClient;
use OAuth\Common\Http\Exception\TokenResponseException;
use OAuth\Common\Storage\Session;
use OAuth\Helper\Example;
use OAuth\OAuth2\Service\Slack;

require_once __DIR__.'/../bootstrap.php';

$helper = new Example();
$storage = new Session();
$client = new CurlClient();

// actions to perform on your slack workspace :
// go to https://api.slack.com/apps and create an app
// under OAuth & Permissions set http://localhost:8000/provider/slack.php as reply url
// under Scopes add identity.basic and identity.email to recover user's data


if (empty($_GET)) {
echo $helper->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 '<a href="'.$slack->getAuthorizationUri().'">get access token</a>';
} 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() . '<br>';

$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'] . '<br>';
echo 'You logged in with slack workspace id = ' . $result['team']['id'] . '<br>';
echo "Full response :<pre>" . json_encode($result, JSON_PRETTY_PRINT) . '</pre>';

} catch (TokenResponseException $exception) {
$helper->getErrorMessage($exception);
}
echo $helper->getFooter();
}
96 changes: 96 additions & 0 deletions src/OAuth/OAuth2/Service/Slack.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

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;

/**
* Slack service.
*
* @author Christophe Garde <christophe.garde@gmail.com>
* @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;
}

}
198 changes: 198 additions & 0 deletions tests/Unit/OAuth2/Service/SlackTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<?php

namespace OAuthTest\Unit\OAuth2\Service;

use OAuth\Common\Token\TokenInterface;
use OAuth\OAuth2\Token\StdOAuth2Token;
use OAuth\OAuth2\Service\Slack;
use PHPUnit\Framework\TestCase;

class SlackTest extends TestCase
{
/**
* @covers \OAuth\OAuth2\Service\Slack::__construct
*/
public function testConstructCorrectInterfaceWithoutCustomUri(): 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\\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'));

}
}