Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
103 - 1
<?php
2
/*
3
 * Copyright 2015 Google Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *     http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
 
18
namespace Google\Auth\Credentials;
19
 
20
use Google\Auth\CredentialsLoader;
21
use Google\Auth\HttpHandler\HttpHandlerFactory;
22
use GuzzleHttp\Exception\ClientException;
23
use GuzzleHttp\Exception\RequestException;
24
use GuzzleHttp\Exception\ServerException;
25
use GuzzleHttp\Psr7\Request;
26
 
27
/**
28
 * GCECredentials supports authorization on Google Compute Engine.
29
 *
30
 * It can be used to authorize requests using the AuthTokenMiddleware, but will
31
 * only succeed if being run on GCE:
32
 *
33
 *   use Google\Auth\Credentials\GCECredentials;
34
 *   use Google\Auth\Middleware\AuthTokenMiddleware;
35
 *   use GuzzleHttp\Client;
36
 *   use GuzzleHttp\HandlerStack;
37
 *
38
 *   $gce = new GCECredentials();
39
 *   $middleware = new AuthTokenMiddleware($gce);
40
 *   $stack = HandlerStack::create();
41
 *   $stack->push($middleware);
42
 *
43
 *   $client = new Client([
44
 *      'handler' => $stack,
45
 *      'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/',
46
 *      'auth' => 'google_auth'
47
 *   ]);
48
 *
49
 *   $res = $client->get('myproject/taskqueues/myqueue');
50
 */
51
class GCECredentials extends CredentialsLoader
52
{
53
    const cacheKey = 'GOOGLE_AUTH_PHP_GCE';
54
    /**
55
     * The metadata IP address on appengine instances.
56
     *
57
     * The IP is used instead of the domain 'metadata' to avoid slow responses
58
     * when not on Compute Engine.
59
     */
60
    const METADATA_IP = '169.254.169.254';
61
 
62
    /**
63
     * The metadata path of the default token.
64
     */
65
    const TOKEN_URI_PATH = 'v1/instance/service-accounts/default/token';
66
 
67
    /**
68
     * The header whose presence indicates GCE presence.
69
     */
70
    const FLAVOR_HEADER = 'Metadata-Flavor';
71
 
72
    /**
73
     * Flag used to ensure that the onGCE test is only done once;.
74
     *
75
     * @var bool
76
     */
77
    private $hasCheckedOnGce = false;
78
 
79
    /**
80
     * Flag that stores the value of the onGCE check.
81
     *
82
     * @var bool
83
     */
84
    private $isOnGce = false;
85
 
86
    /**
87
     * Result of fetchAuthToken.
88
     */
89
    protected $lastReceivedToken;
90
 
91
    /**
92
     * The full uri for accessing the default token.
93
     *
94
     * @return string
95
     */
96
    public static function getTokenUri()
97
    {
98
        $base = 'http://' . self::METADATA_IP . '/computeMetadata/';
99
 
100
        return $base . self::TOKEN_URI_PATH;
101
    }
102
 
103
    /**
104
     * Determines if this an App Engine Flexible instance, by accessing the
105
     * GAE_VM environment variable.
106
     *
107
     * @return true if this an App Engine Flexible Instance, false otherwise
108
     */
109
    public static function onAppEngineFlexible()
110
    {
111
        return isset($_SERVER['GAE_VM']) && 'true' === $_SERVER['GAE_VM'];
112
    }
113
 
114
    /**
115
     * Determines if this a GCE instance, by accessing the expected metadata
116
     * host.
117
     * If $httpHandler is not specified a the default HttpHandler is used.
118
     *
119
     * @param callable $httpHandler callback which delivers psr7 request
120
     *
121
     * @return true if this a GCEInstance false otherwise
122
     */
123
    public static function onGce(callable $httpHandler = null)
124
    {
125
        if (is_null($httpHandler)) {
126
            $httpHandler = HttpHandlerFactory::build();
127
        }
128
        $checkUri = 'http://' . self::METADATA_IP;
129
        try {
130
            // Comment from: oauth2client/client.py
131
            //
132
            // Note: the explicit `timeout` below is a workaround. The underlying
133
            // issue is that resolving an unknown host on some networks will take
134
            // 20-30 seconds; making this timeout short fixes the issue, but
135
            // could lead to false negatives in the event that we are on GCE, but
136
            // the metadata resolution was particularly slow. The latter case is
137
            // "unlikely".
138
            $resp = $httpHandler(
139
                new Request('GET', $checkUri),
140
                ['timeout' => 0.3]
141
            );
142
 
143
            return $resp->getHeaderLine(self::FLAVOR_HEADER) == 'Google';
144
        } catch (ClientException $e) {
145
            return false;
146
        } catch (ServerException $e) {
147
            return false;
148
        } catch (RequestException $e) {
149
            return false;
150
        }
151
    }
152
 
153
    /**
154
     * Implements FetchAuthTokenInterface#fetchAuthToken.
155
     *
156
     * Fetches the auth tokens from the GCE metadata host if it is available.
157
     * If $httpHandler is not specified a the default HttpHandler is used.
158
     *
159
     * @param callable $httpHandler callback which delivers psr7 request
160
     *
161
     * @return array the response
162
     *
163
     * @throws \Exception
164
     */
165
    public function fetchAuthToken(callable $httpHandler = null)
166
    {
167
        if (is_null($httpHandler)) {
168
            $httpHandler = HttpHandlerFactory::build();
169
        }
170
        if (!$this->hasCheckedOnGce) {
171
            $this->isOnGce = self::onGce($httpHandler);
172
        }
173
        if (!$this->isOnGce) {
174
            return array();  // return an empty array with no access token
175
        }
176
        $resp = $httpHandler(
177
            new Request(
178
                'GET',
179
                self::getTokenUri(),
180
                [self::FLAVOR_HEADER => 'Google']
181
            )
182
        );
183
        $body = (string)$resp->getBody();
184
 
185
        // Assume it's JSON; if it's not throw an exception
186
        if (null === $json = json_decode($body, true)) {
187
            throw new \Exception('Invalid JSON response');
188
        }
189
 
190
        // store this so we can retrieve it later
191
        $this->lastReceivedToken = $json;
192
        $this->lastReceivedToken['expires_at'] = time() + $json['expires_in'];
193
 
194
        return $json;
195
    }
196
 
197
    /**
198
     * @return string
199
     */
200
    public function getCacheKey()
201
    {
202
        return self::cacheKey;
203
    }
204
 
205
    /**
206
     * @return array|null
207
     */
208
    public function getLastReceivedToken()
209
    {
210
        if ($this->lastReceivedToken) {
211
            return [
212
                'access_token' => $this->lastReceivedToken['access_token'],
213
                'expires_at' => $this->lastReceivedToken['expires_at'],
214
            ];
215
        }
216
 
217
        return null;
218
    }
219
}