From ef25e725a12f99c31494a035e417b4668256a2f5 Mon Sep 17 00:00:00 2001 From: atymic Date: Sun, 31 May 2020 11:38:16 +1000 Subject: [PATCH] feat: re implement steam provider (#415) --- Provider.php | 240 ++++++++++++++++++++++++++++++++++++++++++-------- composer.json | 3 +- 2 files changed, 206 insertions(+), 37 deletions(-) diff --git a/Provider.php b/Provider.php index 4645375..a41e2ea 100644 --- a/Provider.php +++ b/Provider.php @@ -2,11 +2,18 @@ namespace SocialiteProviders\Steam; +use GuzzleHttp\RequestOptions; +use Illuminate\Http\Request; use Illuminate\Support\Arr; -use LightOpenID; +use RuntimeException; use SocialiteProviders\Manager\OAuth2\AbstractProvider; use SocialiteProviders\Manager\OAuth2\User; +/** + * Steam socialite provider, based on `laravel-steam-auth` by @invisnik. + * + * @see https://github.com/invisnik/laravel-steam-auth + */ class Provider extends AbstractProvider { /** @@ -15,37 +22,56 @@ class Provider extends AbstractProvider const IDENTIFIER = 'STEAM'; /** - * {@inheritdoc} + * @var string */ - protected $stateless = true; + public $steamId; /** - * Returns the Open ID object. - * - * @return \LightOpenID + * @var array */ - protected function getOpenID() - { - $openID = new LightOpenID( - $redirect = $this->redirectUrl, - $this->getConfig('proxy') - ); + protected $customRequestOptions = []; - $openID->returnUrl = $redirect; + /** + * @var string + */ + const OPENID_URL = 'https://steamcommunity.com/openid/login'; - return $openID; - } + /** + * @var string + */ + const STEAM_INFO_URL = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=%s&steamids=%s'; + + /** + * @var string + */ + const OPENID_SIG = 'openid_sig'; + + /** + * @var string + */ + const OPENID_SIGNED = 'openid_signed'; + + /** + * @var string + */ + const OPENID_ASSOC_HANDLE = 'openid_assoc_handle'; + + /** + * @var string + */ + const OPENID_NS = 'http://specs.openid.net/auth/2.0'; + + /** + * {@inheritdoc} + */ + protected $stateless = true; /** * {@inheritdoc} */ protected function getAuthUrl($state) { - $openID = $this->getOpenID(); - - $openID->identity = 'https://steamcommunity.com/openid'; - - return $openID->authUrl(); + return $this->buildUrl(); } /** @@ -53,17 +79,11 @@ protected function getAuthUrl($state) */ public function user() { - $openID = $this->getOpenID(); - - if (!$openID->validate()) { - throw new OpenIDValidationException(); + if (!$this->validate()) { + throw new OpenIDValidationException('Failed to validate openID login'); } - $user = $this->mapUserToObject($this->getUserByToken( - $this->parseAccessToken($openID->identity) - )); - - return $user; + return $this->mapUserToObject($this->getUserByToken($this->steamId)); } /** @@ -71,9 +91,7 @@ public function user() */ protected function parseAccessToken($body) { - preg_match('/\/id\/(\d+)$/i', $body, $matches); - - return $matches[1]; + return null; } /** @@ -81,8 +99,17 @@ protected function parseAccessToken($body) */ protected function getUserByToken($token) { - $response = $this->getHttpClient()->get( - sprintf('https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=%s&steamids=%s', $this->clientSecret, $token) + if (is_null($this->steamId)) { + return null; + } + + if (empty($this->getConfig('api_key'))) { + throw new RuntimeException('The Steam API key has not been specified.'); + } + + $response = $this->getHttpClient()->request( + 'GET', + sprintf(self::STEAM_INFO_URL, $this->getConfig('api_key'), $this->steamId) ); $contents = json_decode($response->getBody()->getContents(), true); @@ -104,6 +131,149 @@ protected function mapUserToObject(array $user) ]); } + /** + * Build the Steam login URL. + * + * @return string + */ + private function buildUrl() + { + $realm = $this->getConfig('realm', $this->request->server('HTTP_HOST')); + + $params = [ + 'openid.ns' => self::OPENID_NS, + 'openid.mode' => 'checkid_setup', + 'openid.return_to' => $this->redirectUrl, + 'openid.realm' => sprintf('https://%s', $realm), + 'openid.identity' => 'http://specs.openid.net/auth/2.0/identifier_select', + 'openid.claimed_id' => 'http://specs.openid.net/auth/2.0/identifier_select', + ]; + + return self::OPENID_URL.'?'.http_build_query($params, '', '&'); + } + + /** + * Checks the steam login. + * + * @return bool + */ + public function validate() + { + if (!$this->requestIsValid()) { + return false; + } + + $requestOptions = $this->getDefaultRequestOptions(); + $customOptions = $this->getCustomRequestOptions(); + + if (!empty($customOptions) && is_array($customOptions)) { + $requestOptions = array_merge($requestOptions, $customOptions); + } + + $response = $this->getHttpClient()->request('POST', self::OPENID_URL, $requestOptions); + + $results = $this->parseResults($response->getBody()->getContents()); + + $this->parseSteamID(); + + return $results['is_valid'] === 'true'; + } + + /** + * Validates if the request object has required stream attributes. + * + * @return bool + */ + private function requestIsValid() + { + return $this->request->has(self::OPENID_ASSOC_HANDLE) + && $this->request->has(self::OPENID_SIGNED) + && $this->request->has(self::OPENID_SIG); + } + + /** + * @return array + */ + public function getDefaultRequestOptions() + { + return [ + RequestOptions::FORM_PARAMS => $this->getParams(), + ]; + } + + /** + * @return array + */ + public function getCustomRequestOptions() + { + return $this->customRequestOptions; + } + + /** + * Get param list for openId validation. + * + * @return array + */ + public function getParams() + { + $params = [ + 'openid.assoc_handle' => $this->request->get(self::OPENID_ASSOC_HANDLE), + 'openid.signed' => $this->request->get(self::OPENID_SIGNED), + 'openid.sig' => $this->request->get(self::OPENID_SIG), + 'openid.ns' => self::OPENID_NS, + 'openid.mode' => 'check_authentication', + ]; + + $signedParams = explode(',', $this->request->get(self::OPENID_SIGNED)); + + foreach ($signedParams as $item) { + $value = $this->request->get('openid_'.str_replace('.', '_', $item)); + $params['openid.'.$item] = $value; + } + + return $params; + } + + /** + * Parse openID response to an array. + * + * @param string $results openid response body + * + * @return array + */ + public function parseResults($results) + { + $parsed = []; + $lines = explode("\n", $results); + + foreach ($lines as $line) { + if (empty($line)) { + continue; + } + + $line = explode(':', $line, 2); + $parsed[$line[0]] = $line[1]; + } + + return $parsed; + } + + /** + * Parse the steamID from the OpenID response. + * + * @return void + */ + public function parseSteamID() + { + preg_match( + '#^https?://steamcommunity.com/openid/id/([0-9]{17,25})#', + $this->request->get('openid_claimed_id'), + $matches + ); + + $this->steamId = is_numeric($matches[1]) ? $matches[1] : 0; + } + /** * {@inheritdoc} */ @@ -120,6 +290,6 @@ protected function getTokenUrl() public static function additionalConfigKeys() { - return ['proxy']; + return ['api_key', 'realm', 'https']; } } diff --git a/composer.json b/composer.json index 0b529e5..84f666e 100644 --- a/composer.json +++ b/composer.json @@ -9,8 +9,7 @@ "require": { "php": "^5.6 || ^7.0", "ext-json": "*", - "socialiteproviders/manager": "~2.0 || ~3.0", - "iignatov/lightopenid": "~1.0" + "socialiteproviders/manager": "~2.0 || ~3.0" }, "autoload": { "psr-4": {