When using this line of code in a Google Apps Script
var user = folders[n].getOwner().getEmail()
I get an error saying I am not authorized to perform such an action (your version may vary, I am translating from italian).
What gives? I am just retrieving an information, such as the owner of a folder.
When the script processes a folder I own, the error does not arise, the error arises when it encounters a folder not of mine. The matter is that this line of code is just for spotting folders which are not of mine, to avoid issuing method that would correctly rise an error, like setTrashed. The script looks for empty folders to delete them, but I cannot delete folders I am not the owner of of course. And yes I am into Google apps for business, does it make some difference?
There isn't any specifc warning about file.getOwner().getEmail(), but there is for Class Session.
In limited-privilege executions (such as in response to onOpen or
onEdit), we only return the identity of the active user if both the
user and the script owner are part of the same domain. This is to
protect the privacy of consumer users, who may not want their email
address exposed.
I have no problem with this in a consumer account.
The following function is an excerpt from a gist I posted for a previous question. It wraps the call to .getEmail() (or getUserLoginId() if you prefer) in a try ... catch block, so it avoids errors for users crossing Apps Domains.
function getFileInfo (file,fileType) {
var fileInfo = {
id: file.getId(),
name: file.getName(),
size: file.getSize(),
type: (fileType == "file") ? docTypeToText_(file.getFileType()) : "folder",
created: file.getDateCreated(),
description: file.getDescription(),
owner: file.getOwner()
}
try {
fileInfo.owner = file.getOwner().getEmail()//.getUserLoginId()
} catch(e)
{
// Possible permission problem
fileInfo.owner = "unknown";
}
return fileInfo;
}
UPDATE: Since this was first posted, something has changed. Now my consumer account encounters the aforementioned error when trying to access getOwner() for a file shared from another account. (March 3, 2013)
Related
I suppose my question is twofold: doGet() in the following context will just fail after 0.1~0.2 seconds without posting logs, so I have no idea how to troubleshoot it by myself. Additionally, if I'm having the script execute on my behalf, do I have to push a request with my authorization token to a more "pertinent" area than just the sheet name, such as within the iteration itself? Read further for more details:
I have a source spreadsheet where I am cross-referencing user inputted data to validate the information we have "on file". Most of our clients are over the age of 55, so I am trying to reduce end-user complexity by having the script run on my behalf whenever they need to use it (to bypass the Authorization screen, with the big scary "This application could be unsafe!" message). The way I've read to accomplish this seems to be with doGet(), so I set up a low-level HTTP Get request that just pushes a doGet() with my OAuth token, returning the sheet name. I also set up a masking function specifically to do this, and linked it to the button originally used for the iteration logic. The doGet() looks like this:
const doGet = e => {
Logger.log(`Recieved HTTP request.`);
const content = ContentService.createTextOutput(iterator(e));
Logger.log(content);
return content;
}
and the button that uses UrlFetchApp looks like:
const runMask = () => {
const active = SpreadsheetApp.getActiveSheet().getSheetName();
const v4 = 'https://script.google.com/macros/s/<scriptid>/dev' // ScriptApp.getService().getUrl() posts 404
UrlFetchApp.fetch(`${v4}?sheetName='${active}'`, {
headers: { Authorization: `Bearer ${ScriptApp.getOAuthToken()}` },
});
I have some logs set up within the real runMask() that proceed all the way to the end of the program, giving me real URLs and OAuth tokens, so I know it's making it through runMask() without an issue. However, the doGet() log doesn't post anything, even at the top of the function. I can see that it's executing the trigger in my execution log, but the log itself remains empty.
I've tried:
using ScriptApp.getService().getUrl() in place of v4: posts 404 in the log w/ truncated server response
replacing ${active} with the name of the sheet: same issue; logging ${active} also returns the correct name of the sheet.
Beyond this, I'm not even sure what to do. I have everything scoped correctly (auth/spreadsheets.currentonly, auth/script.external_request, and auth/userinfo.email), and I have no issues about operational security (as both the spreadsheet and script are written by me, the clients have no need to grant access to their entire drive). Before trying to implement doGet() and bypass the authorization screen, the iterator itself worked just fine. As such, I have chosen not to include it here, as it's hardly relevant (the function that executes the iteration function never makes it to that point).
I understand this has been quite the deluge of information; I'd be happy to provide more information or context as needed.
Getting ReferenceError: iterator is not defined (line 12, file "ag2")
With this:
const doGet = e => {
Logger.log(`Recieved HTTP request.`);
const content = ContentService.createTextOutput(iterator(e));
Logger.log(content);
return content;
}
Issued with url/exec?option=A
It runs with
const doGet = e => {
Logger.log(`Recieved HTTP request.`);
const content = ContentService.createTextOutput(JSON.stringify(e));
Logger.log(content);
return content;
}
and returns the appropriate stringified object
Only use the test URL (/dev) for testing the web app from a web browser.
Before doGet from a web browser using a versioned deployment (/exec) remember to publish a new version.
Assign a Google Cloud Project to your Google Apps Script project. For details see https://developers.google.com/apps-script/guides/cloud-platform-projects.
To make it easier to debug your avoid calling functions from a Google Apps Script method like createTextOutput, instead, assign the function result to a variable and use it as the method parameter, i.e. replace
const content = ContentService.createTextOutput(iterator(e));
by
const something = iterator(e);
const content = ContentService.createTextOutput(something);
For debugging purposes, create a function to call your doGet function, and check that it hasn't any problem to run, i.e.
function __test__doGet(){
const e = {
parameter: {}
}
doGet(e);
}
Related
Exception handling in google apps script web apps
Issue:
When I saw your question, I'm worried about I have everything scoped correctly (auth/spreadsheets.currentonly, auth/script.external_request, and auth/userinfo.email).
If you are using only the following scopes at oauthScopes of appsscript.json,
https://www.googleapis.com/auth/spreadsheets.currentonly
https://www.googleapis.com/auth/script.external_request
https://www.googleapis.com/auth/userinfo.email
Unfortunately, these scopes cannot be used for access to Web Apps. Although I'm not sure about the method for running your function of runMask, I thought that this might be the reason for your issue.
Solution:
If you want to access Web Apps of https://script.google.com/macros/s/<scriptid>/dev using the access token retrieved by ScriptApp.getOAuthToken(), please include the following scope.
https://www.googleapis.com/auth/drive.readonly
or
https://www.googleapis.com/auth/drive
After you include the above scope, please reauthorize the scopes, and test it again. When your function of iterator has already been declared and the script worked, by running runMask, you can see the log of Logger.log(Recieved HTTP request.) and Logger.log(content) at the log.
Reference:
Taking advantage of Web Apps with Google Apps Script
I cannot find anything about Google having updated policies on April 2022. This script was working without any issues before, then in the middle of the day, permissions needed to be re-added. Even with said permissions granted, Google still says access is denied. It was later discovered that the macro has not ran successfully since March 31st.
var newJobFile = DriveApp.getFileById(newJobID);
newJobFile.AddEditors(['group#gmail.com','admin#gmail.com');
newJobFile.setOwner('admin#gmail.com');
The last line is what generates the error. When debugging, I can see as the variable gets created, I cannot see any information about it. For example, if I add a "Filecreated.getName();" before the line with the error, the variable menu remains blank. I am not sure if this is normal. The "newid" variable is confirmed to have the spreadsheet ID. I can copy it into the URL and it takes me to the page. Why is this suddenly an issue and how can I fix it? I did not have a appscripts.json file before with OAuthScopes and it worked fine then. I've added it with the proper permissions, but it does not change anything. I've added several permission scopes to try to resolve this but none of them do. Any advice?
"oauthScopes": [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/spreadsheets"
]
EDIT: I am still having this issue. Even with everything controlled under one account, I cannot transfer ownership to 'Central_account#gmail.com' due to 'Exception : Access Denied : DriveApp'. I have found just one other person experiencing this same issue on the Google App Script group page. Is anyone else not having issues with transferring ownership starting in April on a regular, non-work account?
EDIT 2: Sorry for not uploading my code earlier, I had to clean it up first.
function onOpen(e) {
SpreadsheetApp.getUi()
.createMenu('Add') //creates toolbar entry to the right of 'Help'
.addItem('Capital project','newProject')
.addToUi();
}
function newProject(){
var dashboard = SpreadsheetApp.getActiveSpreadsheet(); //Stores dashboard into a variable for later
var template = SpreadsheetApp.openById('xxxxxx') //Opens the project sheet template in the backend
var nameEntry = getName(); //Prompts user for the name of the new job file
if (nameEntry == null){
return;
}
SpreadsheetApp.setActiveSpreadsheet(template); //Template is now the active sheet
var newJobID = copySheet(nameEntry); //Creates a copy of the New Project template
var newJobFile = DriveApp.getFileById(newJobID);
newJobFile.AddEditors(['group#gmail.com','admin#gmail.com');
newJobFile.setOwner('admin#gmail.com');
}
function getName(){
var ui = SpreadsheetApp.getUi();
var name = ui.prompt( //Prompts the user for an input name
'',
'?????-? Project Description',
ui.ButtonSet.OK_CANCEL);
var cancelCheck = name.getSelectedButton();
if (cancelCheck == ui.Button.CANCEL || cancelCheck == ui.Button.CLOSE) {
return null;
}
var sheetName = name.getResponseText();
return sheetName;
}
function copySheet(name) {
var activeSS = SpreadsheetApp.getActive();
var newFile = activeSS.copy(name) //Creates a copy of the New Project template
SpreadsheetApp.setActiveSpreadsheet(newFile); //Resets active spreadsheet to the recent copy
activeSS = SpreadsheetApp.getActive();
activeSS.getRange('A1').activateAsCurrentCell();
activeSS.getCurrentCell().setValue(name); //Set cell A1 to the name of the file
var newHyperlink = '=HYPERLINK("' + activeSS.getUrl() + '#gid=15580246",A1)';
activeSS.getRange('A2').activateAsCurrentCell();
activeSS.getCurrentCell().setValue(newHyperlink);
return activeSS.getId();
}
With logs, I can see that everything works as intended except for the .setOwner() method. It returns the Access Denied error. I have checked all google accounts and each has enabled CustomScripts to access their drive. Has .setOwner() been deprecated for non workspace accounts?
I've updated my original snippets to match my code.
Apparently the process to transfer file ownership between consumer accounts has changed. According to this guide, the prospective new owner needs to accept the transfer request. See example below:
And I found this article where you can see that the process was different a few months ago, you could set a new owner immediately without sending invitation.
I tested the setOwner() method with a Google Workspace account and it works as within Google Workspace you can directly transfer file ownership between users within the same organization, then I tested the same script with a Gmail account and tried to set another Gmail account as the new owner and I got the same error message: "Exception: Access denied: DriveApp".
Based on all the information, it seems that this behavior is expected as the process to change a file ownership for Gmail accounts is different now, you can’t set a new owner directly, the person you invite to own the file must accept your request to complete the transfer.
I don't have enough reputation to comment, but I'd second what Lorena Gomez said that it's likely an issue with your OAuthScopes. According to the Apps Script documentation, the setOwner() method on the File class requires the /auth/drive authorization scope.
It looks like you have both the /auth/drive and the /auth/drive.file scope configured, and I think the /auth/drive.file scope, which is narrower, is overriding the broader /auth/drive scope, which is required to call setOwner().
Have you tried removing the /auth/drive.file scope and running the script only with /auth/drive?
I'm trying to set a user's OU from an App Script inside App Maker.
(user is a variable with an email address)
function getUser(user) {
var x = AdminDirectory.Users.update(
{
orgUnitPath: "/",
userKey: user,
});
console.log("function ran");
}
This code errors with:
Exception: Invalid number of arguments provided. Expected 2-3 only at getUser (ServerScripts:107)
Invalid number of arguments provided. Expected 2-3 only
at getUser (ServerScripts:107)
at getUser (ClientHandoff:21:21)
at TestMoveOU.Panel1.Button1.onClick:1:1
What am I doing wrong here? Looking at the docs, you only need to provide the properties you're changing.
The Apps Script documentation says the following:
For detailed information on this service, see the reference documentation for the Admin SDK Directory API. Like all advanced services in Apps Script, the Admin SDK Directory service uses the same objects, methods, and parameters as the public API.
Therefore, we need to consult the documentation to get clarification on how to achieve this.
The method requires at least two parameters: that means that the the first parameter is a user object resource and the second parameter is the email address of the user: AdminDirectory.Users.update(resource, userKey). So you need to do this:
function getUser(user) {
var userResource = {
orgUnitPath: "/"
};
var updated = AdminDirectory.Users.update(userResource, user);
console.log(updated.primaryEmail);
}
So why do you need to specify the user email in the method when it is already being specified in the userResource object? Well, the email address in the userResource object would be the new value, in case you want to change the email address.
P.S. Perhaps you might wanna change the name of the function to something that is more of a match; updateUser() perhaps? I hope this helps!
My ultimate goal is to access the contents of a file uploaded via a Google Form from within a function triggered by formSubmit. I added some info in a comment to this question, but I think I need to update the question itself. When I deactivate the Smartsheets Sync add-on in the web form, this all works as expected. My theory is that the Smartsheets Sync add-on is not preserving the Event object in certain scenarios.
I began with:
function onFormSubmit (e) {
Logger.log (e);
}
I set up my trigger and tested a form submission, and in the log, I saw:
[<datetime>] {authMode=FULL, source=Form, response=FormResponse triggerUid=<id>}
as expected. I also explored the FormResponse object and verified that a valid Google Drive ID is in the response.
Next, I added a call to DriveApp.getFileById:
function onFormSubmit (e) {
Logger.log (e);
var responses = e.response.getItemResponses ();
var file = DriveApp.getFileById (responses [1].getResponse ());
Logger.log (file);
}
Resubmitting a form brings up a permission error with DriveApp. Not surprising, so I ran onFormSubmit directly from the script editor. It failed because it was invoked without an Event object, but it did invoke the dialog that allowed me to grant DriveApp permissions.
Now, when I submit a form, the Event object doesn't contain a FormResponse object. From the log:
[<datetime>] {authMode=FULL, source=Form, triggerUid=<id>}
So, does granting DriveApp permission somehow revoke permission to inspect the user's response? Alternatively, is there another way for me to use Google App Script to access a file uploaded via a Google Form?
The file ID is put into the file upload answer (response). The following code gets the answer to the file upload question, which is the file ID. Note that arrays are zero indexed, so the first question is at index zero. This code assumes that the file upload question is the very first question.
If this answers your question, you can mark it as correct by clicking the green arrow.
function onFormSubmit(e) {
var file,fileID,form,responseID,submittedResponse,uploadResponse;
responseID = e.response.getId();//The the ID of the current reponse
Logger.log('responseID: ' + responseID)
form = FormApp.getActiveForm();//Get the Form that this script is bound to
submittedResponse = form.getResponse(responseID);//Get the response that
//was just submitted
uploadResponse = submittedResponse.getItemResponses()[0];//This assumes
//that the very first question is the file upload
fileID = uploadResponse.getResponse();//Get the file ID of the file just uploaded
Logger.log('fileID: ' + fileID)
file = DriveApp.getFileById(fileID);//Get the file
Logger.log('file.getName(): ' + file.getName());//verify that this is
//the correct file - and that the code is working
}
I am trying to transfer ownership of files to Super Administrator. The essential part of the code is shown below. The same code works if I transfer ownership of a document that I own. But in this case the original owner is another user in the same domain. [EDIT] I know there is a "transfer-ownership-of-files-from-1-user-to-another" in admin panel, but this is needed for a case when multiple files/folders, from multiple owners needs to be transferred programmatically. [/EDIT]
So the simple scenario is:
user#domain.com owns a file
the ownership needs to be transferred to the admin#domain.com (Super Administrator). The code is running in AppEngine in Java Servlet. And the Super Administrator is authenticated with OAuth.
However it throws (500 OK) exception always in line update.execute(); (showed in the end).
Is this operation impossible even for a Super Admin?
Thanks for any tips!
try {
Drive service = getDriveService();
Permission newPerm = new Permission();
newPerm.setValue("admin#domain.com");
newPerm.setType("user");
newPerm.setRole("writer");
newPerm = service.permissions().insert(fileId, newPerm).execute();
newPerm.setRole("owner");
Update update = service.permissions().update(fileId, newPerm.getId(), newPerm);
update.setTransferOwnership(true);
update.execute();
} catch (IOException e) {
e.printStackTrace();
log.severe(e.getMessage());
}
The exception thrown looks like:
SEVERE: 500 OK
{
"code" : 500,
"errors" : [ {
"domain" : "global",
"message" : "Internal Error",
"reason" : "internalError"
} ],
"message" : "Internal Error"
}
I finally got this working at least partly. Thanks #ZigMandel for the "impersonation" hint! Now I followed this Domain-wide Delegation of Authority carefully. And used the GoogleCredential created using "Service account"-key. With this credential I can impersonate the Drive API operations like adding permissions and transferring ownership to Admin, for example.
The problem that still remains is that with current Drive API it is impossible to programmatically determine the owner email address, which is needed for impersonated credential creation. Now I need to find a workaround for this. Anyway the fundamental idea works now. Thanks!
You wont be able to do it from apps script.
Can be done from appengine. Ive done it but its beyond of the scope to explain here. Involves using the drive api on behalf of the file owner and adding the oauth permissions at the domain level.