Skip to content

Commit

Permalink
[5.x] Add Slack OpenID provider (#704)
Browse files Browse the repository at this point in the history
* feat(slack): add separate openid provider

* feat(slack): add create openid driver method

* feat(slack): specifically specify nickname as `null`

* style(slack): remove native return types to keep in style of the entire package

* style(slack): remove declare strict types to keep in style of the entire package

* style(slack): do not get contents before decoding

* test(slack): add tests for Slack OpenID provider

* formatting

---------

Co-authored-by: Taylor Otwell <taylor@laravel.com>
  • Loading branch information
maartenpaauw and taylorotwell committed May 3, 2024
1 parent 146052c commit c7b0193
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/SocialiteManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Laravel\Socialite\Two\GoogleProvider;
use Laravel\Socialite\Two\LinkedInOpenIdProvider;
use Laravel\Socialite\Two\LinkedInProvider;
use Laravel\Socialite\Two\SlackOpenIdProvider;
use Laravel\Socialite\Two\SlackProvider;
use Laravel\Socialite\Two\TwitterProvider as TwitterOAuth2Provider;
use League\OAuth1\Client\Server\Twitter as TwitterServer;
Expand Down Expand Up @@ -184,6 +185,20 @@ protected function createSlackDriver()
);
}

/**
* Create an instance of the specified driver.
*
* @return \Laravel\Socialite\Two\AbstractProvider
*/
protected function createSlackOpenidDriver()
{
$config = $this->config->get('services.slack-openid');

return $this->buildProvider(
SlackOpenIdProvider::class, $config
);
}

/**
* Build an OAuth 2 provider instance.
*
Expand Down
66 changes: 66 additions & 0 deletions src/Two/SlackOpenIdProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Laravel\Socialite\Two;

use GuzzleHttp\RequestOptions;
use Illuminate\Support\Arr;

class SlackOpenIdProvider extends AbstractProvider implements ProviderInterface
{
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = ['openid', 'email', 'profile'];

/**
* The separating character for the requested scopes.
*
* @var string
*/
protected $scopeSeparator = ' ';

/**
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://slack.com/openid/connect/authorize', $state);
}

/**
* {@inheritdoc}
*/
protected function getTokenUrl()
{
return 'https://slack.com/api/openid.connect.token';
}

/**
* {@inheritdoc}
*/
protected function getUserByToken($token)
{
$response = $this->getHttpClient()->get('https://slack.com/api/openid.connect.userInfo', [
RequestOptions::HEADERS => ['Authorization' => 'Bearer '.$token],
]);

return json_decode($response->getBody(), true);
}

/**
* {@inheritdoc}
*/
protected function mapUserToObject(array $user)
{
return (new User)->setRaw($user)->map([
'id' => Arr::get($user, 'sub'),
'nickname' => null,
'name' => Arr::get($user, 'name'),
'email' => Arr::get($user, 'email'),
'avatar' => Arr::get($user, 'picture'),
'organization_id' => Arr::get($user, 'https://slack.com/team_id'),
]);
}
}
110 changes: 110 additions & 0 deletions tests/SlackOpenIdProviderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use Illuminate\Http\Request;
use Laravel\Socialite\Contracts\User as UserContract;
use Laravel\Socialite\Two\SlackOpenIdProvider;
use Laravel\Socialite\Two\User;
use Mockery as m;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

class SlackOpenIdProviderTest extends TestCase
{
protected function tearDown(): void
{
parent::tearDown();

m::close();
}

public function test_response()
{
$user = $this->fromResponse([
'sub' => 'U1Q2W3E4R5T',
'given_name' => 'Maarten',
'picture' => 'https://secure.gravatar.com/avatar/qwerty-123.jpg?s=512',
'name' => 'Maarten Paauw',
'family_name' => 'Paauw',
'email' => 'maarten.paauw@example.com',
'https://slack.com/team_id' => 'T0P9O8I7U6Y',
]);

$this->assertInstanceOf(User::class, $user);
$this->assertSame('U1Q2W3E4R5T', $user->getId());
$this->assertNull($user->getNickname());
$this->assertSame('Maarten Paauw', $user->getName());
$this->assertSame('maarten.paauw@example.com', $user->getEmail());
$this->assertSame('https://secure.gravatar.com/avatar/qwerty-123.jpg?s=512', $user->getAvatar());

$this->assertSame([
'id' => 'U1Q2W3E4R5T',
'nickname' => null,
'name' => 'Maarten Paauw',
'email' => 'maarten.paauw@example.com',
'avatar' => 'https://secure.gravatar.com/avatar/qwerty-123.jpg?s=512',
'organization_id' => 'T0P9O8I7U6Y',
], $user->attributes);
}

public function test_missing_email_and_avatar()
{
$user = $this->fromResponse([
'sub' => 'U1Q2W3E4R5T',
'given_name' => 'Maarten',
'name' => 'Maarten Paauw',
'family_name' => 'Paauw',
'https://slack.com/team_id' => 'T0P9O8I7U6Y',
]);

$this->assertInstanceOf(User::class, $user);
$this->assertSame('U1Q2W3E4R5T', $user->getId());
$this->assertNull($user->getNickname());
$this->assertSame('Maarten Paauw', $user->getName());
$this->assertNull($user->getEmail());
$this->assertNull($user->getAvatar());

$this->assertSame([
'id' => 'U1Q2W3E4R5T',
'nickname' => null,
'name' => 'Maarten Paauw',
'email' => null,
'avatar' => null,
'organization_id' => 'T0P9O8I7U6Y',
], $user->attributes);
}

protected function fromResponse(array $response): UserContract
{
$request = m::mock(Request::class);
$request->allows('input')->with('code')->andReturns('fake-code');

$stream = m::mock(StreamInterface::class);
$stream->allows('__toString')->andReturns(json_encode(['access_token' => 'fake-token']));

$accessTokenResponse = m::mock(ResponseInterface::class);
$accessTokenResponse->allows('getBody')->andReturns($stream);

$basicProfileStream = m::mock(StreamInterface::class);
$basicProfileStream->allows('__toString')->andReturns(json_encode($response));

$basicProfileResponse = m::mock(ResponseInterface::class);
$basicProfileResponse->allows('getBody')->andReturns($basicProfileStream);

$guzzle = m::mock(Client::class);
$guzzle->expects('post')->andReturns($accessTokenResponse);
$guzzle->allows('get')->with('https://slack.com/api/openid.connect.userInfo', [
RequestOptions::HEADERS => [
'Authorization' => 'Bearer fake-token',
],
])->andReturns($basicProfileResponse);

$provider = new SlackOpenIdProvider($request, 'client_id', 'client_secret', 'redirect');
$provider->stateless();
$provider->setHttpClient($guzzle);

return $provider->user();
}
}

0 comments on commit c7b0193

Please sign in to comment.