diff --git a/Provider.php b/Provider.php index 94809b7..e1de69f 100644 --- a/Provider.php +++ b/Provider.php @@ -2,12 +2,9 @@ namespace SocialiteProviders\Slack; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\HandlerStack; use GuzzleHttp\RequestOptions; use Illuminate\Support\Arr; -use Psr\Http\Message\ResponseInterface; +use Laravel\Socialite\Two\InvalidStateException; use SocialiteProviders\Manager\OAuth2\AbstractProvider; use SocialiteProviders\Manager\OAuth2\User; @@ -15,125 +12,98 @@ class Provider extends AbstractProvider { public const IDENTIFIER = 'SLACK'; - /** - * {@inheritdoc} - */ - public function getScopes() + protected $scopes = []; + + protected array $userScopes = ['identity.basic', 'identity.email', 'identity.team', 'identity.avatar']; + + public function scopes($scopes) { - if (count($this->scopes) > 0) { - return $this->scopes; - } + $this->userScopes = array_unique(array_merge($this->userScopes, (array) $scopes)); - // Provide some default scopes if the user didn't define some. - // See: https://github.com/SocialiteProviders/Providers/pull/53 - return ['identity.basic', 'identity.email', 'identity.team', 'identity.avatar']; + return $this; } - /** - * Middleware that throws exceptions for non successful slack api calls - * "http_error" request option is set to true. - * - * @return callable Returns a function that accepts the next handler. - */ - private function getSlackApiErrorMiddleware() + public function botScopes($scopes) { - return fn (callable $handler) => function ($request, array $options) use ($handler) { - if (empty($options['http_errors'])) { - return $handler($request, $options); - } - - return $handler($request, $options)->then( - function (ResponseInterface $response) use ($request) { - $body = json_decode((string) $response->getBody(), true); - $response->getBody()->rewind(); - - if ($body['ok']) { - return $response; - } - - throw RequestException::create($request, $response); - } - ); - }; + $this->scopes = array_unique(array_merge($this->scopes, (array) $scopes)); + + return $this; } - /** - * {@inheritdoc} - */ - protected function getHttpClient() + protected function getCodeFields($state = null) { - $handler = HandlerStack::create(); - $handler->push($this->getSlackApiErrorMiddleware(), 'slack_api_errors'); + $fields = parent::getCodeFields($state); - if ($this->httpClient === null) { - $this->httpClient = new Client(['handler' => $handler]); + $fields['user_scope'] = $this->formatScopes($this->userScopes, $this->scopeSeparator); + $fields['scope'] = $this->formatScopes($this->scopes, $this->scopeSeparator); + + return $fields; + } + + public function user() + { + if ($this->user) { + return $this->user; + } + + if ($this->hasInvalidState()) { + throw new InvalidStateException; } - return $this->httpClient; + $response = $this->getAccessTokenResponse($this->getCode()); + + $user = $this->getUserByToken(Arr::get($response, 'authed_user.access_token')); + + /** @var User $userInstance */ + $userInstance = $this->userInstance($response, $user); + $userInstance->setAccessTokenResponseBody($response); + + return $userInstance; } - /** - * {@inheritdoc} - */ - protected function getAuthUrl($state) + protected function mapUserToObject(array $user) { - return $this->buildAuthUrlFromBase( - 'https://slack.com/oauth/authorize', - $state - ); + return (new User())->setRaw($user)->map([ + 'id' => Arr::get($user, 'user.id'), + 'name' => Arr::get($user, 'user.name'), + 'email' => Arr::get($user, 'user.email'), + 'avatar' => Arr::get($user, 'user.image_512'), + 'organization_id' => Arr::get($user, 'team.id'), + ]); } - /** - * {@inheritdoc} - */ - protected function getTokenUrl() + public function getAccessTokenResponse($code) { - return 'https://slack.com/api/oauth.access'; + $response = $this->getHttpClient()->post($this->getTokenUrl(), [ + RequestOptions::HEADERS => $this->getTokenHeaders($code), + RequestOptions::FORM_PARAMS => $this->getTokenFields($code), + ]); + + return json_decode($response->getBody(), true); + } + + public function getAuthUrl($state) + { + return $this->buildAuthUrlFromBase('https://slack.com/oauth/v2/authorize', $state); } /** * {@inheritdoc} */ - protected function getUserByToken($token) + protected function getTokenUrl() { - try { - $response = $this->getHttpClient()->get( - 'https://slack.com/api/users.identity', - [ - RequestOptions::HEADERS => [ - 'Authorization' => 'Bearer '.$token, - ], - ] - ); - } catch (RequestException $exception) { - // Getting user informations requires the "identity.*" scopes, however we might want to not add them to the - // scope list for various reasons. Instead of throwing an exception on this error, we return an empty user. - - if ($exception->hasResponse()) { - $data = json_decode((string) $exception->getResponse()->getBody(), true); - - if (Arr::get($data, 'error') === 'missing_scope') { - return []; - } - } - - throw $exception; - } - - return json_decode((string) $response->getBody(), true); + return 'https://slack.com/api/oauth.v2.access'; } /** * {@inheritdoc} */ - protected function mapUserToObject(array $user) + protected function getUserByToken($token) { - return (new User())->setRaw($user)->map([ - 'id' => Arr::get($user, 'user.id'), - 'name' => Arr::get($user, 'user.name'), - 'email' => Arr::get($user, 'user.email'), - 'avatar' => Arr::get($user, 'user.image_192'), - 'organization_id' => Arr::get($user, 'team.id'), + $response = $this->getHttpClient()->get('https://slack.com/api/users.identity', [ + RequestOptions::HEADERS => ['Authorization' => 'Bearer ' . $token], ]); + + return json_decode($response->getBody(), true); } }