Skip to content

Commit c5db278

Browse files
committed
:octocat: Steam provider cleanup
1 parent 2a55cc3 commit c5db278

File tree

3 files changed

+150
-35
lines changed

3 files changed

+150
-35
lines changed

examples/get-token/Steam.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
header('Location: '.$provider->getAuthorizationURL());
2626
}
2727
// step 3: receive the access token
28-
elseif(isset($_GET['openid_sig']) && isset($_GET['openid_signed'])){
28+
elseif(isset($_GET['openid_sig'], $_GET['openid_signed'], $_GET['openid_claimed_id'])){
2929
// the Steam provider takes the whole $_GET array as it uses multiple of the query parameters
3030
$token = $provider->getAccessToken($_GET);
3131

@@ -44,11 +44,10 @@
4444
// use the file storage from now on
4545
$provider->setStorage($factory->getFileStorage());
4646

47-
$data = $provider->me();
48-
$token = $provider->getAccessTokenFromStorage(); // the user's steamid is stored as access token
49-
$tokenJSON = $token->toJSON();
47+
$token = $provider->getAccessTokenFromStorage(); // the user's steamid is stored as access token
5048

51-
printf('<pre>%s</pre><textarea cols="120" rows="5" onclick="this.select();">%s</textarea>', $data, $tokenJSON);
49+
printf('<pre>%s</pre>', print_r($provider->me(), true));
50+
printf('<textarea cols="120" rows="5" onclick="this.select();">%s</textarea>', $token->toJSON());
5251
}
5352
// step 1 (optional): display a login link
5453
else{

src/Providers/Steam.php

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@
1111

1212
namespace chillerlan\OAuth\Providers;
1313

14-
use chillerlan\HTTP\Utils\QueryUtil;
14+
use chillerlan\HTTP\Utils\{MessageUtil, QueryUtil, UriUtil};
1515
use chillerlan\OAuth\Core\{AccessToken, AuthenticatedUser, OAuthProvider, UserInfo};
16-
use chillerlan\HTTP\Utils\UriUtil;
1716
use Psr\Http\Message\{RequestInterface, ResponseInterface, UriInterface};
18-
use function explode, intval, preg_replace;
17+
use function explode, intval, str_replace;
1918

2019
/**
2120
* Steam OpenID
2221
*
2322
* @see https://steamcommunity.com/dev
2423
* @see https://partner.steamgames.com/doc/webapi_overview
24+
* @see https://partner.steamgames.com/doc/features/auth
2525
* @see https://steamwebapi.azurewebsites.net/
2626
*/
2727
class Steam extends OAuthProvider implements UserInfo{
@@ -54,9 +54,22 @@ public function getAuthorizationURL(array|null $params = null, array|null $scope
5454
}
5555

5656
/**
57-
*
57+
* Obtains an "authentication token" (the steamID64)
58+
*/
59+
public function getAccessToken(array $urlQuery):AccessToken{
60+
$body = $this->getAccessTokenRequestBodyParams($urlQuery);
61+
$response = $this->sendAccessTokenRequest($this->accessTokenURL, $body);
62+
$token = $this->parseTokenResponse($response, $urlQuery['openid_claimed_id']);
63+
64+
$this->storage->storeAccessToken($token, $this->name);
65+
66+
return $token;
67+
}
68+
69+
/**
70+
* prepares the request body parameters for the access token request
5871
*/
59-
public function getAccessToken(array $received):AccessToken{
72+
protected function getAccessTokenRequestBodyParams(array $received):array{
6073

6174
$body = [
6275
'openid.mode' => 'check_authentication',
@@ -68,33 +81,27 @@ public function getAccessToken(array $received):AccessToken{
6881
$body['openid.'.$item] = $received['openid_'.$item];
6982
}
7083

84+
return $body;
85+
}
86+
87+
/**
88+
* sends a request to the access token endpoint $url with the given $params as URL query
89+
*/
90+
protected function sendAccessTokenRequest(string $url, array $body):ResponseInterface{
91+
7192
$request = $this->requestFactory
72-
->createRequest('POST', $this->accessTokenURL)
93+
->createRequest('POST', $url)
7394
->withHeader('Content-Type', 'application/x-www-form-urlencoded')
7495
->withBody($this->streamFactory->createStream(QueryUtil::build($body)));
7596

76-
$token = $this->parseTokenResponse($this->http->sendRequest($request));
77-
$id = preg_replace('/[^\d]/', '', $received['openid_claimed_id']);
78-
79-
// as this method is intended for one-time authentication only we'll not receive a token.
80-
// instead we're gonna save the verified steam user id as token as it is required
81-
// for several "authenticated" endpoints.
82-
$token->accessToken = $id;
83-
$token->extraParams = [
84-
'claimed_id' => $received['openid_claimed_id'],
85-
'id_int' => intval($id),
86-
];
87-
88-
$this->storage->storeAccessToken($token, $this->name);
89-
90-
return $token;
97+
return $this->http->sendRequest($request);
9198
}
9299

93100
/**
94101
* @throws \chillerlan\OAuth\Providers\ProviderException
95102
*/
96-
protected function parseTokenResponse(ResponseInterface $response):AccessToken{
97-
$data = explode("\x0a", (string)$response->getBody());
103+
protected function parseTokenResponse(ResponseInterface $response, string $claimed_id):AccessToken{
104+
$data = explode("\x0a", MessageUtil::getContents($response));
98105

99106
if(!isset($data[1]) || !str_starts_with($data[1], 'is_valid')){
100107
throw new ProviderException('unable to parse token response');
@@ -104,17 +111,24 @@ protected function parseTokenResponse(ResponseInterface $response):AccessToken{
104111
throw new ProviderException('invalid id');
105112
}
106113

107-
// the response is only validation, so we'll just return an empty token and add the id in the next step
108114
$token = $this->createAccessToken();
115+
$id = str_replace('https://steamcommunity.com/openid/id/', '', $claimed_id);
109116

110-
$token->accessToken = 'SteamID';
117+
// as this method is intended for one-time authentication only we'll not receive a token.
118+
// instead we're gonna save the verified steam user id as token as it is required
119+
// for several "authenticated" endpoints.
120+
$token->accessToken = $id;
111121
$token->expires = AccessToken::NEVER_EXPIRES;
122+
$token->extraParams = [
123+
'claimed_id' => $claimed_id,
124+
'id_int' => intval($id),
125+
];
112126

113127
return $token;
114128
}
115129

116130
/**
117-
*
131+
* @inheritDoc
118132
*/
119133
public function getRequestAuthorization(RequestInterface $request, AccessToken|null $token = null):RequestInterface{
120134
$uri = UriUtil::withQueryValue($request->getUri(), 'key', $this->options->secret);

tests/Providers/Unit/SteamTest.php

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,125 @@
1111

1212
namespace chillerlan\OAuthTest\Providers\Unit;
1313

14+
use chillerlan\HTTP\Utils\MessageUtil;
15+
use chillerlan\OAuth\Providers\ProviderException;
1416
use chillerlan\OAuth\Providers\Steam;
17+
use function rawurlencode, sprintf;
1518

1619
/**
1720
* @property \chillerlan\OAuth\Providers\Steam $provider
1821
*/
1922
final class SteamTest extends OAuthProviderUnitTestAbstract{
2023

24+
protected const ID_VALID = "ns:http://specs.openid.net/auth/2.0\x0ais_valid:true\x0a";
25+
protected const ID_INVALID = "ns:http://specs.openid.net/auth/2.0\x0ais_valid:false\x0a";
26+
27+
// array from $_GET during the callback
28+
protected const OPENID_CALLBACK = [
29+
'openid_ns' => 'http://specs.openid.net/auth/2.0',
30+
'openid_mode' => 'id_res',
31+
'openid_op_endpoint' => 'https://steamcommunity.com/openid/login',
32+
'openid_claimed_id' => 'https://steamcommunity.com/openid/id/69420',
33+
'openid_identity' => 'https://steamcommunity.com/openid/id/69420',
34+
'openid_return_to' => 'https://smiley.codes/oauth/',
35+
'openid_response_nonce' => '2021-03-16T06:40:46ZtLLZ4JqhLZ2IULBg8x2P8YitHQY=',
36+
'openid_assoc_handle' => '1234567890',
37+
'openid_signed' => 'signed,op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle',
38+
'openid_sig' => '7WEtj64YlaJLNqL6M0gZvVmOLFg=',
39+
];
40+
41+
2142
protected function getProviderFQCN():string{
2243
return Steam::class;
2344
}
2445

25-
public function testRequest():void{
26-
$this::markTestIncomplete();
46+
/*
47+
* auth URL
48+
*/
49+
50+
public function testGetAuthURL():void{
51+
$query = $this->provider->getAuthorizationURL()->getQuery();
52+
53+
$this::assertStringContainsString('openid.mode=checkid_setup', $query);
54+
$this::assertStringContainsString(sprintf('openid.return_to=%s', rawurlencode($this->options->callbackURL)), $query);
55+
$this::assertStringContainsString(sprintf('openid.realm=%s', rawurlencode($this->options->key)), $query);
56+
}
57+
58+
59+
/*
60+
* token response parser
61+
*/
62+
63+
public function testParseAccessTokenResponse():void{
64+
$body = $this->streamFactory->createStream($this::ID_VALID);
65+
$response = $this->responseFactory->createResponse()->withBody($body);
66+
67+
/** @var \chillerlan\OAuth\Core\AccessToken $token */
68+
$token = $this->invokeReflectionMethod('parseTokenResponse', [$response, 'https://steamcommunity.com/openid/id/69420']);
69+
70+
$this::assertSame('69420', $token->accessToken);
71+
}
72+
73+
public function testParseTokenResponseNoDataException():void{
74+
$this->expectException(ProviderException::class);
75+
$this->expectExceptionMessage('unable to parse token response');
76+
77+
$this->invokeReflectionMethod('parseTokenResponse', [$this->responseFactory->createResponse(), 'nope']);
78+
}
79+
80+
public function testParseTokenResponseInvalidIdException():void{
81+
$this->expectException(ProviderException::class);
82+
$this->expectExceptionMessage('invalid id');
83+
84+
$body = $this->streamFactory->createStream($this::ID_INVALID);
85+
$response = $this->responseFactory->createResponse()->withBody($body);
86+
87+
$this->invokeReflectionMethod('parseTokenResponse', [$response, 'nope']);
2788
}
2889

29-
public function testMeUnknownErrorException():void{
30-
$this->markTestSkipped('N/A');
90+
91+
/*
92+
* access token
93+
*/
94+
95+
public function testGetAccessToken():void{
96+
$this->setMockResponse($this->streamFactory->createStream($this::ID_VALID));
97+
98+
$token = $this->provider->getAccessToken($this::OPENID_CALLBACK);
99+
100+
$this->assertSame('69420', $token->accessToken);
101+
}
102+
103+
public function testGetAccessTokenRequestBodyParams():void{
104+
$params = $this->invokeReflectionMethod('getAccessTokenRequestBodyParams', [$this::OPENID_CALLBACK]);
105+
106+
$this::assertArrayHasKey('openid.sig', $params);
107+
$this::assertSame('check_authentication', $params['openid.mode']);
108+
}
109+
110+
111+
/*
112+
* request
113+
*/
114+
115+
public function testRequest():void{
116+
117+
$response = $this->provider
118+
->storeAccessToken($this->getTestToken())
119+
->request(
120+
path : '/',
121+
method : 'post',
122+
// coverage
123+
body : ['foo' => 'bar'],
124+
headers : ['Content-Type' => 'application/json'],
125+
protocolVersion: '1.1',
126+
)
127+
;
128+
129+
$data = MessageUtil::decodeJSON($response, true);
130+
131+
$this::assertSame('{"foo":"bar"}', $data['body']);
132+
$this::assertArrayHasKey('key', $data['request']['params']);
31133
}
32134

33135
public function testGetRequestAuthorizationInvalidTokenException():void{

0 commit comments

Comments
 (0)