1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace League\OAuth2\Client\Provider; |
4
|
|
|
|
5
|
|
|
use League\OAuth2\Client\Exception\HostedDomainException; |
6
|
|
|
use League\OAuth2\Client\Provider\Exception\IdentityProviderException; |
7
|
|
|
use League\OAuth2\Client\Token\AccessToken; |
8
|
|
|
use League\OAuth2\Client\Tool\BearerAuthorizationTrait; |
9
|
|
|
use Psr\Http\Message\ResponseInterface; |
10
|
|
|
|
11
|
|
|
class Google extends AbstractProvider |
12
|
|
|
{ |
13
|
|
|
use BearerAuthorizationTrait; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* @var string If set, this will be sent to google as the "access_type" parameter. |
17
|
|
|
* @link https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters |
18
|
|
|
*/ |
19
|
|
|
protected $accessType; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @var string Comma-separated list of domains or domain regular expressions. |
23
|
|
|
* If only one regular value is passed, it will be sent to google as the "hd" parameter. |
24
|
|
|
* @link https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters |
25
|
|
|
*/ |
26
|
|
|
protected $hostedDomain; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var string If set, this will be sent to google as the "prompt" parameter. |
30
|
|
|
* @link https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters |
31
|
|
|
*/ |
32
|
|
|
protected $prompt; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var array List of scopes that will be used for authentication. |
36
|
|
|
* @link https://developers.google.com/identity/protocols/googlescopes |
37
|
|
|
*/ |
38
|
|
|
protected $scopes = []; |
39
|
|
|
|
40
|
|
|
public function getBaseAuthorizationUrl() |
41
|
|
|
{ |
42
|
|
|
return 'https://accounts.google.com/o/oauth2/v2/auth'; |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
public function getBaseAccessTokenUrl(array $params) |
46
|
|
|
{ |
47
|
|
|
return 'https://www.googleapis.com/oauth2/v4/token'; |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
public function getResourceOwnerDetailsUrl(AccessToken $token) |
51
|
|
|
{ |
52
|
|
|
return 'https://openidconnect.googleapis.com/v1/userinfo'; |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
protected function getAuthorizationParameters(array $options) |
56
|
|
|
{ |
57
|
|
|
if (empty($options['hd']) && $this->hostedDomain && !$this->multipleDomains()) { |
58
|
|
|
$options['hd'] = $this->hostedDomain; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
if (empty($options['access_type']) && $this->accessType) { |
62
|
|
|
$options['access_type'] = $this->accessType; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
if (empty($options['prompt']) && $this->prompt) { |
66
|
|
|
$options['prompt'] = $this->prompt; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
// The "approval_prompt" option MUST be removed to prevent conflicts with non-empty "prompt". |
70
|
|
|
if (!empty($options['prompt'])) { |
71
|
|
|
$options['approval_prompt'] = null; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
// Default scopes MUST be included for OpenID Connect. |
75
|
|
|
// Additional scopes MAY be added by constructor or option. |
76
|
|
|
$scopes = array_merge($this->getDefaultScopes(), $this->scopes); |
77
|
|
|
|
78
|
|
|
if (!empty($options['scope'])) { |
79
|
|
|
$scopes = array_merge($scopes, $options['scope']); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
$options['scope'] = array_unique($scopes); |
83
|
|
|
|
84
|
|
|
return parent::getAuthorizationParameters($options); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
protected function getDefaultScopes() |
88
|
|
|
{ |
89
|
|
|
// "openid" MUST be the first scope in the list. |
90
|
|
|
return [ |
91
|
|
|
'openid', |
92
|
|
|
'email', |
93
|
|
|
'profile', |
94
|
|
|
]; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
protected function getScopeSeparator() |
98
|
|
|
{ |
99
|
|
|
return ' '; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
protected function checkResponse(ResponseInterface $response, $data) |
103
|
|
|
{ |
104
|
|
|
// @codeCoverageIgnoreStart |
105
|
|
|
if (empty($data['error'])) { |
106
|
|
|
return; |
107
|
|
|
} |
108
|
|
|
// @codeCoverageIgnoreEnd |
109
|
|
|
|
110
|
|
|
$code = 0; |
111
|
|
|
$error = $data['error']; |
112
|
|
|
|
113
|
|
|
if (is_array($error)) { |
114
|
|
|
$code = $error['code']; |
115
|
|
|
$error = $error['message']; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
throw new IdentityProviderException($error, $code, $data); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
protected function createResourceOwner(array $response, AccessToken $token) |
122
|
|
|
{ |
123
|
|
|
$user = new GoogleUser($response); |
124
|
|
|
|
125
|
|
|
$this->assertMatchingDomains($user->getHostedDomain()); |
126
|
|
|
|
127
|
|
|
return $user; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
protected static function isDomainExpression($str) { |
131
|
|
|
return preg_match('/[\(\|\*]/', $str) && !preg_match('!/!', $str); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
protected function multipleDomains() { |
135
|
|
|
return strpos($this->hostedDomain, ',') !== FALSE || self::isDomainExpression($this->hostedDomain); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* @throws HostedDomainException If the domain does not match the configured domain. |
140
|
|
|
*/ |
141
|
|
|
protected function assertMatchingDomains($hostedDomain) |
142
|
|
|
{ |
143
|
|
|
if ($this->hostedDomain === null) { |
144
|
|
|
// No hosted domain configured. |
145
|
|
|
return; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
$domains = array_filter(explode(',', $this->hostedDomain)); |
149
|
|
|
if (! $domains) { |
|
|
|
|
150
|
|
|
// No hosted domains configured. |
151
|
|
|
return; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
foreach ($domains as $whiteListedDomain) { |
155
|
|
|
if ($this->assertMatchingDomain($whiteListedDomain, $hostedDomain)) { |
156
|
|
|
return; |
157
|
|
|
} |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
throw HostedDomainException::notMatchingDomain($this->hostedDomain); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* @return bool Whether user-originating domain equals or matches $reference. |
165
|
|
|
*/ |
166
|
|
|
protected function assertMatchingDomain($reference, $hostedDomain) |
167
|
|
|
{ |
168
|
|
|
if ($reference === '*' && $hostedDomain) { |
169
|
|
|
// Any hosted domain is allowed. |
170
|
|
|
return true; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
if ($reference === $hostedDomain) { |
174
|
|
|
// Hosted domain is correct. |
175
|
|
|
return true; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
if (self::isDomainExpression($reference) && @preg_match('/' . $reference . '/', $hostedDomain)) { |
179
|
|
|
// Hosted domain is correct. |
180
|
|
|
return true; |
181
|
|
|
} |
182
|
|
|
} |
183
|
|
|
} |
184
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.