Note: I am not using a Cloud Client Library, and instead am using the Google Drive API via Powershell and HTTP, going against the guides recommendations.
I am trying to have a Powershell script do a simple upload of a file using the Google Drive API (v3). My current GCP project includes a service account that has been granted domain-wide authority with the following scopes:
https://www.googleapis.com/auth/drivehttps://www.googleapis.com/auth/drive.file
Following the "Preparing to make an authorized API call" guide, I have successfully created a JWT, used said JWT to get an access token, and use said access token to interact with the API. I have also confirmed that the service account is able to successfully "impersonate" a user account in our Workspace (using the sub parameter in the "Additional claims" section of the guide above).
The issue I am facing is regarding permissions. I get a successful response back from GET https://www.googleapis.com/drive/v3/drives and can see the Shared Drives that the impersonated-user has access to. However, I get a (403) Forbidden when requesting to list the contents of one of these Shared Drives; specifically via GET https://www.googleapis.com/drive/v3/files?driveId=<DRIVE_ID_HERE>?includeItemsFromAllDrives=true?supportsAllDrives=true?corpora=allDrives.
Is there an issue with my request parameters? I'm confused as to why a user with Content Manager access to a Shared Drive is able to list Shared Drives and not any contents within said drives?
Edit 1: added snippets for 1) building JWT & access token and 2) Google Drive API requests
JWT & Access Token
$now = (Get-Date).ToUniversalTime()
$createDate = [Math]::Floor([decimal](Get-Date($now) -UFormat '%s'))
$expiryDate = [Math]::Floor([decimal](Get-Date($now.AddHours(1)) -UFormat '%s'))
$payload = [Ordered]@{
iss = 'service_account@xyz-123.iam.gserviceaccount.com'
sub = 'account-user@gmail.com'
scope = 'https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file'
aud = 'https://oauth2.googleapis.com/token'
iat = $createDate
exp = $expiryDate
} | ConvertTo-Json
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("cert.pfx", 'pw')
# link to JWT module is in OP
$jwt = New-Jwt -PayloadJson $payload -Cert $cert -Verbose
$accessTokenResponse = Invoke-WebRequest 'https://oauth2.googleapis.com/token' `
-UseBasicParsing `
-Method 'POST' `
-Headers @{'Content-Type' = 'application/x-www-form-urlencoded'} `
-Body "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=$jwt" `
-Verbose
$accessToken = ($accessTokenResponse.Content | ConvertFrom-Json).access_token
Request to drives-list
$uploadResponse = Invoke-WebRequest 'https://www.googleapis.com/drive/v3/drives' `
-UseBasicParsing `
-Method 'GET' `
-Headers @{'Authorization' = "Bearer $accessToken"'} `
-Verbose
Request to files-list
# Using driveId in the response from drives-list above
$uploadResponse = Invoke-WebRequest 'https://www.googleapis.com/drive/v3/files?driveId=<DRIVE_ID_HERE>?includeItemsFromAllDrives=true?supportsAllDrives=true?corpora=allDrives' `
-UseBasicParsing `
-Method 'GET' `
-Headers @{'Authorization' = "Bearer $accessToken"'} `
-Verbose
The 403 response I get back from the files-list request is:
PSMessageDetails :
Exception : System.Net.WebException: The remote server returned an error: (403) Forbidden.
at Microsoft.PowerShell.Commands.WebRequestPSCmdlet.GetResponse(WebRequest request)
at Microsoft.PowerShell.Commands.WebRequestPSCmdlet.ProcessRecord()
TargetObject : System.Net.HttpWebRequest
CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
ErrorDetails :
InvocationInfo : System.Management.Automation.InvocationInfo
ScriptStackTrace : at <ScriptBlock>, C:\...\GDriveAPITest.ps1: line 80
at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}
Edit 2: added snippet to determine if impersonation is working
After reviewing answers/comments, I wanted to confirm if impersonation is working in my existing code. In the snippets above in Edit 1, I am passing in the account-to-impersonate in the sub param during access token creation. This step is outline in the docs, under Additional claims in the HTTP/REST guide.
I then did a GET to about-get to determine if the impersonation is working with the access token.
Request to about-get
$uploadResponse = Invoke-WebRequest 'https://www.googleapis.com/drive/v3/about?fields=*' `
-UseBasicParsing `
-Method 'GET' `
-Headers @{'Authorization' = "Bearer $accessToken"; 'Content-Type' = 'text/plain'} `
-Verbose
Response from about-get (in JSON)
{
"kind": "drive#about",
"user": {
"kind": "drive#user",
"displayName": "Impersonated User",
"photoLink": "https://lh3.googleusercontent.com/a/default-user=s64",
"me": true,
"permissionId": "<PERMISSION_ID_HERE>",
"emailAddress": "impersonated_user@gmail.com"
},
"storageQuota": { ... },
"importFormats": { ... },
"exportFormats": { ... },
"maxImportSizes": { ... },
"maxUploadSize": "0000000000000",
"appInstalled": false,
"folderColorPalette": [ ... ],
"teamDriveThemes": [ ... ],
"driveThemes": [ ... ],
"canCreateTeamDrives": true,
"canCreateDrives": true
}
Does this confirm that I have impersonation working?
The impersonated user is a Content Manager on the Shared Drive and the Drive SDK is enabled in the org/project. The next update I am trying is to confirm all scopes needed by the Drive API endpoint(s) are assigned to the service account and the JWT token. Will report back after updating.