| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536 |
- <?php
- /*
- * This file is part of the overtrue/socialite.
- *
- * (c) overtrue <i@overtrue.me>
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
- namespace Overtrue\Socialite\Providers;
- use GuzzleHttp\Client;
- use GuzzleHttp\ClientInterface;
- use Overtrue\Socialite\AccessToken;
- use Overtrue\Socialite\AccessTokenInterface;
- use Overtrue\Socialite\AuthorizeFailedException;
- use Overtrue\Socialite\InvalidStateException;
- use Overtrue\Socialite\ProviderInterface;
- use Symfony\Component\HttpFoundation\RedirectResponse;
- use Symfony\Component\HttpFoundation\Request;
- /**
- * Class AbstractProvider.
- */
- abstract class AbstractProvider implements ProviderInterface
- {
- /**
- * Provider name.
- *
- * @var string
- */
- protected $name;
- /**
- * The HTTP request instance.
- *
- * @var \Symfony\Component\HttpFoundation\Request
- */
- protected $request;
- /**
- * The client ID.
- *
- * @var string
- */
- protected $clientId;
- /**
- * The client secret.
- *
- * @var string
- */
- protected $clientSecret;
- /**
- * @var \Overtrue\Socialite\AccessTokenInterface
- */
- protected $accessToken;
- /**
- * The redirect URL.
- *
- * @var string
- */
- protected $redirectUrl;
- /**
- * The custom parameters to be sent with the request.
- *
- * @var array
- */
- protected $parameters = [];
- /**
- * The scopes being requested.
- *
- * @var array
- */
- protected $scopes = [];
- /**
- * The separating character for the requested scopes.
- *
- * @var string
- */
- protected $scopeSeparator = ',';
- /**
- * The type of the encoding in the query.
- *
- * @var int Can be either PHP_QUERY_RFC3986 or PHP_QUERY_RFC1738
- */
- protected $encodingType = PHP_QUERY_RFC1738;
- /**
- * Indicates if the session state should be utilized.
- *
- * @var bool
- */
- protected $stateless = false;
- /**
- * Create a new provider instance.
- *
- * @param \Symfony\Component\HttpFoundation\Request $request
- * @param string $clientId
- * @param string $clientSecret
- * @param string|null $redirectUrl
- */
- public function __construct(Request $request, $clientId, $clientSecret, $redirectUrl = null)
- {
- $this->request = $request;
- $this->clientId = $clientId;
- $this->clientSecret = $clientSecret;
- $this->redirectUrl = $redirectUrl;
- }
- /**
- * Get the authentication URL for the provider.
- *
- * @param string $state
- *
- * @return string
- */
- abstract protected function getAuthUrl($state);
- /**
- * Get the token URL for the provider.
- *
- * @return string
- */
- abstract protected function getTokenUrl();
- /**
- * Get the raw user for the given access token.
- *
- * @param \Overtrue\Socialite\AccessTokenInterface $token
- *
- * @return array
- */
- abstract protected function getUserByToken(AccessTokenInterface $token);
- /**
- * Map the raw user array to a Socialite User instance.
- *
- * @param array $user
- *
- * @return \Overtrue\Socialite\User
- */
- abstract protected function mapUserToObject(array $user);
- /**
- * Redirect the user of the application to the provider's authentication screen.
- *
- * @param string $redirectUrl
- *
- * @return \Symfony\Component\HttpFoundation\RedirectResponse
- */
- public function redirect($redirectUrl = null)
- {
- $state = null;
- if (!is_null($redirectUrl)) {
- $this->redirectUrl = $redirectUrl;
- }
- if ($this->usesState()) {
- $state = $this->makeState();
- }
- return new RedirectResponse($this->getAuthUrl($state));
- }
- /**
- * {@inheritdoc}
- */
- public function user(AccessTokenInterface $token = null)
- {
- if (is_null($token) && $this->hasInvalidState()) {
- throw new InvalidStateException();
- }
- $token = $token ?: $this->getAccessToken($this->getCode());
- $user = $this->getUserByToken($token);
- $user = $this->mapUserToObject($user)->merge(['original' => $user]);
- return $user->setToken($token)->setProviderName($this->getName());
- }
- /**
- * Set redirect url.
- *
- * @param string $redirectUrl
- *
- * @return $this
- */
- public function setRedirectUrl($redirectUrl)
- {
- $this->redirectUrl = $redirectUrl;
- return $this;
- }
- /**
- * Set redirect url.
- *
- * @param string $redirectUrl
- *
- * @return $this
- */
- public function withRedirectUrl($redirectUrl)
- {
- $this->redirectUrl = $redirectUrl;
- return $this;
- }
- /**
- * Return the redirect url.
- *
- * @return string
- */
- public function getRedirectUrl()
- {
- return $this->redirectUrl;
- }
- /**
- * @param \Overtrue\Socialite\AccessTokenInterface $accessToken
- *
- * @return $this
- */
- public function setAccessToken(AccessTokenInterface $accessToken)
- {
- $this->accessToken = $accessToken;
- return $this;
- }
- /**
- * Get the access token for the given code.
- *
- * @param string $code
- *
- * @return \Overtrue\Socialite\AccessTokenInterface
- */
- public function getAccessToken($code)
- {
- if ($this->accessToken) {
- return $this->accessToken;
- }
- $postKey = (version_compare(ClientInterface::VERSION, '6') === 1) ? 'form_params' : 'body';
- $response = $this->getHttpClient()->post($this->getTokenUrl(), [
- 'headers' => ['Accept' => 'application/json'],
- $postKey => $this->getTokenFields($code),
- ]);
- return $this->parseAccessToken($response->getBody());
- }
- /**
- * Set the scopes of the requested access.
- *
- * @param array $scopes
- *
- * @return $this
- */
- public function scopes(array $scopes)
- {
- $this->scopes = $scopes;
- return $this;
- }
- /**
- * Set the request instance.
- *
- * @param Request $request
- *
- * @return $this
- */
- public function setRequest(Request $request)
- {
- $this->request = $request;
- return $this;
- }
- /**
- * Get the request instance.
- *
- * @return \Symfony\Component\HttpFoundation\Request
- */
- public function getRequest()
- {
- return $this->request;
- }
- /**
- * Indicates that the provider should operate as stateless.
- *
- * @return $this
- */
- public function stateless()
- {
- $this->stateless = true;
- return $this;
- }
- /**
- * Set the custom parameters of the request.
- *
- * @param array $parameters
- *
- * @return $this
- */
- public function with(array $parameters)
- {
- $this->parameters = $parameters;
- return $this;
- }
- /**
- * @return string
- */
- public function getName()
- {
- if (empty($this->name)) {
- $this->name = strstr((new \ReflectionClass(get_class($this)))->getShortName(), 'Provider', true);
- }
- return $this->name;
- }
- /**
- * Get the authentication URL for the provider.
- *
- * @param string $url
- * @param string $state
- *
- * @return string
- */
- protected function buildAuthUrlFromBase($url, $state)
- {
- return $url.'?'.http_build_query($this->getCodeFields($state), '', '&', $this->encodingType);
- }
- /**
- * Get the GET parameters for the code request.
- *
- * @param string|null $state
- *
- * @return array
- */
- protected function getCodeFields($state = null)
- {
- $fields = array_merge([
- 'client_id' => $this->clientId,
- 'redirect_uri' => $this->redirectUrl,
- 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
- 'response_type' => 'code',
- ], $this->parameters);
- if ($this->usesState()) {
- $fields['state'] = $state;
- }
- return $fields;
- }
- /**
- * Format the given scopes.
- *
- * @param array $scopes
- * @param string $scopeSeparator
- *
- * @return string
- */
- protected function formatScopes(array $scopes, $scopeSeparator)
- {
- return implode($scopeSeparator, $scopes);
- }
- /**
- * Determine if the current request / session has a mismatching "state".
- *
- * @return bool
- */
- protected function hasInvalidState()
- {
- if ($this->isStateless()) {
- return false;
- }
- $state = $this->request->getSession()->get('state');
- return !(strlen($state) > 0 && $this->request->get('state') === $state);
- }
- /**
- * Get the POST fields for the token request.
- *
- * @param string $code
- *
- * @return array
- */
- protected function getTokenFields($code)
- {
- return [
- 'client_id' => $this->clientId,
- 'client_secret' => $this->clientSecret,
- 'code' => $code,
- 'redirect_uri' => $this->redirectUrl,
- ];
- }
- /**
- * Get the access token from the token response body.
- *
- * @param \Psr\Http\Message\StreamInterface|array $body
- *
- * @return \Overtrue\Socialite\AccessTokenInterface
- */
- protected function parseAccessToken($body)
- {
- if (!is_array($body)) {
- $body = json_decode($body, true);
- }
- if (empty($body['access_token'])) {
- throw new AuthorizeFailedException('Authorize Failed: '.json_encode($body, JSON_UNESCAPED_UNICODE), $body);
- }
- return new AccessToken($body);
- }
- /**
- * Get the code from the request.
- *
- * @return string
- */
- protected function getCode()
- {
- return $this->request->get('code');
- }
- /**
- * Get a fresh instance of the Guzzle HTTP client.
- *
- * @return \GuzzleHttp\Client
- */
- protected function getHttpClient()
- {
- return new Client(['http_errors' => false]);
- }
- /**
- * Determine if the provider is operating with state.
- *
- * @return bool
- */
- protected function usesState()
- {
- return !$this->stateless;
- }
- /**
- * Determine if the provider is operating as stateless.
- *
- * @return bool
- */
- protected function isStateless()
- {
- return $this->stateless;
- }
- /**
- * Return array item by key.
- *
- * @param array $array
- * @param string $key
- * @param mixed $default
- *
- * @return mixed
- */
- protected function arrayItem(array $array, $key, $default = null)
- {
- if (is_null($key)) {
- return $array;
- }
- if (isset($array[$key])) {
- return $array[$key];
- }
- foreach (explode('.', $key) as $segment) {
- if (!is_array($array) || !array_key_exists($segment, $array)) {
- return $default;
- }
- $array = $array[$segment];
- }
- return $array;
- }
- /**
- * Put state to session storage and return it.
- *
- * @return string|bool
- */
- protected function makeState()
- {
- $state = sha1(uniqid(mt_rand(1, 1000000), true));
- $session = $this->request->getSession();
- if (is_callable([$session, 'put'])) {
- $session->put('state', $state);
- } elseif (is_callable([$session, 'set'])) {
- $session->set('state', $state);
- } else {
- return false;
- }
- return $state;
- }
- }
|