Unable to list files with GoogleDrive API - google-drive-api

I'm trying to get a list of documents from google drive via its SDK. I use the service account to accomplish the authentication, and set the scope to https://www.googleapis.com/auth/drive which has the full permission.
However, the response contains an empty item list though there do have test files in my google drive.
AuthorizationServerDescription desc = GoogleAuthenticationServer.Description;
X509Certificate2 key = new X509Certificate2(m_file, "notasecret", X509KeyStorageFlags.Exportable);
string scope = Google.Apis.Drive.v2.DriveService.Scopes.Drive.ToString().ToLower();
AssertionFlowClient client = new AssertionFlowClient(desc, key) { ServiceAccountId = m_email, Scope = "https://www.googleapis.com/auth/" + scope };
OAuth2Authenticator<AssertionFlowClient> auth = new OAuth2Authenticator<AssertionFlowClient>(client, AssertionFlowClient.GetState);
DriveService service = new DriveService(auth);
FilesResource.ListRequest request = service.Files.List();
request.Q = "";
Stream dataStream = request.FetchAsStream();
StreamReader reader = new StreamReader(dataStream);
string responseFromServer = reader.ReadToEnd();

Check Nivco's answer to Google Drive SDK: Empty array of files returned from Drive for more details about your issue.
You're basically listing the files in the service account's Drive and not yours. What you need to do is setup the email for the user of the domain you are trying to impersonate, check the Google Drive SDK documentation for instructions and sample code showing how to do it:
https://developers.google.com/drive/delegation

Related

how to access and modify google workspace domain user's google drive files as a domain administrator using google apps scripts

Objective:
as a google workspace domain admin for a school that uses google workspace education, I want to create a google apps script that given a google workspace user's email address (the current owner), the scritp should be able to get a list of all the user's folders and files in their google drive and then it should also be able to transfer the ownership of those folders and files to domain user and add the current owner as a viewer so they can only see the folders/files but can't modify them in any way.
things I tried:
DriveApp can access files/folders and change the ownership of the file/folder but only if you are the owner, and I want to do this as the domain admin, regardless which user owns the google drive and respective files/folders.
Drive API, seems to do the same as DriveApp as far you're the owner, I couldn't figure out how to give Drive API admin permissions so I can see every domain user google drive file list, if that's even possible.
GAM advance: I found this as management tool, I set it and it migh do what I need but it's bit complex for me, plus I was really hoping to be able to build the tool myself.
What worked halfway:
I found this: https://github.com/googleworkspace/apps-script-oauth2#using-service-accounts which refers to using a service account. It took a while but I manage to get a list of items that exist on a user's google drive with the script below. but I can't figure out how to access those files/folders so I can change the ownership or set viewers on them. I think I read that the service account will only give me read-only access so I'm doubting this is even possible.
Here's what I got so far:
function main(){
// Private key and client email of the service account.
var key = getJsonKey()
var clientEmail = 'service_account_email_setup_in_google_dev_console';
// Email address of the user to impersonate.
var userEmail = 'a_regular_domain_user#my_google_workspace_domain.com';
try{
var drive = getDriveService_(key,userEmail,clientEmail);
if (drive.hasAccess()) {
// this code gets me a json response with items that list id's and urls and other
//file metadata of the
// files that belongs to the domain user, this is as far as i got.
var url = 'https://www.googleapis.com/drive/v2/files';
var response = UrlFetchApp.fetch(url, {
headers: {
Authorization: 'Bearer ' + drive.getAccessToken()
}
});
var result = JSON.parse(response.getContentText());
//the following code returns a fileid in the user's google
//drive not shared with the admin
var fileid = JSON.stringify(result.items[0].id)
Logger.log(fileid);
//but the following code returns an error indicating that the
//file is not found (in reality it's not accessible by the
//admin account)
var file = Drive.Files.get(fileid);
//access a list of items and as I traverse it I'd like to
//change the ownership and
//add the the current user as a file viewer
//??
} else {
Logger.log(drive.getLastError());
}
}catch (e){
Logger.log(e)
}
}
// Load the JSON key file with private key for service account
function getJsonKey(){
var keyFile = DriveApp.getFileById("json_fileid_in_drive_obtained_from_googledevcons");
var key = JSON.parse(keyFile.getBlob().getDataAsString()).private_key;
return key
}
function reset() {
getDriveService__().reset();
}
//get the google drive from the domain user's email address
function getDriveService_(key,userEmail,clientEmail) {
return OAuth2.createService('GoogleDrive:' + userEmail)
.setTokenUrl('https://oauth2.googleapis.com/token')
.setPrivateKey(key)
.setIssuer(clientEmail)
.setSubject(userEmail)
.setPropertyStore(PropertiesService.getUserProperties())
.setCache(CacheService.getUserCache())
.setScope('https://www.googleapis.com/auth/drive');
}
Any help is appreciated :)
You are going in the right direction, the only part you are missing currently is setting up Domain Wide Delegation this will allow you to impersonate the users in your domain so you can make the changes on behalf of them by granting the service account permissions through the above mentioned DWD.
Since you have already created the Oauth2Service you will just need to send the user to impersonate through the OauthParams:
const oauthParams = {
serviceName: 'Nameofyourservice',
serviceAccount,
scopes: ['https://www.googleapis.com/auth/appsmarketplace.license', 'https://www.googleapis.com/auth/userinfo.email', 'https://mail.google.com', 'https://www.googleapis.com/auth/iam'],
userToImpersonate: 'usertoimpersonate#test.com',
};
The scopes were from the Marketplace API as an example.

Google DriveService API Upload with mimetype application/vnd.google-apps.document failing

I am trying to upload a file to Google Drive and share/access the same via Google Docs. I have copied below the code snippet. I am using Google Drive API V3. It's failing with error GoogleApiException:
Google.Apis.Requests.RequestError Bad Request [400].
Not sure what's missing in the request. I have tried few additional parameters of File, but it always returns Bad Request Error. If anyone know the solution please let me know.
string fileType = "application/vnd.google-apps.document";
var newFile = new File
{
Name = title,
MimeType = fileType,
Description= title,
};
var uploadProgress = DriveService.Files.Create(newFile, fileStream, fileType);
Google.Apis.Upload.IUploadProgress progress = uploadProgress.Upload();
I believe that in v3 you do not need to specify the mimeType as a third parameter for DriveService.Files.Create()
This is opposed to Files.Insert() in v2, where you would also need to specify Convert = true
Change to:
var uploadProgress = DriveService.Files.Create(newFile, fileStream);
Make sure that your original mimeType is compatible with Google Docs
Make sure that you build fileStream correctly.

Why do I get an empty 200 OK response when uploading a file to a shared Google Drive folder when using a service account?

I am trying to use a Service Account to upload a file into a shared Google Drive folder, using Google Drive API v3 and the .Net client. The service account is added to the folder and "Can organise, add and edit". And if I do a list request I get the folder back (so I know the service account authentication works and that it at least has access to the shared folder).
If I try to upload a file though, I get a 200 response (so no error) but with an empty ResponseBody. And if I list again, the file isn't there. I don't understand what this means or why it is happening and can't find any information about this situation anywhere.
Here's my upload code. [PARENT_ID] is the ID of the shared folder and I've got the content of the CSV file I'm uploading and converted into bytes to put into the stream I've then sent.
using (var driveService = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = googleCredential.CreateScoped(DriveService.Scope.Drive),
ApplicationName = "FunctionApp"
}))
{
File fileMetadata = new File()
{
Name = "FileName",
MimeType = "application/vnd.google-apps.spreadsheet",
Parents = new List<string>() { "[PARENT_ID]" }
};
FilesResource.CreateMediaUpload request;
using (var csvStream = new MemoryStream())
{
csvStream.Write(csvBytes);
request = driveService.Files.Create(
fileMetadata, csvStream, "text/csv");
}
request.Fields = "id";
request.Upload();
var file = request.ResponseBody;
Console.WriteLine("File ID: " + file.Id);
}
The file variable is always null so obviously file.Id throws a NullReferenceException.
I've been staring at this for hours but have not been able to work out what is wrong. Any ideas?
I've tried saving the csv string to a file a then reading it in a file stream (closer to the example at the bottom of https://developers.google.com/drive/api/v3/manage-uploads#import_to_google_docs_types_) but this yields the same result.
Note: I've been able to create a file in the same folder using the "Try this API" tool on https://developers.google.com/drive/api/v3/reference/files/create using the parameters the same as what I pass in the metadata here, but of course that doesn't use a service account and doesn't pass any actual data to the file.
Thanks to #JonSkeet for the solution on GitHub (https://github.com/googleapis/google-api-dotnet-client/issues/1490)
The problem was that the stream was being disposed before the upload (ie after the using). The code change was to not use a using at all.
var csvStream = new MemoryStream(csvBytes);
request = driveService.Files.Create(fileMetadata, csvStream, "text/csv");
var result = request.Upload();
This did a successful upload as expected.
This also didn't use the request.Fields although this could maybe have been left in.

Custom google app script doesn't work after copying spreadsheet with google java client

I have google spreadsheet template with custom add-on which I'm trying to copy
def copyTemplateSpreadsheet(Drive driveService) {
File templateCopy = new File()
templateCopy.setName("excel-template")
def copiedFile = driveService.files().copy(templateSpreadsheetId, templateCopy).execute()
setCorrectPermission(driveService, copiedFile.getId())
copiedFile
}
private void setCorrectPermission(Drive driveService, def fileId) {
Permission newPermission = new Permission();
newPermission.setType("anyone");
newPermission.setRole("writer");
driveService.permissions().create(fileId, newPermission).execute();
}
The problem is that copied spreadsheet has broken add-on (isn't displayed in add-ons menu). There is correct add-on code in script editor but when I try to run any function I get error message
"We're sorry, a server error occurred. Please wait a bit and try again"
Keep in mind that the very same code work well in my template spreadsheet. Even if I delete all the code and leave empty onOpen function the error still appears.
Copying add-ons works well when I do it using regular google drive website (drive.google.com) and also worked when I tried to use google's API Explorer (https://developers.google.com/drive/v3/reference/files/copy#try-it). The problem seems to only when using sdk (at least java one - I haven't tried any other)
Also keep in mind I'm using google service account created as described in this article https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount
and creating Drive instance with following code
Drive getDriveService() throws GeneralSecurityException, IOException, URISyntaxException {
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(G_SERVICE_EMAIL)
.setServiceAccountScopes(Arrays.asList(DriveScopes.DRIVE))
.setServiceAccountPrivateKeyFromP12File(PKC_12_FILE)
.build();
Drive service = new Drive.Builder(httpTransport, jsonFactory, null)
.setHttpRequestInitializer(credential)
.build();
return service;
}
not sure if it matters though, especially since everything else seems to work just fine
Any fix ideas? I'm open to any workarounds as long as they work.
Also I would be fine with creating new file and just adding add-on code but it seems like I can't do it with API
I've found an acceptable solution which I described there
How can I create spreadsheet with included gs script by API?
Hope it helps someone ;)
Following the comment in How can I create spreadsheet with included gs script by API?, this is the app Scripts solution for making an authenticated POST:
function sendToHR(url,data){
var forDriveScope = DriveApp.getStorageUsed(); //needed to get Drive Scope requested
var dataToSend = [getName(),getID()];
for(key in data){
dataToSend.push(data[key])
}
var paylod = {
"data" : dataToSend
};
paylod = JSON.stringify(paylod);
var param = {
"method":"POST",
"headers" : {"Accept":"application/json","Authorization": "Bearer " + ScriptApp.getOAuthToken()},
"payload": paylod
};
return UrlFetchApp.fetch(url,param).getContentText();
}
And I have an example in python, that might be a bit more useful to you, in order for the python script to execute as a certien user I downloaded a JSON file with the keys from the projects console -> create credential -> get Key and download the file
def get_service():
global http_auth
global delegated_credentials
scopes = ['https://www.googleapis.com/auth/userinfo.email']
keyfile = os.path.join(CURR_DIR, JSON_FILENAME)
credentials = ServiceAccountCredentials.from_json_keyfile_name(
keyfile, scopes=scopes)
delegated_credentials = credentials.create_delegated(ADMIN_EMAIL)
http_auth = delegated_credentials.authorize(Http())
return build('SERVICE', 'v1', http=http_auth,
discoveryServiceUrl='DISCOVERY API SERVICE')
ADMIN_EMAIL is the actual admin email address and CURR_DIR and JSON_FILENAME are related to the downloaded file in your case I'm guessing you dont need admin rights just download the JSON file from the console of your current project and use your email address. Mine works when using the discovery API but a regular POST should be a bit faster to make

Unable to use sub account with Google Service Account

I needed to create a script that uploads the resulting screen shots to google drive.
I was hoping I could just auth in as my google user, but that seems... harder? So I abandoned that tact. Next I moved onto service accounts. This works fine (now) for my service account, but when I attempt to specify a user ($auth->sub) I get "Unauthorized client or scope in request.".
function buildService($userEmail) {
$DRIVE_SCOPE = 'https://www.googleapis.com/auth/drive';
$SERVICE_ACCOUNT_EMAIL = 'notsupplied#developer.gserviceaccount.com';
$SERVICE_ACCOUNT_PKCS12_FILE_PATH = 'pathtofile.p12';
$key = file_get_contents($SERVICE_ACCOUNT_PKCS12_FILE_PATH);
$auth = new Google_Auth_AssertionCredentials(
$SERVICE_ACCOUNT_EMAIL,
array($DRIVE_SCOPE),
$key);
$auth->sub = 'myuser#gmail.com';
$client = new Google_Client();
$client->setAssertionCredentials($auth);
return new Google_Service_Drive($client);
}
I'd love to abandon the service account and just auth with my regular google user if thats just as easy. Or solve how (in the api settings maybe?) I can ensure myuser#gmail.com can be used.
Refresh_token is the key here. In a webbrowser use this link to approve your google user:
https://accounts.google.com/AccountChooser?service=lso&continue=https%3A%2F%2Faccounts.google.com%2Fo%2Foauth2%2Fauth%3Fresponse_type%3Dcode%26scope%3Dhttps%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive%26redirect_uri%3Dhttps%3A%2F%2Fwww.example.com%2Foauth2callback%26access_type%3Doffline%26client_id%3D<CLIENT_ID>%26hl%3Den%26from_login%3D1%26as%3D34eac985232ba748&btmpl=authsub&hl=en&approval_prompt=force&access_type=offline
which will return a URL like https://www.example.com/oauth2callback?code=
Then post code=&client_id=&client_secret=&redirect_uri=&grant_type=authorization_code to https://accounts.google.com/o/oauth2/token
This will return a "refresh_token" parameter. Save this. Very important. If you don't get one you have to go to https://security.google.com/settings/security/permissions to revoke permissions from your app.
After you get the refresh token you're good to go:
$client = new Google_Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setRedirectUri($redirect_uri);
$client->addScope("https://www.googleapis.com/auth/drive");
$client->setAccessType('offline');
$token = $client->refreshToken('<YOUR_REFRESH_TOKEN>');
$service = new Google_Service_Drive($client);