vendor/google/auth/src/Credentials/ServiceAccountCredentials.php line 113

Open in your IDE?
  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. namespace Google\Auth\Credentials;
  18. use Google\Auth\CredentialsLoader;
  19. use Google\Auth\GetQuotaProjectInterface;
  20. use Google\Auth\OAuth2;
  21. use Google\Auth\ProjectIdProviderInterface;
  22. use Google\Auth\ServiceAccountSignerTrait;
  23. use Google\Auth\SignBlobInterface;
  24. use InvalidArgumentException;
  25. /**
  26.  * ServiceAccountCredentials supports authorization using a Google service
  27.  * account.
  28.  *
  29.  * (cf https://developers.google.com/accounts/docs/OAuth2ServiceAccount)
  30.  *
  31.  * It's initialized using the json key file that's downloadable from developer
  32.  * console, which should contain a private_key and client_email fields that it
  33.  * uses.
  34.  *
  35.  * Use it with AuthTokenMiddleware to authorize http requests:
  36.  *
  37.  *   use Google\Auth\Credentials\ServiceAccountCredentials;
  38.  *   use Google\Auth\Middleware\AuthTokenMiddleware;
  39.  *   use GuzzleHttp\Client;
  40.  *   use GuzzleHttp\HandlerStack;
  41.  *
  42.  *   $sa = new ServiceAccountCredentials(
  43.  *       'https://www.googleapis.com/auth/taskqueue',
  44.  *       '/path/to/your/json/key_file.json'
  45.  *   );
  46.  *   $middleware = new AuthTokenMiddleware($sa);
  47.  *   $stack = HandlerStack::create();
  48.  *   $stack->push($middleware);
  49.  *
  50.  *   $client = new Client([
  51.  *       'handler' => $stack,
  52.  *       'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/',
  53.  *       'auth' => 'google_auth' // authorize all requests
  54.  *   ]);
  55.  *
  56.  *   $res = $client->get('myproject/taskqueues/myqueue');
  57.  */
  58. class ServiceAccountCredentials extends CredentialsLoader implements
  59.     GetQuotaProjectInterface,
  60.     SignBlobInterface,
  61.     ProjectIdProviderInterface
  62. {
  63.     use ServiceAccountSignerTrait;
  64.     /**
  65.      * The OAuth2 instance used to conduct authorization.
  66.      *
  67.      * @var OAuth2
  68.      */
  69.     protected $auth;
  70.     /**
  71.      * The quota project associated with the JSON credentials
  72.      *
  73.      * @var string
  74.      */
  75.     protected $quotaProject;
  76.     /**
  77.      * @var string|null
  78.      */
  79.     protected $projectId;
  80.     /**
  81.      * @var array<mixed>|null
  82.      */
  83.     private $lastReceivedJwtAccessToken;
  84.     /**
  85.      * @var bool
  86.      */
  87.     private $useJwtAccessWithScope false;
  88.     /**
  89.      * @var ServiceAccountJwtAccessCredentials|null
  90.      */
  91.     private $jwtAccessCredentials;
  92.     /**
  93.      * Create a new ServiceAccountCredentials.
  94.      *
  95.      * @param string|string[]|null $scope the scope of the access request, expressed
  96.      *   either as an Array or as a space-delimited String.
  97.      * @param string|array<mixed> $jsonKey JSON credential file path or JSON credentials
  98.      *   as an associative array
  99.      * @param string $sub an email address account to impersonate, in situations when
  100.      *   the service account has been delegated domain wide access.
  101.      * @param string $targetAudience The audience for the ID token.
  102.      */
  103.     public function __construct(
  104.         $scope,
  105.         $jsonKey,
  106.         $sub null,
  107.         $targetAudience null
  108.     ) {
  109.         if (is_string($jsonKey)) {
  110.             if (!file_exists($jsonKey)) {
  111.                 throw new \InvalidArgumentException('file does not exist');
  112.             }
  113.             $jsonKeyStream file_get_contents($jsonKey);
  114.             if (!$jsonKey json_decode((string) $jsonKeyStreamtrue)) {
  115.                 throw new \LogicException('invalid json for auth config');
  116.             }
  117.         }
  118.         if (!array_key_exists('client_email'$jsonKey)) {
  119.             throw new \InvalidArgumentException(
  120.                 'json key is missing the client_email field'
  121.             );
  122.         }
  123.         if (!array_key_exists('private_key'$jsonKey)) {
  124.             throw new \InvalidArgumentException(
  125.                 'json key is missing the private_key field'
  126.             );
  127.         }
  128.         if (array_key_exists('quota_project_id'$jsonKey)) {
  129.             $this->quotaProject = (string) $jsonKey['quota_project_id'];
  130.         }
  131.         if ($scope && $targetAudience) {
  132.             throw new InvalidArgumentException(
  133.                 'Scope and targetAudience cannot both be supplied'
  134.             );
  135.         }
  136.         $additionalClaims = [];
  137.         if ($targetAudience) {
  138.             $additionalClaims = ['target_audience' => $targetAudience];
  139.         }
  140.         $this->auth = new OAuth2([
  141.             'audience' => self::TOKEN_CREDENTIAL_URI,
  142.             'issuer' => $jsonKey['client_email'],
  143.             'scope' => $scope,
  144.             'signingAlgorithm' => 'RS256',
  145.             'signingKey' => $jsonKey['private_key'],
  146.             'sub' => $sub,
  147.             'tokenCredentialUri' => self::TOKEN_CREDENTIAL_URI,
  148.             'additionalClaims' => $additionalClaims,
  149.         ]);
  150.         $this->projectId = isset($jsonKey['project_id'])
  151.             ? $jsonKey['project_id']
  152.             : null;
  153.     }
  154.     /**
  155.      * When called, the ServiceAccountCredentials will use an instance of
  156.      * ServiceAccountJwtAccessCredentials to fetch (self-sign) an access token
  157.      * even when only scopes are supplied. Otherwise,
  158.      * ServiceAccountJwtAccessCredentials is only called when no scopes and an
  159.      * authUrl (audience) is suppled.
  160.      *
  161.      * @return void
  162.      */
  163.     public function useJwtAccessWithScope()
  164.     {
  165.         $this->useJwtAccessWithScope true;
  166.     }
  167.     /**
  168.      * @param callable $httpHandler
  169.      *
  170.      * @return array<mixed> {
  171.      *     A set of auth related metadata, containing the following
  172.      *
  173.      *     @type string $access_token
  174.      *     @type int $expires_in
  175.      *     @type string $token_type
  176.      * }
  177.      */
  178.     public function fetchAuthToken(callable $httpHandler null)
  179.     {
  180.         if ($this->useSelfSignedJwt()) {
  181.             $jwtCreds $this->createJwtAccessCredentials();
  182.             $accessToken $jwtCreds->fetchAuthToken($httpHandler);
  183.             if ($lastReceivedToken $jwtCreds->getLastReceivedToken()) {
  184.                 // Keep self-signed JWTs in memory as the last received token
  185.                 $this->lastReceivedJwtAccessToken $lastReceivedToken;
  186.             }
  187.             return $accessToken;
  188.         }
  189.         return $this->auth->fetchAuthToken($httpHandler);
  190.     }
  191.     /**
  192.      * @return string
  193.      */
  194.     public function getCacheKey()
  195.     {
  196.         $key $this->auth->getIssuer() . ':' $this->auth->getCacheKey();
  197.         if ($sub $this->auth->getSub()) {
  198.             $key .= ':' $sub;
  199.         }
  200.         return $key;
  201.     }
  202.     /**
  203.      * @return array<mixed>
  204.      */
  205.     public function getLastReceivedToken()
  206.     {
  207.         // If self-signed JWTs are being used, fetch the last received token
  208.         // from memory. Else, fetch it from OAuth2
  209.         return $this->useSelfSignedJwt()
  210.             ? $this->lastReceivedJwtAccessToken
  211.             $this->auth->getLastReceivedToken();
  212.     }
  213.     /**
  214.      * Get the project ID from the service account keyfile.
  215.      *
  216.      * Returns null if the project ID does not exist in the keyfile.
  217.      *
  218.      * @param callable $httpHandler Not used by this credentials type.
  219.      * @return string|null
  220.      */
  221.     public function getProjectId(callable $httpHandler null)
  222.     {
  223.         return $this->projectId;
  224.     }
  225.     /**
  226.      * Updates metadata with the authorization token.
  227.      *
  228.      * @param array<mixed> $metadata metadata hashmap
  229.      * @param string $authUri optional auth uri
  230.      * @param callable $httpHandler callback which delivers psr7 request
  231.      * @return array<mixed> updated metadata hashmap
  232.      */
  233.     public function updateMetadata(
  234.         $metadata,
  235.         $authUri null,
  236.         callable $httpHandler null
  237.     ) {
  238.         // scope exists. use oauth implementation
  239.         if (!$this->useSelfSignedJwt()) {
  240.             return parent::updateMetadata($metadata$authUri$httpHandler);
  241.         }
  242.         $jwtCreds $this->createJwtAccessCredentials();
  243.         if ($this->auth->getScope()) {
  244.             // Prefer user-provided "scope" to "audience"
  245.             $updatedMetadata $jwtCreds->updateMetadata($metadatanull$httpHandler);
  246.         } else {
  247.             $updatedMetadata $jwtCreds->updateMetadata($metadata$authUri$httpHandler);
  248.         }
  249.         if ($lastReceivedToken $jwtCreds->getLastReceivedToken()) {
  250.             // Keep self-signed JWTs in memory as the last received token
  251.             $this->lastReceivedJwtAccessToken $lastReceivedToken;
  252.         }
  253.         return $updatedMetadata;
  254.     }
  255.     /**
  256.      * @return ServiceAccountJwtAccessCredentials
  257.      */
  258.     private function createJwtAccessCredentials()
  259.     {
  260.         if (!$this->jwtAccessCredentials) {
  261.             // Create credentials for self-signing a JWT (JwtAccess)
  262.             $credJson = [
  263.                 'private_key' => $this->auth->getSigningKey(),
  264.                 'client_email' => $this->auth->getIssuer(),
  265.             ];
  266.             $this->jwtAccessCredentials = new ServiceAccountJwtAccessCredentials(
  267.                 $credJson,
  268.                 $this->auth->getScope()
  269.             );
  270.         }
  271.         return $this->jwtAccessCredentials;
  272.     }
  273.     /**
  274.      * @param string $sub an email address account to impersonate, in situations when
  275.      *   the service account has been delegated domain wide access.
  276.      * @return void
  277.      */
  278.     public function setSub($sub)
  279.     {
  280.         $this->auth->setSub($sub);
  281.     }
  282.     /**
  283.      * Get the client name from the keyfile.
  284.      *
  285.      * In this case, it returns the keyfile's client_email key.
  286.      *
  287.      * @param callable $httpHandler Not used by this credentials type.
  288.      * @return string
  289.      */
  290.     public function getClientName(callable $httpHandler null)
  291.     {
  292.         return $this->auth->getIssuer();
  293.     }
  294.     /**
  295.      * Get the quota project used for this API request
  296.      *
  297.      * @return string|null
  298.      */
  299.     public function getQuotaProject()
  300.     {
  301.         return $this->quotaProject;
  302.     }
  303.     /**
  304.      * @return bool
  305.      */
  306.     private function useSelfSignedJwt()
  307.     {
  308.         // If claims are set, this call is for "id_tokens"
  309.         if ($this->auth->getAdditionalClaims()) {
  310.             return false;
  311.         }
  312.         // When true, ServiceAccountCredentials will always use JwtAccess for access tokens
  313.         if ($this->useJwtAccessWithScope) {
  314.             return true;
  315.         }
  316.         return is_null($this->auth->getScope());
  317.     }
  318. }