'Google Drive API - Service account w/ domain-wide authority can list shared drives but not their contents
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 = '[email protected]'
sub = '[email protected]'
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": "[email protected]"
},
"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.
Solution 1:[1]
Did you configure domain wide delegation? Even if the impersonation and everything may be correct, without domain wide delegation the service account does not get enough permissions and can show this error.
Answer modification: The problem is actually related to the user impersonation as it is not being performed correctly in the code.
Solution 2:[2]
Creating a service account is not enough and performing domain wide delegation is not enough in this situation. You also have to impersonate another user in your domain.
The main purpose of granting domain-wide authority to a service account and later using impersonation is for these accounts to be able to access data on behalf of a user in your domain as otherwise the service account acts like just another account and it is trying to access its own data from Drive.
If the problem occurs at the impersonation part, you might want to double check if the scope/s set in the code match the ones you granted for the service account.
So for instance, these scopes https://www.googleapis.com/auth/drive, https://www.googleapis.com/auth/drive.file should be added to the service account as well.
However, since you are trying to access a shared drive, the following might also cause a 403:
Drive SDK is not enabled for your organization;
The user you are impersonating does not in fact have access to the shared and/or cannot retrieve the files from it;
In this case it would be useful to check the permissions which have been granted to the actual user for this shared drive.
Reference
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | ale13 |
