Using the Google Apps Email Settings API a delegate is deleted with the script below. If an error occurs, for example trying to delete delegates that do not exist, the following message is returned:
Exception: Request failed for returned code 400. Truncated server
response: <xml version="1.0" encoding="UTF-8"?>
<AppsForYourDomainErrors> <error errorCode="1303"
invalidInput="XXXX#XXXX.com" reason="E... (use muteHttpExceptions
option to examine full response)
However when using muteHttpExceptions = true the authentication fails:
Exception: Failed to authenticate for service: google
This forces me to use a try / catch structure instead of examining the HTTPResponse object. I would like to know why this is happening and how to solve it.
The test function:
function test() {
var consumerKey = 'XXXX';
var consumerSecret = 'XXXX';
var domain = 'XXXX.com';
var userName = 'XXXX'
var delegateName = 'XXXX#XXXX.com'
var serviceName = 'google';
var scope = 'https://apps-apis.google.com/a/feeds/emailsettings/2.0/';
var oAuthConfig = UrlFetchApp.addOAuthService(serviceName);
oAuthConfig.setRequestTokenUrl('https://www.google.com/accounts/OAuthGetRequestToken?scope=' + scope);
oAuthConfig.setAuthorizationUrl('https://www.google.com/accounts/OAuthAuthorizeToken');
oAuthConfig.setAccessTokenUrl('https://www.google.com/accounts/OAuthGetAccessToken');
oAuthConfig.setConsumerKey(consumerKey);
oAuthConfig.setConsumerSecret(consumerSecret);
var fetchParameters = {};
fetchParameters.oAuthServiceName = serviceName;
fetchParameters.oAuthUseToken = 'always';
fetchParameters.method = 'DELETE';
fetchParameters.muteHttpExceptions = false;
try {
var url = 'https://apps-apis.google.com/a/feeds/emailsettings/2.0/'+ domain + '/' + userName + '/delegation/' + delegateName;
var result = UrlFetchApp.fetch(url, fetchParameters);
} catch (e) {
Logger.log(e);
}
}
This question has been posted to the Google Apps Script issue tracker as ticket 3478 and acknowledged as a bug. The ticket remains open but the following workaround has been proposed:
Revoking access in your Google Account's Security settings to both www.google.com AND the source of Apps Script (spreadsheet etc).
Change the oAuthServiceName parameter to something else.
Re-running the script
Related
I am trying to use Google Ads on Google Apps Script using this guide. I created client id and client secret on Google Console's API & Services.
Not too sure if this configuration is correct but the account is linked to Google Apps Script as I have pagespeed insights as well and there are some requests on the dashboard. I added https://www.googleapis.com/auth/drive as the scope. Again not too sure if I should add Google Ads to the scope. Lastly, got my refresh token from Google Auth playground. When I run the script above I got the following error:
Error: No access token received: {
"error": "invalid_client",
"error_description": "Unauthorized"
}
authenticate_ # test.gs:120
withRefreshToken # test.gs:144
initializeOAuthClient # test.gs:28
Honestly not too sure what I am doing wrong here so any help would be very much appreciated. Thank you.
Edit
Codes:
//From Google Console API & Services
var CLIENT_ID = '"MY_CLIENT_ID';
var CLIENT_SECRET = 'MY_CLIENT_SECRET';
//From Google Authplayground
var REFRESH_TOKEN = 'REFRESH_TOKEN';
// Enter scopes which should match scopes in File > Project properties
// For this project, e.g.: https://www.googleapis.com/auth/drive
var SCOPES = "https://www.googleapis.com/auth/adwords";
// Script ID taken from 'File > Project Properties'
var SCRIPT_ID = 'MY_SCRIPT_ID';
var authUrlFetch;
// Call this function just once, to initialize the OAuth client.
function initializeOAuthClient() {
if (typeof OAuth2 === 'undefined') {
var libUrl = 'https://developers.google.com/google-ads/scripts/docs/examples/oauth20-library';
throw Error('OAuth2 library not found. Please take a copy of the OAuth2 ' +
'library from ' + libUrl + ' and append to the bottom of this script.');
}
var tokenUrl = 'https://accounts.google.com/o/oauth2/token';
authUrlFetch = OAuth2.withRefreshToken(tokenUrl, CLIENT_ID, CLIENT_SECRET,
REFRESH_TOKEN, SCOPES);
}
/**
* Execute a remote function.
* #param {string} remoteFunctionName The name of the function to execute.
* #param {Object[]} functionParams An array of JSON objects to pass to the
* remote function.
* #return {?Object} The return value from the function.
*/
function executeRemoteFunction(remoteFunctionName, functionParams) {
var apiParams = {
'function': remoteFunctionName,
'parameters': functionParams
};
var httpOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
payload: JSON.stringify(apiParams)
};
var url = 'https://script.googleapis.com/v1/scripts/' + SCRIPT_ID + ':run';
var response = authUrlFetch.fetch(url, httpOptions);
var data = JSON.parse(response.getContentText());
// Retrieve the value that has been returned from the execution.
if (data.error) {
throw Error('There was an error: ' + response.getContentText());
}
return data.response.result;
}
// Paste in OAuth2 library here, from:
// https://developers.google.com/google-ads/scripts/docs/examples/oauth20-library
I have pasted the oauth2.0 library under the codes above.
Edit 2
I fixed the part of function initializeOAuthClient. It now shows execution complete, but when I try to run function executeRemoteFunction I am getting TypeError: Cannot read property 'fetch' of undefined. I am guessing I have to input remoteFunctionName and functionParams but where do I find them?
I make a script to generate a form with data from my server, and deploy it as a web app. The script is in a google standard project, and is in develop status with specific user specified for testing. Proper scopes are indicated, and the user's credentials are successfully generated. Following the guide for "execute a function", however, I always got error: {"error": {"code": 404, "message": "Requested entity was not found.", "status": "NOT_FOUND"}}. Below are my codes:
in the code.js:
function doGet(e){
Logger.log('doGet is called');
var params = JSON.stringify(e);
Logger.log('params: '+params);
var mywork = params.parameters['mywork'];
var formName = params.parameter['formName'];
var subject = params.parameter['subject'];
var result = makeQuestionsForm(mywork, formName, subject);
return ContentService.createTextOutput(JSON.stringify(result))
.setMimeType(ContentService.MimeType.JSON);
}
function doPost(e){
Logger.log('doPost is called');
var params = JSON.stringify(e);
Logger.log('params: '+params);
var mywork = params.parameters['mywork'];
var formName = params.parameter['formName'];
var subject = params.parameter['subject'];
var result = makeQuestionsForm(mywork, formName, subject);
return ContentService.createTextOutput(JSON.stringify(result))
.setMimeType(ContentService.MimeType.JSON);
}
function makeQuestionsForm(mywork, formName, subject) {
// code for generating the form
}
My code calling the script:
service = build('script', 'v1', credentials=self.creds)
parameters = {'mywork':self.sheet_body,'subject':request.session['subject'],'formName':title}
api_request = {
"function": "doPost",
"parameters": parameters,
"devMode": True
}
response = service.scripts().run(body=api_request,
scriptId=SCRIPT_ID).execute()
I did many tries: specifying function as 'doPost', 'doGet', or 'makeQuestionsForm', specifying scriptId as the id of project or id of script. All got the "NOT_FOUND" result.
Alternatively, I tried urlopen method directly calling the script's url, with token provided at request header. Got not-authorized.
Can you help see, what I did wrong or missed? Thanks
This is a function that I use to access a webapp from a gmail addon:
function deleteBlackList() {
const url=Utilities.formatString('%s?mode=dable',burl());
const params={"muteHttpExceptions":true,"method":"get","headers": {"Authorization": "Bearer " + ScriptApp.getOAuthToken()}};
const resp=UrlFetchApp.fetch(url,params);
const ts=Utilities.formatDate(new Date(), "redacted", "E MMM dd,yyyy HH:mm:ss");
Logger.log(url);
Logger.log(resp.getContentText("UTF-8"));
var action=CardService.newAction().setFunctionName('buildEmailFilterUI');
return CardService.newCardBuilder()
.setHeader(CardService.newCardHeader().setTitle('Deleted BlackListed Emails')).addSection(CardService.newCardSection()
.addWidget(CardService.newTextParagraph().setText(Utilities.formatString('TimeStamp: %s\n%s\n',ts,resp.getContentText()))))
.addSection(CardService.newCardSection().addWidget(CardService.newTextButton().setText('Return to Opening Form').setOnClickAction(action)))
.build();
}
The burl is the exec version of the url and I just hardwire it into the addon.
I found the problem. To execute a function, the project has to be deployed as an API executable. It was deployed as web apps, that's why it's NOT FOUND. After deploy the project as an api executable, now, I can access the function. Permission denied though at the moment, but that's another issue.
I'm trying to used Google Apps Script to pull Questrade account information into a Google sheets spreadsheet. I've added the oAuth2 library from GitHub(https://github.com/googleworkspace/apps-script-oauth2) then mostly copy and pasted (with minor edits) from the example code.
The weird thing is this code has worked, exactly how it is, but a day later it no longer works and returns the following:
Exception: Request failed for https://api01.iq.questrade.com returned code 401. Truncated server response: {"code":1017,"message":"Access token is invalid"} (use muteHttpExceptions option to examine full response)
My Google Apps Script is posted below. I've only removed my Questrade Client_ID and Google Script Script_ID. I have three buttons in my spreadsheet which I've linked to functions in the script:
Button 1 - QT oAuth - calls showSidebar
Button 2 - Load Account Info - calls makeRequest
Button 3 - QT Logout - calls logout
Typically, I press the QT Logout button to reset 0Auth2 services then I press the QT oAuth button. This seems to successfully go through the authorization process. I then press the Load Account Info button and about 99 times out of 100 I get the invalid access token message. I don't know if it's relevant, but when I log into Questrades API hub I can see that the script is adding an authorization after the QT oAuth button is pressed but it seems to disappear after about a minute.
The script:
function getQTService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth2.createService('QT')
// Set the endpoint URLs.
.setAuthorizationBaseUrl('https://login.questrade.com/oauth2/authorize')
.setTokenUrl('https://login.questrade.com/oauth2/token')
// Set the client ID and secret.
.setClientId('Client_ID')
.setClientSecret(' ') //there is no client secret but oAuth2 requires one
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
.setCache(CacheService.getUserCache())
// Set the scopes to request (space-separated for Google services).
.setScope('read_acc')
}
function showSidebar() {
var QTService = getQTService();
if (!QTService.hasAccess()) {
var authorizationUrl = QTService.getAuthorizationUrl();
var template = HtmlService.createTemplate(
'Authorize. ' +
'Reopen the sidebar when the authorization is complete.');
template.authorizationUrl = authorizationUrl;
var page = template.evaluate();
SpreadsheetApp.getUi().showSidebar(page);
} else {
// ...
}
}
function authCallback(request) {
var QTService = getQTService();
var isAuthorized = QTService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
function makeRequest() {
var QTService = getQTService();
var token = QTService.getAccessToken();
var spreadsheet = SpreadsheetApp.openById("Script_ID");
// Get account number
var response = UrlFetchApp.fetch('https://api01.iq.questrade.com/v1/accounts',{
headers: {
Authorization: 'Bearer ' + token
}
});
var json = response.getContentText();
var accountdata = JSON.parse(json);
var j = 0;
while(j < accountdata.accounts.length) {
var Account_num = accountdata.accounts[j].number;
var Account_type = accountdata.accounts[j].type;
var sheet = spreadsheet.getSheetByName(Account_type);
// GET CASH BALANCE
var response = UrlFetchApp.fetch('https://api01.iq.questrade.com/v1/accounts/' + Account_num + '/balances',{
headers: {
Authorization: 'Bearer ' + token
}
});
json = response.getContentText();
var balancedata = JSON.parse(json);
var i = 0;
while(balancedata.perCurrencyBalances[i].currency != 'CAD') {
i=i+1;
}
//send cash value to spreadsheet
sheet.getRange("G1").setValue(balancedata.perCurrencyBalances[i].cash);
// GET POSITIONS
var response = UrlFetchApp.fetch('https://api01.iq.questrade.com/v1/accounts/' + Account_num + '/positions',{
headers: {
Authorization: 'Bearer ' + token
}
});
json = response.getContentText();
var positionsdata = JSON.parse(json);
var num_of_positions = positionsdata.positions.length;
var i = 0;
while(i < num_of_positions) { //this loop is not that smart assumes the positions are where I specify, fix later
if(positionsdata.positions[i].symbol == 'VCN.TO'){
sheet.getRange("D5").setValue(positionsdata.positions[i].openQuantity);
}
if(positionsdata.positions[i].symbol == 'VUN.TO') {
sheet.getRange("D6").setValue(positionsdata.positions[i].openQuantity);
}
if(positionsdata.positions[i].symbol == 'VIU.TO') {
sheet.getRange("D7").setValue(positionsdata.positions[i].openQuantity);
}
if(positionsdata.positions[i].symbol == 'VEE.TO') {
sheet.getRange("D8").setValue(positionsdata.positions[i].openQuantity);
}
i=i+1;
}
j=j+1;
}
//send cash value to spreadsheet
// sheet.getRange("G1").setValue(data.perCurrencyBalances[i].cash);
}
function logout() {
var service = getQTService();
service.reset();
}
Any advice on what might be going wrong here would be greatly appreciated.
I don't think you can rely on using api01. I think you have to extract the api_server from the auth call that gives you a token (or at least I did this using the example on https://www.questrade.com/api/documentation/getting-started with refresh_token). My last 3 refresh_token auths for a bearer have yielded the api06 endpoint. I took your code and with the oauth authorizing and using api06 it works fine.
The magic sauce is var api_server = QTService.getToken().api_server;
I've deployed a protected web app, and I'd like to trigger it without logging in each time:
I'd like to access the web app URL without logging in:
Based on this document, it's not possible without logging in from browser:
https://github.com/tanaikech/taking-advantage-of-Web-Apps-with-google-apps-script/blob/master/README.md
If the script of Web Apps uses some scopes, client users have to
authorize the scopes by own browser.
I'm assuming scopes means the web app is protected.
I've tried this: https://github.com/gsuitedevs/apps-script-oauth2/blob/master/samples/GoogleServiceAccount.gs but it asks for "request access"
If I click on request access, then it shows me this:
At this point, I'm thinking it's not possible to setup a service account with scope to trigger a protected deployed web app without authenticating through a browser each time. Can anyone confirm this?
My assumption is that the web app scope is https://www.googleapis.com/auth/drive since it has access to all drive's files.
Update: (What I tried but didn't work)
I matched the scope from the script:
To the service account:
The blurred area above is the client id i got from:
I've generated the access token using this script:
function accessTokens(){
var private_key = "-----BEGIN PRIVATE KEY-----*****\n-----END PRIVATE KEY-----\n"; // private_key of JSON file retrieved by creating Service Account
var client_email = "****#****.iam.gserviceaccount.com"; // client_email of JSON file retrieved by creating Service Account
var scopes = ["https://www.googleapis.com/auth/documents","https://www.googleapis.com/auth/forms","https://www.googleapis.com/auth/script.external_request","https://www.googleapis.com/auth/spreadsheets","https://www.googleapis.com/auth/userinfo.email"]; // Scopes
var url = "https://www.googleapis.com/oauth2/v3/token";
var header = {
alg: "RS256",
typ: "JWT",
};
var now = Math.floor(Date.now() / 1000);
var claim = {
iss: client_email,
scope: scopes.join(" "),
aud: url,
exp: (now + 3600).toString(),
iat: now.toString(),
};
var signature = Utilities.base64Encode(JSON.stringify(header)) + "." + Utilities.base64Encode(JSON.stringify(claim));
var jwt = signature + "." + Utilities.base64Encode(Utilities.computeRsaSha256Signature(signature, private_key));
var params = {
method: "post",
payload: {
assertion: jwt,
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
},
};
var res = UrlFetchApp.fetch(url, params).getContentText();
Logger.log(res);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
sheet.getRange(1, 3).setValue(JSON.parse(res)['access_token']);
}
And still has the same error, it asks for request access.
After a couple days into this, I've figured it out (with help of course).
Get the scope from your deployed web app script: File > Project Properties > Scopes
Add the scope along with https://www.googleapis.com/auth/drive in page Manage API client access https://admin.google.com/AdminHome?chromeless=1#OGX:ManageOauthClients (use comma delimited to add multiple scopes: http...,http..., etc.)
For the client name, get the client id from the service account page in your admin console: https://console.developers.google.com
Deploy your script Publish > Deploy as Web App
After generating access token(instruction below), append the access token with your deployed web app url &access_token=YOURTOKENHERE
Use this script with a google sheet, it will generate the access_token in cell A1 of Sheet1 (Replace the 4 variables with the info relevant to you):
function accessTokens(){
var private_key = "-----BEGIN PRIVATE KEY-----n-----END PRIVATE KEY-----\n"; // private_key of JSON file retrieved by creating Service Account
var client_email = "*****#****.iam.gserviceaccount.com"; // client_email of JSON file retrieved by creating Service Account
var scopes = ["https://www.googleapis.com/auth/documents","https://www.googleapis.com/auth/forms","https://www.googleapis.com/auth/script.external_request","https://www.googleapis.com/auth/spreadsheets","https://www.googleapis.com/auth/userinfo.email","https://www.googleapis.com/auth/drive"]; // Scopes
var impersonate_email = "" //impersonate email
var url = "https://www.googleapis.com/oauth2/v4/token";
var header = {
alg: "RS256",
typ: "JWT",
};
var now = Math.floor(Date.now() / 1000);
var claim = {
iss: client_email,
sub: impersonate_email,
scope: scopes.join(" "),
aud: url,
exp: (now + 3600).toString(),
iat: now.toString(),
};
var signature = Utilities.base64Encode(JSON.stringify(header)) + "." + Utilities.base64Encode(JSON.stringify(claim));
var jwt = signature + "." + Utilities.base64Encode(Utilities.computeRsaSha256Signature(signature, private_key));
var params = {
method: "post",
payload: {
assertion: jwt,
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
},
};
var res = UrlFetchApp.fetch(url, params).getContentText();
Logger.log(res);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
sheet.getRange(1, 1).setValue(JSON.parse(res)['access_token']);
}
I'm tryng to edit the users signature via GAS.
I have found lots of examples and tutorials, and one of the Waqar Ahmad's answers looks very good - Email Settings APIs Authentication.
However, it doesn't work for me.
I don't really understand OAuth autorization, but couldn't find a tutorial for it.
With what should I replace anonymous?
oAuthConfig.setConsumerKey("anonymous");
oAuthConfig.setConsumerSecret("anonymous");
I go to https://console.developers.google.com/ create a project and use clientID for key and client secret for secret, is it right?
Adding more information:
This is the Waqar's code I'm using:
/*-----------------------------------------------------------------------------
This function will update the HTML signature of a user.
Input will be jason data
To disable signature, pass an empty string as signature value
sample parameter
ob = {user='hps', signature='<b>Regards</b><br>Waqar'}
To disable signature
ob = {user='hps', signature=''}
-----------------------------------------------------------------*/
function updateSignature(ob) {
//ob = {};
//ob.user = "hps";
//ob.signature = "<b>Regards</b><br>Waqar";
ob = {};
ob.user = "test#xxxx.it";
ob.signature = "<b>Regards</b><br>Waqar";
var base = 'https://apps-apis.google.com/a/feeds/emailsettings/2.0/';
var xmlRaw = '<?xml version="1.0" encoding="utf-8"?>'+
'<atom:entry xmlns:atom="http://www.w3.org/2005/Atom" xmlns:apps="http://schemas.google.com/apps/2006">'+
'<apps:property name="signature" value="'+htmlEncode(ob.signature)+'" />'+
'</atom:entry>';
var fetchArgs = googleOAuth_('emailSetting',base);
fetchArgs.method = 'PUT';
fetchArgs.payload = xmlRaw;
fetchArgs.contentType = 'application/atom+xml';
var domain = UserManager.getDomain();
var url = base+domain+'/'+ob.user+'/signature';
var urlFetch = UrlFetchApp.fetch(url, fetchArgs);
var status = urlFetch.getResponseCode();
return status;
}
//--------------------------------------------------------------------------
//This function will retreive Signature settings as json.
/*Sample returned object
{user=hps, signature=<b>Regards</b><br>Waqar}
*/
//-----------------------------------------------------------------
function retrieveSignature(user) {
var user = 'hps';
var base = 'https://apps-apis.google.com/a/feeds/emailsettings/2.0/';
var fetchArgs = googleOAuth_('emailSetting',base);
fetchArgs.method = 'GET';
var domain = UserManager.getDomain();
var url = base+domain+'/'+user+'/signature?alt=json';
var urlFetch = UrlFetchApp.fetch(url, fetchArgs);
var jsonString = urlFetch.getContentText();
var jsonArray = Utilities.jsonParse(jsonString).entry.apps$property;
var ob = {};
ob.user = user;
for(var i in jsonArray){
ob[jsonArray[i].name] = jsonArray[i].value;
}
return ob;
}
//Google oAuthConfig..
function googleOAuth_(name,scope) {
var oAuthConfig = UrlFetchApp.addOAuthService(name);
oAuthConfig.getAccessTokenUrl()
oAuthConfig.setRequestTokenUrl("https://www.google.com/accounts/OAuthGetRequestToken?scope="+scope);
oAuthConfig.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken");
oAuthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");
oAuthConfig.setConsumerKey("xxxxxxxxxxxx.apps.googleusercontent.com");
oAuthConfig.setConsumerSecret("xxxxxxxx-xxxxxx-xxxxx");
return {oAuthServiceName:name, oAuthUseToken:"always"};
}
//This function will escape '<' and '>' characters from a HTML string
function htmlEncode(str){
str = str.replace(/</g,'<');
return str.replace(/>/g,'>')
}
to get the oAuthConfig.setConsumerKey and oAuthConfig.setConsumerSecret I have created a new project in the google developer console, modified to on the Admin SDK API status, created a "Client ID for native application" and used the CLIENT ID in setConsuperKey, and CLIENT SECRET in setConsumerSecret.
Executing the script updateSignature the test#xxxx.it's signature should be changed,
I can see the box "autorization required" clik ok, and appear the request access box,
i click on "grant access" but nothing happes, and no error are shown.
Executing the same function in debug mode, i have the same boxes and a red box with "Errore OAuth" at the end.
I'm doing something wrong... please help me to find the mistake!!
Thanks again.
Marco
Finally i found the clue!!
Thank's to mike's quastions and answers
who let me discover This example
I Finally understand!!
in consumerkey you have to set your domain (p.e. "mydomain.it",
in consumerSecret you have to set the "Secret data of the customer to OAuth:" from admin google console->secority->advanced settings->Manage data OAuth key and secret for this domain.
I was confusing by the secret key in the google developer console, may be this answer can help