Google Drive Transfer Ownership fails on file from Windows Google Drive App - google-drive-api

Same problem as Transfer ownership of file uploaded using Google drive API - seems like no solution there. I've added my own question a) for different language, and b) simpler code, with two methods tried. I've tried one of the answers from that, and don't think the other applies.
I need to transfer ownership of a Google Drive file or folder from me to someone else. If the file was created in Google Drive (web) then it works fine. If the file was created by the API (eg through Google Drive Windows app) then it fails with Insufficient permissions for this file [403]
I'm not using Google Apps for Work, so can't impersonate or use the Admin tool.
I've tried:
public bool ChangeItemOwnership(DriveService service, string fileId, string myPermissionId, string toPermissionId)
{
try
{
Permission permission = service.Permissions.Get(fileId, myPermissionId).Execute();
if (permission.Role == "owner")
{
permission = service.Permissions.Get(fileId, toPermissionId).Execute();
permission.Role = "owner";
PermissionsResource.UpdateRequest updatePermission = service.Permissions.Update(permission, fileId, toPermissionId);
updatePermission.TransferOwnership = true;
permission = updatePermission.Execute();
}
}
catch
{
return false;
}
return true;
}
and
public bool ChangeItemOwnership(DriveService service, string fileId, string myPermissionId, string toPermissionId)
{
try
{
Permission permission = service.Permissions.Get(fileId, myPermissionId).Execute();
if (permission.Role == "owner")
{
Permission patchedPermission = new Permission();
patchedPermission.Role = "owner";
PermissionsResource.PatchRequest patchPermission = service.Permissions.Patch(patchedPermission, fileId, toPermissionId);
patchPermission.TransferOwnership = true;
patchPermission.Execute();
}
}
catch
{
return false;
}
return true;
}
FYI, I authorise with:
static string[] Scopes = { DriveService.Scope.Drive };
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(_resourceStream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
I wondered whether the problem might be the "user" above but I haven't seen any other answer to what that should read - I tried putting my email address but same result. Also considered different scopes but can't see any that might apply.
It even fails on Google's API pages:
https://developers.google.com/drive/v2/reference/permissions/update
fileId = a file's Id - try one created through Google Drive Windows app
permissionId = from https://developers.google.com/drive/v2/reference/permissions/getIdForEmail
transferOwnership = true
Request body: role = owner
Really stuck on this. Can anyone help?
EDIT: As requested, the permissions relating to me, my user, on the file. I've hidden some things which might be better hidden (not sure). The only differences are the ETag and the SelfLink.
I get fromPermissionId by:
About about = service.About.Get().Execute();
fromPermissionId = about.PermissionId;
Below is permission from after the line:
Permission permission = service.Permissions.Get(fileId, fromPermissionId).Execute();
FILE FOR WHICH OWNERSHIP CANNOT BE HANDED OVER:
permission
{Google.Apis.Drive.v2.Data.Permission}
AdditionalRoles: null
AuthKey: null
Domain: "gmail.com"
EmailAddress: "***********#gmail.com"
ETag: "\"fbeGFVkC******djp1CYyuwDABw/aPlNOCkOt******X9klsSp45w\""
Id: "1726185******1763882"
Kind: "drive#permission"
Name: "James Carlyle-Clarke"
PhotoLink: null
Role: "owner"
SelfLink: "https://www.googleapis.com/drive/v2/files/0B******DI_IeTk9z******ZTRkk/permissions/1726185******1763882"
Type: "user"
Value: null
WithLink: null
FILE FOR WHICH OWNERSHIP CAN BE HANDED OVER:
permission
{Google.Apis.Drive.v2.Data.Permission}
AdditionalRoles: null
AuthKey: null
Domain: "gmail.com"
EmailAddress: "***********#gmail.com"
ETag: "\"fbeGFVkC******djp1CYyuwDABw/cT4fcr2Tka******EpimOAHa4hw\""
Id: "1726185******1763882"
Kind: "drive#permission"
Name: "James Carlyle-Clarke"
PhotoLink: null
Role: "owner"
SelfLink: "https://www.googleapis.com/drive/v2/files/1Uyg0o******hZKvIRS-ghAi0GeJ******7029Eofr1o/permissions/1726185******1763882"
Type: "user"
Value: null
WithLink: null
SECOND UPDATE:
Found a post suggesting using Insert() - Google Drive API ownership of files uploaded from service account
Tried it. It failed, with error Bad Request. User message: "You can't change the owner of this item yet. (We're working on it.)" [400]
Interestingly the old methods above still give the same 403 error.
Still looking for a solution, but I'm hoping someone from Google has been lurking and is now working on fixing it.
THIRD UPDATE:
Tried PropertyList properties = service.Properties.List(fileId).Execute();
There are no properties at all on either of the files (the one I can change ownership of, and the one I can't change ownership of).
If you need more then let me know.

Related

GSuite Admin SDK > Directory API: How do I add values to a custom schema for a user?

We are setting up some automation around SSO into AWS, but I have run into a problem.
There is a custom user attribute called AWSLab, and if a user does not have any IAMRole values populated for this attribute, then I need to add one.
The IAMRole field has Info type set to Text and No. of values set to Multi-value on the GSuite side, so I am putting it into an array for this API request.
Also, when I do a GET on the user and look at other schemas attached, I see this key named type set to work so I include that too.
Below is my function in Google Apps Script:
function check_user_access(){
var email = 'user#domain.com';
var role = [
'arn:aws:iam::123456789012:role/User',
'arn:aws:iam::123456789012:saml-provider/GoogleAppsProvider'
].join(',')
optArgs = {
projection: "full"
}
var user = AdminDirectory.Users.get(email, optArgs)
var schema = user.customSchemas
Logger.log("typeof(schema): %s", typeof(schema))
if(schema["AWSLab"]) {
Logger.log("schema[\"AWSLab\"] found on user '%s': %s", email, schema["AWSLab"])
} else {
Logger.log("schema[\"AWSLab\"] not found! Updating...")
Logger.log("schema before:\n\n%s\n", JSON.stringify(schema))
schema["AWSLab"] = { "IAMRole": [{ "type": "work", "value": role }] }
Logger.log("schema after:\n\n%s\n", JSON.stringify(schema))
AdminDirectory.Users.update(userFull, email) // line 35
}
}
When this function runs, I see this error:
Invalid Input: [AWSLab] (line 35, file "Labs")
I have some extra lines in there right now, to output some details for troubleshooting, but it's not helping me see the problem.
As it turns out, the issue was with the name of the custom schema.
At creation, the schema had a different name which was then edited at some point.
The key to figuring this out was populating the schema fields in question on a user with some dummy data, then pulling the user out via the API with a GET and examining the JSON.

Google Classroom, delete teacher from course backend Error 500

I'm able to delete a teacher from a course but in this particular case I get this error (In Classroom API Reference ):
{
"error": {
"code": 500,
"message": "Internal error encountered.",
"status": "INTERNAL"
}
}
From my NodeJS app:
{ code: 500,
message: 'Internal error encountered.',
errors:
[ { message: 'Internal error encountered.',
domain: 'global',
reason: 'backendError' } ],
status: 'INTERNAL' }
The code (NodeJS app):
let classroom = google.classroom('v1');
let data = {
auth : auth,
courseId : idCurso,
userId : emailDocente
};
classroom.courses.teachers.delete(data, (err, response) => {
//...code
});
I get this error from the UI.
More Info:
There are two teachers in the course: myemail#mydomain.com and
admin#mydomain.com admin#mydomain.com is the owner.
I need to remove myemail#mydomain.com The user is active and exists in GSuite Admin
SDK.
courseState is ACTIVE
The error was caused by: the teacher was the owner of the google drive's folder. I had to delete from drive and the trash too. Developers should validate this and show a more clear message or delete the drive folder before deleting the teacher. Hope you guys update this. Thanks.
Update: I haven't been able to transfer ownership using drive API due to the fact that the user which I'm using to connect to the API doesn't have permission to change ownership I cannot have the real user to log in and change ownership because it's an automated process which runs every x minutes. I have to assign ownership of the drive folder to the user I connect to the API -not the real user- otherwise this will not work. I hope Google devs fix this.

Unable to update Google Drive files using Drive API v3 -- The resource body includes fields which are not directly writable

I am trying to use Google Drive API (v3) to make updates to documents
in Google Drive.
I have read this migration guide:
Google Drive API v3 Migration
And coded it to make a new empty File() with the details I want to update
and then calling execute() with that and the file ID.
But i am still getting an error. Can anyone point out where I am doing wrong?
thanks alot!!
Error:
{
"code" : 403,
"errors" : [{
"domain" : "global",
"message" : "The resource body includes fields which are not directly writable.",
"reason" : "fieldNotWritable"
}],
"message" : "The resource body includes fields which are not directly writable."
}
Code snippet below:
File newFileDetails = new File();
FileList result = service2.files().list()
.setPageSize(10)
.setFields("nextPageToken, files(id, name)")
.execute();
List<File> files = result.getFiles();
if (files == null || files.size() == 0) {
System.out.println("No files found.");
} else {
System.out.println("Files:");
for (File file : files) {
if (file.getName().equals("first_sheet")) {
System.out.printf("%s (%s)\n", file.getName(), file.getId());
newFileDetails.setShared(true);
service2.files().update(file.getId(), newFileDetails).execute();
}
}
}
I had the same issue and found a solution. The key point is: you must create a new File object without Id and use it in update() method. Here is a piece of my code:
val oldMetadata = service!!.files().get(fileId.id).execute()
val newMetadata = File()
newMetadata.name = oldMetadata.name
newMetadata.parents = oldMetadata.parents
newMetadata.description = idHashPair.toDriveString()
val content = ByteArrayContent("application/octet-stream", fileContent)
val result = service!!.files().update(fileId.id, newMetadata, content).execute()
It works. I hope it'll help you.
Referring to https://developers.google.com/drive/v3/reference/files#resource-representations, you can see that shared isn't a writable field. If you think about it, this makes perfect sense. You can share a file by adding a new permission, and you can check if a file has been shared by reading the shared property. But saying a file is shared, other than by actually sharing it, makes no sense.
in the code it looks like this
Drive service... // your own declared implementation of service
File file = new File(); //using the com.google.api.services.drive.model package
// part where you set your data to file like:
file.setName("new name for file");
String fileID = "id of file, which you want to change";
service.files().update(fileID,file).execute();
trying to change the fields from remote files, and rewriting to this file can throw the security exception like exception below.
but it is not a solution for your question.
If you want to share file to another google account by email, you can do it with reimplementing authorization to authorization with using service account of your app, and the add the needed email, as owner of the file.
I was doing the same thing. My goal was to share my file programmatically with my Python code.
And yes, I was getting the same error:
"The resource body includes fields which are not directly writable"
I solved this problem by adding the service's email address of my Virtual Machine (I created it on my Compute Engine dashboard) to Editors of the file.
Then I ran this Python code in my VM:
from googleapiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
# Took the json file from my Google Cloud Platform (GCP) → IAM & Admin → Service Accounts:
service_key_file = 'service_key.json'
scope = 'https://www.googleapis.com/auth/drive'
credentials = ServiceAccountCredentials.from_json_keyfile_name(service_key_file, scopes=scope)
driveV3 = build('drive', 'v3', credentials=credentials)
fileId = '1ZP1xZ0WaH8w2yaQTSx99gafNZWawQabcdVW5DSngavQ' # A spreadsheet file on my GDrive.
newGmailUser = 'testtest#gmail.com'
permNewBody = {
'role': 'reader',
'type': 'user',
'emailAddress': newGmailUser,
}
driveV3.permissions().create(fileId=fileId, body=permNewBody).execute()
print(f"""The file is now shared with this user:
{newGmailUser}\n
See the file here:
https://docs.google.com/spreadsheets/d/1ZP1xZ0WaH8w2yaQTSx99gafNZWawQabcdVW5DSngavQ""")

addViewer the method used with the class DriveApp always sends the email to the recipient. Can I disable the email?

Code A and B do the same things but in case A when add a viewer, the command addViewer don't send the email to currentUser. Instead, in the case B when add a viewer the currentUser receive an email of file sharing.
//CASE A
var f = DocsList.getFolderById(folder.getId());
f.addViewer(currentUser);
//CASE B
var f = DriveApp.getFolderById(folder.getId());
f.addViewer(currentUser);
I would like to share wihouth automatic email because at the end of procedure I'll send a custom email (with the file inside folder link) if the procedure completed successfull, otherwaise I will delete the folder.
How can I do? DocList will soon be deprecated!!
To do this you'll need to use the Drive API permissions.insert method [notice it's not DriveApp] to silently insert a permission.
The Drive API is exposed to Google Apps Script through the Google Drive Advanced Service. To use it you'll need to enable it for your project.
Once that's done your permission insert code might look like this:
/**
* Insert a new permission without sending notification email.
*
* #param {String} fileId ID of the file to insert permission for.
* #param {String} value User or group e-mail address, domain name or
* {#code null} "default" type.
* #param {String} type The value "user", "group", "domain" or "default".
* #param {String} role The value "owner", "writer" or "reader".
*/
function insertSilentPermission(fileId, value, type, role) {
var request = Drive.Permissions.insert({
'value': value,
'type': type,
'role': role,
'withLink': false
},
fileId,
{
'sendNotificationEmails': false
});
}
I've edited this answer to include Laura's feedback below so it now works as expected.
the method require "p" uppercase: Drive.Permissions.insert
But after this little thing, the function returns an error:
The numbers of arguments is invalid. Expected 2-3
I tried with this:
Drive.Permissions.insert(
{
'role': 'reader',
'type': 'user',
'value': 'username#test.it'
},
fileId,
{
'sendNotificationEmails': 'false'
});
It works fine but only for FILE. For FOLDER (in fileId i put a folder ID) happens a strange thing:
Before run this code sharing Settings of folder are:
Share only with specific persons
person#example.com can view
admin#example.com is owner
After running the code sharing Settings of folder become:
anyone in example.com with the link can view --> this is WRONG
username#example.com can view --> this is OK
person#example.com can view
admin#example.com is owner
If you are sharing a file with Google Scripts, the email notification will always go to the viewers and editors. The option to not send that email is only available if you share a file manually inside Google Drive.

Get shared link through Google Drive API

I am working on an app using Google Drive. I want the user to be able to share files by link, setting the permissions to anyone and withLink as described in the Google Developers documentation.
However, I cannot figure out what link to share. When I share a file in the Google Drive browser interface, I see the Link to share in the format:
https://docs.google.com/presentation/d/[...]/edit?usp=sharing
A link in this format is not part of the file resource object, nor it is returned from the http call setting the permissions. I hope someone can explain to me how to get this link through the REST api?
You can use the alternateLink property in the File resource to get a link that can be shared for opening the file in the relevant Google editor or viewer:
https://developers.google.com/drive/v2/reference/files
Update
[With API V3](https://developers.google.com/drive/api/v3/reference/files it is suggested to use the webViewLink property.
To actually enable link sharing using Google Drive API:
drive.permissions.create({
fileId: '......',
requestBody: {
role: 'reader',
type: 'anyone',
}
});
Get the webLinkView value using:
const webViewLink = await drive.files.get({
fileId: file.id,
fields: 'webViewLink'
}).then(response =>
response.data.webViewLink
);
In my case using the PHP Api v3, for the link to be non-empty you must define that you request this field... and if you have the right permissions:
so something like this:
$file =self::$service->files->get("1ogXyGxcJdMXt7nJddTpVqwd6_G8Hd5sUfq4b4cxvotest",array("fields"=>"webViewLink"));
Here's a practical example on how to get the WebViewLink file property (A.K.A. File edit link):
$file = $service->files->get($file_id, array('fields' => 'webViewLink'));
$web_link_view = $file->getWebViewLink();
OR
$sheetsList = $drive_service->files->listFiles([
'fields' => 'files(id, name, webViewLink, webContentLink)',
]);
$web_link_view = $sheetsList->current()->getWebViewLink();
Pay attention that you should load the file specifying which fields you wanna bring with it (In this case, webViewLink). If you don't do that, only id and name will be available.
In case you need to adjust the file sharing settings, this is how you do it:
$permissions = new \Google_Service_Drive_Permission();
$permissions->setRole('writer');
$permissions->setType('anyone');
$drive_service->permissions->create($file_id, $permissions);
Possible values for setRole() and setType() can be found here: https://developers.google.com/drive/api/v3/reference/permissions/create
For python, I only needed to get the file "id".
Then "created" the link like this:
def create_folder(folder_name, folder_id):
"""Create a folder and prints the folder ID and Folder link
Returns : Folder Id
"""
try:
# create drive api client
service = build("drive", "v3", credentials=creds)
file_metadata = {
"name": folder_name,
"mimeType": "application/vnd.google-apps.folder",
"parents": [folder_id],
}
file = (
service.files().create(body=file_metadata, fields="id").execute()
)
id = file.get("id")
print(
f'Folder ID: "{id}".',
f'https://drive.google.com/drive/u/0/folders/{id}',
)
return id
except HttpError as error:
print(f"An error occurred: {error}")
return None