| @@ -14,14 +14,14 @@ | ||
| 14 | 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | 15 | * See the License for the specific language governing permissions and |
| 16 | 16 | * limitations under the License. |
| 17 | 17 | */ |
| 18 | -namespace Dudlewebs\WPMCS\Google\Auth; | |
| 18 | +namespace Dudlewebs\WPMCS\GCP\Google\Auth; | |
| 19 | 19 | |
| 20 | -use Dudlewebs\WPMCS\Google\Auth\HttpHandler\HttpClientCache; | |
| 21 | -use Dudlewebs\WPMCS\Google\Auth\HttpHandler\HttpHandlerFactory; | |
| 22 | -use Dudlewebs\WPMCS\GuzzleHttp\Psr7; | |
| 23 | -use Dudlewebs\WPMCS\GuzzleHttp\Psr7\Utils; | |
| 20 | +use Dudlewebs\WPMCS\GCP\Google\Auth\HttpHandler\HttpClientCache; | |
| 21 | +use Dudlewebs\WPMCS\GCP\Google\Auth\HttpHandler\HttpHandlerFactory; | |
| 22 | +use Dudlewebs\WPMCS\GCP\GuzzleHttp\Psr7; | |
| 23 | +use Dudlewebs\WPMCS\GCP\GuzzleHttp\Psr7\Utils; | |
| 24 | 24 | /** |
| 25 | 25 | * Tools for using the IAM API. |
| 26 | 26 | * |
| 27 | 27 | * @see https://cloud.google.com/iam/docs IAM Documentation |
| @@ -34,8 +34,9 @@ | ||
| 34 | 34 | const IAM_API_ROOT = 'https://iamcredentials.googleapis.com/v1'; |
| 35 | 35 | const SIGN_BLOB_PATH = '%s:signBlob?alt=json'; |
| 36 | 36 | const SERVICE_ACCOUNT_NAME = 'projects/-/serviceAccounts/%s'; |
| 37 | 37 | private const IAM_API_ROOT_TEMPLATE = 'https://iamcredentials.UNIVERSE_DOMAIN/v1'; |
| 38 | + private const GENERATE_ID_TOKEN_PATH = '%s:generateIdToken'; | |
| 38 | 39 | /** |
| 39 | 40 | * @var callable |
| 40 | 41 | */ |
| 41 | 42 | private $httpHandler; |
| @@ -40,11 +41,11 @@ | ||
| 40 | 41 | */ |
| 41 | 42 | private $httpHandler; |
| 42 | 43 | private string $universeDomain; |
| 43 | 44 | /** |
| 44 | - * @param callable $httpHandler [optional] The HTTP Handler to send requests. | |
| 45 | + * @param callable|null $httpHandler [optional] The HTTP Handler to send requests. | |
| 45 | 46 | */ |
| 46 | - public function __construct(callable $httpHandler = null, string $universeDomain = GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN) | |
| 47 | + public function __construct(?callable $httpHandler = null, string $universeDomain = GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN) | |
| 47 | 48 | { |
| 48 | 49 | $this->httpHandler = $httpHandler ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient()); |
| 49 | 50 | $this->universeDomain = $universeDomain; |
| 50 | 51 | } |
| @@ -64,23 +65,48 @@ | ||
| 64 | 65 | * @return string The signed string, base64-encoded. |
| 65 | 66 | */ |
| 66 | 67 | public function signBlob($email, $accessToken, $stringToSign, array $delegates = []) |
| 67 | 68 | { |
| 68 | - $httpHandler = $this->httpHandler; | |
| 69 | - $name = sprintf(self::SERVICE_ACCOUNT_NAME, $email); | |
| 70 | - $apiRoot = str_replace('UNIVERSE_DOMAIN', $this->universeDomain, self::IAM_API_ROOT_TEMPLATE); | |
| 71 | - $uri = $apiRoot . '/' . sprintf(self::SIGN_BLOB_PATH, $name); | |
| 69 | + $name = \sprintf(self::SERVICE_ACCOUNT_NAME, $email); | |
| 70 | + $apiRoot = \str_replace('UNIVERSE_DOMAIN', $this->universeDomain, self::IAM_API_ROOT_TEMPLATE); | |
| 71 | + $uri = $apiRoot . '/' . \sprintf(self::SIGN_BLOB_PATH, $name); | |
| 72 | 72 | if ($delegates) { |
| 73 | 73 | foreach ($delegates as &$delegate) { |
| 74 | - $delegate = sprintf(self::SERVICE_ACCOUNT_NAME, $delegate); | |
| 74 | + $delegate = \sprintf(self::SERVICE_ACCOUNT_NAME, $delegate); | |
| 75 | 75 | } |
| 76 | 76 | } else { |
| 77 | 77 | $delegates = [$name]; |
| 78 | 78 | } |
| 79 | - $body = ['delegates' => $delegates, 'payload' => base64_encode($stringToSign)]; | |
| 79 | + $body = ['delegates' => $delegates, 'payload' => \base64_encode($stringToSign)]; | |
| 80 | 80 | $headers = ['Authorization' => 'Bearer ' . $accessToken]; |
| 81 | - $request = new Psr7\Request('POST', $uri, $headers, Utils::streamFor(json_encode($body))); | |
| 82 | - $res = $httpHandler($request); | |
| 83 | - $body = json_decode((string) $res->getBody(), \true); | |
| 81 | + $request = new Psr7\Request('POST', $uri, $headers, Utils::streamFor(\json_encode($body))); | |
| 82 | + $res = ($this->httpHandler)($request); | |
| 83 | + $body = \json_decode((string) $res->getBody(), \true); | |
| 84 | 84 | return $body['signedBlob']; |
| 85 | + } | |
| 86 | + /** | |
| 87 | + * Sign a string using the IAM signBlob API. | |
| 88 | + * | |
| 89 | + * Note that signing using IAM requires your service account to have the | |
| 90 | + * `iam.serviceAccounts.signBlob` permission, part of the "Service Account | |
| 91 | + * Token Creator" IAM role. | |
| 92 | + * | |
| 93 | + * @param string $clientEmail The service account email. | |
| 94 | + * @param string $targetAudience The audience for the ID token. | |
| 95 | + * @param string $bearerToken The token to authenticate the IAM request. | |
| 96 | + * @param array<string, string> $headers [optional] Additional headers to send with the request. | |
| 97 | + * | |
| 98 | + * @return string The signed string, base64-encoded. | |
| 99 | + */ | |
| 100 | + public function generateIdToken(string $clientEmail, string $targetAudience, string $bearerToken, array $headers = []) : string | |
| 101 | + { | |
| 102 | + $name = \sprintf(self::SERVICE_ACCOUNT_NAME, $clientEmail); | |
| 103 | + $apiRoot = \str_replace('UNIVERSE_DOMAIN', $this->universeDomain, self::IAM_API_ROOT_TEMPLATE); | |
| 104 | + $uri = $apiRoot . '/' . \sprintf(self::GENERATE_ID_TOKEN_PATH, $name); | |
| 105 | + $headers['Authorization'] = 'Bearer ' . $bearerToken; | |
| 106 | + $body = ['audience' => $targetAudience, 'includeEmail' => \true, 'useEmailAzp' => \true]; | |
| 107 | + $request = new Psr7\Request('POST', $uri, $headers, Utils::streamFor(\json_encode($body))); | |
| 108 | + $res = ($this->httpHandler)($request); | |
| 109 | + $body = \json_decode((string) $res->getBody(), \true); | |
| 110 | + return $body['token']; | |
| 85 | 111 | } |
| 86 | 112 | } |