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
18namespace Google\Auth\Middleware;
19
20use Google\Auth\FetchAuthTokenInterface;
21use Google\Auth\GetQuotaProjectInterface;
22use Psr\Http\Message\RequestInterface;
23
24/**
25 * AuthTokenMiddleware is a Guzzle Middleware that adds an Authorization header
26 * provided by an object implementing FetchAuthTokenInterface.
27 *
28 * The FetchAuthTokenInterface#fetchAuthToken is used to obtain a hash; one of
29 * the values value in that hash is added as the authorization header.
30 *
31 * Requests will be accessed with the authorization header:
32 *
33 * 'authorization' 'Bearer <value of auth_token>'
34 */
35class AuthTokenMiddleware
36{
37    /**
38     * @var callable
39     */
40    private $httpHandler;
41
42    /**
43     * @var FetchAuthTokenInterface
44     */
45    private $fetcher;
46
47    /**
48     * @var ?callable
49     */
50    private $tokenCallback;
51
52    /**
53     * Creates a new AuthTokenMiddleware.
54     *
55     * @param FetchAuthTokenInterface $fetcher is used to fetch the auth token
56     * @param callable $httpHandler (optional) callback which delivers psr7 request
57     * @param callable $tokenCallback (optional) function to be called when a new token is fetched.
58     */
59    public function __construct(
60        FetchAuthTokenInterface $fetcher,
61        callable $httpHandler = null,
62        callable $tokenCallback = null
63    ) {
64        $this->fetcher = $fetcher;
65        $this->httpHandler = $httpHandler;
66        $this->tokenCallback = $tokenCallback;
67    }
68
69    /**
70     * Updates the request with an Authorization header when auth is 'google_auth'.
71     *
72     *   use Google\Auth\Middleware\AuthTokenMiddleware;
73     *   use Google\Auth\OAuth2;
74     *   use GuzzleHttp\Client;
75     *   use GuzzleHttp\HandlerStack;
76     *
77     *   $config = [..<oauth config param>.];
78     *   $oauth2 = new OAuth2($config)
79     *   $middleware = new AuthTokenMiddleware($oauth2);
80     *   $stack = HandlerStack::create();
81     *   $stack->push($middleware);
82     *
83     *   $client = new Client([
84     *       'handler' => $stack,
85     *       'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/',
86     *       'auth' => 'google_auth' // authorize all requests
87     *   ]);
88     *
89     *   $res = $client->get('myproject/taskqueues/myqueue');
90     *
91     * @param callable $handler
92     * @return \Closure
93     */
94    public function __invoke(callable $handler)
95    {
96        return function (RequestInterface $request, array $options) use ($handler) {
97            // Requests using "auth"="google_auth" will be authorized.
98            if (!isset($options['auth']) || $options['auth'] !== 'google_auth') {
99                return $handler($request, $options);
100            }
101
102            $request = $request->withHeader('authorization', 'Bearer ' . $this->fetchToken());
103
104            if ($quotaProject = $this->getQuotaProject()) {
105                $request = $request->withHeader(
106                    GetQuotaProjectInterface::X_GOOG_USER_PROJECT_HEADER,
107                    $quotaProject
108                );
109            }
110
111            return $handler($request, $options);
112        };
113    }
114
115    /**
116     * Call fetcher to fetch the token.
117     *
118     * @return string|null
119     */
120    private function fetchToken()
121    {
122        $auth_tokens = $this->fetcher->fetchAuthToken($this->httpHandler);
123
124        if (array_key_exists('access_token', $auth_tokens)) {
125            // notify the callback if applicable
126            if ($this->tokenCallback) {
127                call_user_func(
128                    $this->tokenCallback,
129                    $this->fetcher->getCacheKey(),
130                    $auth_tokens['access_token']
131                );
132            }
133
134            return $auth_tokens['access_token'];
135        }
136
137        if (array_key_exists('id_token', $auth_tokens)) {
138            return $auth_tokens['id_token'];
139        }
140
141        return null;
142    }
143
144    /**
145     * @return string|null
146     */
147    private function getQuotaProject()
148    {
149        if ($this->fetcher instanceof GetQuotaProjectInterface) {
150            return $this->fetcher->getQuotaProject();
151        }
152
153        return null;
154    }
155}
156