Using addEditor(s) with users outside your workspace - google-apps-script

I used appscript to create copies of a document and share them with a particular user, this works fine in that the code creates the document and the user receives an invitation to edit, but I have found that at least some users are not able to edit the document. When I view the sharing options on the individual docs, I can see that they are restricted to my workspace only.
I had the same issue when doing this process manually, but thought addEditor would be able to override this, as the shared Gdrive is not restricted in this way overall (i.e, we can share docs with individuals by adding them as editor, from outside our organisation.) . I do not want to have to individually share >100 docs per week manually, so any fixes/workarounds would be very appreciated.
Some limitations: the doc must be shared to one user exactly, but still be accessible to everyone in my workspace.
All of the users I am sharing the document are external to the organisation.
I tried making the master copy of the file accessible to anyone who had the link, hoping that this would mean they could access the file without manually approving the request, but it looks like this doesn't transfer to the copies. I'm not sure enough of the actual issue to know that this would solve it.
This is how the code looks:
function createCopy() {
let file = DriveApp.getFileById("ID of file to copy");
let sheet = SpreadsheetApp.openByUrl('Sheet with names and emails').getSheetByName("Sheet1");
let range = sheet.getRange('D2:D5');
let email = sheet.getRange('C2:C5').getValues().flat();
let values = range.getValues();
let folder = DriveApp.getFolderById("Destination Folder");
for (let i = 0; i < values.length; i++) {
Logger.log(values[i]);
file.makeCopy(values[i].toString(), folder).addEditor(email[i]);
}
}

From the question
Some limitations: the doc must be shared to one user exactly, but still be accessible to everyone in my workspace.
Try this
file.makeCopy(values[i].toString(), folder).addEditor(email[i]);
by
file.makeCopy(values[i].toString(), folder)
.addEditor(email[i])
.setSharing(DriveApp.Access.DOMAIN, DriveApp.Permission.EDIT)
References
https://developers.google.com/apps-script/reference/drive/file#setsharingaccesstype,-permissiontype

Related

Is it possible to make a getCommenters() functionality in Google Apps Script?

The DriveApp > File class of Google Apps Script has these methods available:
getViewers()
getEditors()
But getCommenters method is not available. (This is interesting since addCommenter, addCommenters, and removeCommenter methods are available.)
I'm making a script where I'm changing file and folder permissions based on email addresses in a Spreadsheet. I would like to have a getCommenters functionality so I can compare if the commenter emails in the Spreadsheet are already commenters to the file, so there will be no need to again push those emails using addCommenters. (Pushing it will generate a new email to the user.) There is no problem in doing this with Viewers and Editors, as the methods are available.
Question: Is it possible to create a function that will mimic the supposed functionality of getCommenters? Or at least pull an array of email addresses of the commenters?
getViewers() returns viewers and commentators. You can filter the list using getAccess():
const getCommenters = (file) =>
file.getViewers().filter(user=>
file.getAccess(user) == "COMMENT")
Getting the right nudge from #TheMaster's answer, this code worked for what I needed:
let file = DriveApp.getFileById('xxxxxxxxxxx-FILE_ID-xxxxxxxxxxx');
let userlist = file.getViewers().filter((user) => file.getAccess(user) == 'COMMENT');
let emaillist = [];
for (var i in userlist) {
emaillist.push(userlist[i].getEmail());
}
Thereafter, emaillist becomes an array of email addresses having COMMENT permissions.

How to properly set sharing permissions on a new file in a GAS-based form

I have had some inconsistent results using GAS to set sharing for files created in the script, from an uploaded blob.
Gobally, the GAS script serves an Html form, collects the uploaded file, and makes some logging and processing. It is a heavily modified version of this https://ctrlq.org/code/19747-google-forms-upload-files adapted for report submissions from students.
The only clues I have so far are related to the position of the line that sets the sharing, relative to the lines in which the file is created:
var folder = DriveApp.getFolderById(dropbox);
// Get the blob
var contentType = data.substring(5,data.indexOf(';'))
var bytes = Utilities.base64Decode(data.substr(data.indexOf('base64,')+7))
var blob = Utilities.newBlob(bytes, contentType, filename)
// Create a folder for the file if it does not exist
try{
var subFolder = folder.getFolder(tp);
}
catch(e) {
var subFolder = folder.createFolder(tp);
}
var file = subFolder.createFile(blob) // Create the file
From this point on, strange things happen. I have narrowed down the problem to the actual line that sets the sharing:
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW)
If this is immediately after the file creation, it works only some times. Over 200 reports have been submitted, and ~50% of them are visible to anyone with the link, while the rest are only shared privately.
Whether these permissions are set or not, everything else completes successfully. So i have no error log to know for sure what is going on.
By moving this line to the end of the function, subsequent submissions have properly set the permissions:
// .... processing lines that include logging to a spreadsheet, sending emails, and setting other permissions.
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW)
return "OK";
My guess is that, for some reason, the time between file creation and permission modification affects the effectivenes of the .setSharing() line.
One hypothesis is that the file may be inheriting the parent folder's permissions (which is not public) with "lag". In other words, the permissions set by the .setSharing() function would be overwritten by some obsecure inheritance function behind file creation line:
var file = subFolder.createFile(blob) // Create the file
The "default" permissions must surely be set at some point, but there is no "flush" function, that I know of, to force or wait for this to happen (such as the one used in Google Spreadsheets).
EDIT: i have found a thread where similar "non determinism" occurs, involving permission inheritance from the parent folder (see this issue).
I would like to know how to make sure that permissions are always set correctly.
Cheers
:)

Google Drive Folders/Files Created Using API Not Visible on Google Interface

This is rather strange. I used Google Drive API to create a folder in Google Drive and then uploaded a file there. I can retrieve the folder and file using the same API (the code is working fine in all respect). However, when I go to Google Drive Web interface, I can't seem to find the folder or file. The file also doesn't sync to my local drive. Is there a setting in API or elsewhere to set the "visibility" ON?
Thank you in advance.
I had the same issue. Turned out to be permissions. When the file is uploaded by the service account, the service account is set as the owner, and then you can't see the files from the Drive UI. I found the solution online (but can't seem to find it again...)
This is what I did...
It's C#, your question didn't specify. The code you're interested in is the permission stuff after you get the response body after the upload...
FilesResource.InsertMediaUpload request = service.Files.Insert(body, stream, "text/plain");
request.Upload();
//Start here...
Google.Apis.Drive.v2.Data.File file = request.ResponseBody;
Permission newPermission = new Permission();
newPermission.Value = "yourdriveaccount#domain.com";
newPermission.Type = "user";
newPermission.Role = "reader";
service.Permissions.Insert(newPermission, file.Id).Execute();
The file was visible on the Drive UI after this. I tried specifying "owner" for the role, like the api suggests, but I got and error saying that they're working on it. I haven't played around with the other setting yet, (I literary did this last night). Let me know if you have any luck with any other combinations on permissions.
Hope that helps
I had the same issue, but this got solved my using a list data type for parents parameter, eg: If one wants to create a folder under a folder("1TBymLMZXPGkouw-lTQ0EccN0CMb_yxUB") then the python code would look something like
drive_service = build('drive', 'v3', credentials=creds)
body={
'name':'generated_folder',
'parents':['1TBymLMZXPGkouw-lTQ0EccN0CMb_yxUB'],
'mimeType':'application/vnd.google-apps.folder'
}
doc = drive_service.files().create(body=body).execute()
While permission issue is the main cause of this problem. What I did to make the folders or files appear after I uploaded it with service account was to specify the parent folder. If you upload / create folder / files without parent folder ID, that object's owner will be the service account that you are using.
By specifying parent ID, it will use the inherited permissions.
Here's the code I use in php (google/apiclient)
$driveFile = new Google\Service\Drive\DriveFile();
$driveFile->name = $req->name;
$driveFile->mimeType = 'application/vnd.google-apps.folder';
$driveFile->parents = ['17SqMne7a27sKVviHcwPn87epV7vOwLko'];
$result = $service->files->create($driveFile);
When you create the folder, you should ensure you set a parent, such as 'root'. Without this, it will be not appear in 'My Drive' and only in Search (Have you tried searching in the UI?)
Since you have already created the folder, you can update the file and give it the parent root as well.
You can test it out using the Parents insert 'Try it now' example.
Put your Folders ID in the fileId box, then in the request body, add root in the ID field.
private void SetFilePermission(string fileId)
{
Permission adminPermission = new Permission
{
EmailAddress = "test#gmail.com", // email address of drive where
//you want to see files
Type = "user",
Role = "owner"
};
var permissionRequest = _driveService.Permissions.Create(adminPermission, fileId);
permissionRequest.TransferOwnership = true; // to make owner (important)
permissionRequest.Execute();
Permission globalPermission = new Permission
{
Type = "anyone",
Role = "reader"
};
var globalpermissionRequest = _driveService.Permissions.Create(globalPermission, fileId);
globalpermissionRequest.Execute();
}

Impossible to add an owner to a Google Site using Google Apps Script

I wrote a Google Apps Script in order to make a copy of a Google Site template, and then add a new owner to this copy.
I use the copySite function, which is asynchronous as the documentation states :
The copy is asynchronous, and the copy operation may still be ongoing
even though a reference to the site has been returned
After the site has been copied, I want to add a new owner to the site using the addOwner function, just like this :
var template = SitesApp.getSiteByUrl(TEMPLATE_URL); // Retrieve the template
var copyOfTemplate = SitesApp.copySite(domain, url, googleSiteName, summary, template);
copiedTemplateURL = copyOfTemplate.getUrl();
copyOfTemplate.addOwner(adminMail); // Add the customer as owner of the new site
The script runs well (no error), the Google site is created, but the owner is not added, and sometimes, the "getUrl()" function doesn't return anything. I tried to add a delay (Utilities.sleep) of 20 seconds, but it doesn't seem to work, and I think it is a "dirty" solution.
Can anybody think of a workaround for this case ? Thanks
I finally came up with a workaround to my issue.
It seems that if the Google site you copy contains many pages, the process time for this copy will be longer and if you try to add an owner during this time, it won't work. Moreover, if you use the getOwners() function after adding an owner(during the copy time), it will return you the correct owners. However, when the site is entirely copied, the owner you added is not mentioned in the sharing settings and therefore can not access the copy of your site template.
The idea is to wait for the copy to be completed, by counting the number of descendant pages that are returned by the getAllDescendants() function on the copied template, and compare it to the number returned by the same function on your original template. Then, you can safely add a new owner.
var timer = 1000;
var template = SitesApp.getSiteByUrl("https://sites.google.com/a/domain.ext/yourtemplate"); // Retrieve the template
var copyOfTemplate = SitesApp.copySite(domain, url, googleSiteName, summary, template);
var descendants = copyOfTemplate.getAllDescendants().length;
var numberOfPages = template.getAllDescendants().length;
while(descendants < numberOfPages || descendants === null) {
Utilities.sleep(timer);
timer = timer + 5000; // sort of "additionnal backoff"
descendants = copyOfTemplate.getAllDescendants().length;
}
//At this point the copy should be completed
copyOfTemplate.addOwner("yourNewOwner#domain.ext"); // Add the customer as owner of the new site
My template contains 41 descendant pages, it takes approximately 35 seconds to copy it properly.

Trigger Google Apps Script by email

I'm looking for examples of a pattern where a demon script running within a GoogleAppsForBusiness domain can parse incoming email messages. Some messages will will contain a call to yet a different GAScript that could, for example, change the ACL setting of a specific document.
I'm assuming someone else has already implemented this pattern but not sure how I go about finding examples.
thx
You can find script examples in the Apps Script user guide and tutorials. You may also search for related discussions on the forum. But I don't think there's one that fits you exactly, all code is out there for sure, but not on a single script.
It's possible that someone wrote such script and never published it. Since it's somewhat straightforward to do and everyone's usage is different. For instance, how do you plan on marking your emails (the ones you've already read, executed, etc)? It may be nice to use a gmail filter to help you out, putting the "command" emails in a label right away, and the script just remove the label (and possibly set another one). Point is, see how it can differ a lot.
Also, I think it's easier if you can keep all functions in the same script project. Possibly just on different files. As calling different scripts is way more complicated.
Anyway, he's how I'd start it:
//set a time-driven trigger to run this function on the desired frequency
function monitorEmails() {
var label = GmailApp.getUserLabelByName('command');
var doneLabel = GmailApp.getUserLabelByName('executed');
var cmds = label.getThreads();
var max = Math.min(cmds.length,5);
for( var i = 0; i < max; ++i ) {
var email = cmds[i].getMessages()[0];
var functionName = email.getBody();
//you may need to do extra parsing here, depending on your usage
var ret = undefined;
try {
ret = this[functionName]();
} catch(err) {
ret = err;
}
//replying the function return value to the email
//this may make sense or not
if( ret !== undefined )
email.reply(ret);
cmds[i].removeLabel(label).addLabel(doneLabel);
}
}
ps: I have not tested this code
You can create a google app that will be triggered by an incoming email message sent to a special address for the app. The message is converted to an HTTP POST which your app receives.
More details here:
https://developers.google.com/appengine/docs/python/mail/receivingmail
I havn't tried this myself yet but will be doing so in the next few days.
There are two ways. First you can use Google pub/sub and handle incomming notifications in your AppScrit endpoint. The second is to use the googleapis npm package inside your AppScript code an example here. Hope it helps.
These are the steps:
made a project on https://console.cloud.google.com/cloudpubsub/topicList?project=testmabs thing?
made a pubsub topic
made a subscription to the webhook url
added that url to the sites i own, i guess? I think I had to do DNS things to confirm i own it, and the error was super vague to figure out that was what i had to do, when trying to add the subscription
added permission to the topic for "gmail-api-push#system.gserviceaccount.com" as publisher (I also added ....apps.googleusercontent.com and youtrackapiuser.caps#gmail.com but i dont think I needed them)
created oauth client info and downloaded it in the credentials section of the google console. (oauthtrash.json)