Completed
Pull Request — master (#76)
by
unknown
01:33
created

Google::isHostedDomainMultiple()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
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 If set, this will be sent to google as the "hd" parameter.
23
     *             Extra-feature: Also accept a comma-separated list of domains or domain regular expressions.
24
     *             In that case, Google connection screen is not bound to a specific hosted domain
25
      *            because no "hd" parameter is sent to Google, but domain matching is still done by this library.
26
     * @link https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters
27
     */
28
    protected $hostedDomain;
29
30
    /**
31
     * @var string If set, this will be sent to google as the "prompt" parameter.
32
     * @link https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters
33
     */
34
    protected $prompt;
35
36
    /**
37
     * @var array List of scopes that will be used for authentication.
38
     * @link https://developers.google.com/identity/protocols/googlescopes
39
     */
40
    protected $scopes = [];
41
42
    public function getBaseAuthorizationUrl()
43
    {
44
        return 'https://accounts.google.com/o/oauth2/v2/auth';
45
    }
46
47
    public function getBaseAccessTokenUrl(array $params)
48
    {
49
        return 'https://www.googleapis.com/oauth2/v4/token';
50
    }
51
52
    public function getResourceOwnerDetailsUrl(AccessToken $token)
53
    {
54
        return 'https://openidconnect.googleapis.com/v1/userinfo';
55
    }
56
57
    protected function getAuthorizationParameters(array $options)
58
    {
59
        if (empty($options['hd']) && $this->hostedDomain && !$this->isHostedDomainMultiple()) {
60
            $options['hd'] = $this->hostedDomain;
61
        }
62
63
        if (empty($options['access_type']) && $this->accessType) {
64
            $options['access_type'] = $this->accessType;
65
        }
66
67
        if (empty($options['prompt']) && $this->prompt) {
68
            $options['prompt'] = $this->prompt;
69
        }
70
71
        // The "approval_prompt" option MUST be removed to prevent conflicts with non-empty "prompt".
72
        if (!empty($options['prompt'])) {
73
            $options['approval_prompt'] = null;
74
        }
75
76
        // Default scopes MUST be included for OpenID Connect.
77
        // Additional scopes MAY be added by constructor or option.
78
        $scopes = array_merge($this->getDefaultScopes(), $this->scopes);
79
80
        if (!empty($options['scope'])) {
81
            $scopes = array_merge($scopes, $options['scope']);
82
        }
83
84
        $options['scope'] = array_unique($scopes);
85
86
        return parent::getAuthorizationParameters($options);
87
    }
88
89
    protected function getDefaultScopes()
90
    {
91
        // "openid" MUST be the first scope in the list.
92
        return [
93
            'openid',
94
            'email',
95
            'profile',
96
        ];
97
    }
98
99
    protected function getScopeSeparator()
100
    {
101
        return ' ';
102
    }
103
104
    protected function checkResponse(ResponseInterface $response, $data)
105
    {
106
        // @codeCoverageIgnoreStart
107
        if (empty($data['error'])) {
108
            return;
109
        }
110
        // @codeCoverageIgnoreEnd
111
112
        $code = 0;
113
        $error = $data['error'];
114
115
        if (is_array($error)) {
116
            $code = $error['code'];
117
            $error = $error['message'];
118
        }
119
120
        throw new IdentityProviderException($error, $code, $data);
121
    }
122
123
    protected function createResourceOwner(array $response, AccessToken $token)
124
    {
125
        $user = new GoogleUser($response);
126
127
        $this->assertMatchingDomains($user->getHostedDomain());
128
129
        return $user;
130
    }
131
132
    protected function isDomainExpression($str)
133
    {
134
        return preg_match('/[\(\|\*]/', $str) && !preg_match('!/!', $str);
135
    }
136
137
    protected function isHostedDomainMultiple()
138
    {
139
        return strpos($this->hostedDomain, ',') !== FALSE || $this->isDomainExpression($this->hostedDomain);
140
    }
141
142
    /**
143
     * @throws HostedDomainException If the domain does not match the configured domain.
144
     */
145
    protected function assertMatchingDomains($hostedDomain)
146
    {
147
        if ($this->hostedDomain === null) {
148
            // No hosted domain configured.
149
            return;
150
        }
151
152
        $domains = array_filter(explode(',', $this->hostedDomain));
153
        if (empty($domains)) {
154
            // No hosted domains configured.
155
            return;
156
        }
157
158
        foreach ($domains as $whiteListedDomain) {
159
            if ($this->assertMatchingDomain($whiteListedDomain, $hostedDomain)) {
160
                return;
161
            }
162
        }
163
164
        throw HostedDomainException::notMatchingDomain($this->hostedDomain);
165
    }
166
167
    /**
168
     * @return bool Whether user-originating domain equals or matches $reference.
169
     */
170
    protected function assertMatchingDomain($reference, $hostedDomain)
171
    {
172
        if ($reference === '*' && $hostedDomain) {
173
            // Any hosted domain is allowed.
174
            return true;
175
        }
176
177
        if ($reference === $hostedDomain) {
178
            // Hosted domain is correct.
179
            return true;
180
        }
181
182
        if ($this->isDomainExpression($reference) && @preg_match('/' . $reference . '/', $hostedDomain)) {
183
            // Hosted domain is correct.
184
            return true;
185
        }
186
    }
187
}
188