1<?php
2/*
3 * Copyright 2019 Google LLC
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;
19
20use Google\Auth\HttpHandler\HttpClientCache;
21use Google\Auth\HttpHandler\HttpHandlerFactory;
22use GuzzleHttp\Psr7;
23use GuzzleHttp\Psr7\Utils;
24
25/**
26 * Tools for using the IAM API.
27 *
28 * @see https://cloud.google.com/iam/docs IAM Documentation
29 */
30class Iam
31{
32    const IAM_API_ROOT = 'https://iamcredentials.googleapis.com/v1';
33    const SIGN_BLOB_PATH = '%s:signBlob?alt=json';
34    const SERVICE_ACCOUNT_NAME = 'projects/-/serviceAccounts/%s';
35
36    /**
37     * @var callable
38     */
39    private $httpHandler;
40
41    /**
42     * @param callable $httpHandler [optional] The HTTP Handler to send requests.
43     */
44    public function __construct(callable $httpHandler = null)
45    {
46        $this->httpHandler = $httpHandler
47            ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
48    }
49
50    /**
51     * Sign a string using the IAM signBlob API.
52     *
53     * Note that signing using IAM requires your service account to have the
54     * `iam.serviceAccounts.signBlob` permission, part of the "Service Account
55     * Token Creator" IAM role.
56     *
57     * @param string $email The service account email.
58     * @param string $accessToken An access token from the service account.
59     * @param string $stringToSign The string to be signed.
60     * @param array<string> $delegates [optional] A list of service account emails to
61     *        add to the delegate chain. If omitted, the value of `$email` will
62     *        be used.
63     * @return string The signed string, base64-encoded.
64     */
65    public function signBlob($email, $accessToken, $stringToSign, array $delegates = [])
66    {
67        $httpHandler = $this->httpHandler;
68        $name = sprintf(self::SERVICE_ACCOUNT_NAME, $email);
69        $uri = self::IAM_API_ROOT . '/' . sprintf(self::SIGN_BLOB_PATH, $name);
70
71        if ($delegates) {
72            foreach ($delegates as &$delegate) {
73                $delegate = sprintf(self::SERVICE_ACCOUNT_NAME, $delegate);
74            }
75        } else {
76            $delegates = [$name];
77        }
78
79        $body = [
80            'delegates' => $delegates,
81            'payload' => base64_encode($stringToSign),
82        ];
83
84        $headers = [
85            'Authorization' => 'Bearer ' . $accessToken
86        ];
87
88        $request = new Psr7\Request(
89            'POST',
90            $uri,
91            $headers,
92            Utils::streamFor(json_encode($body))
93        );
94
95        $res = $httpHandler($request);
96        $body = json_decode((string) $res->getBody(), true);
97
98        return $body['signedBlob'];
99    }
100}
101