Google App Script request validation on server - google-apps-script

I am developing an add-on for Google Docs and I want to make POST request to my web server from add-on. I have already done that, but how should I validate on server-side that the request is coming from my add-on only? Is there csrf like mechanism in Google App Script? If not, any workaround to it?

There is a direct method in Apps Script to get UUID : Utilities.getUuid()
Reference : https://developers.google.com/apps-script/reference/utilities/utilities#getuuid
For memory previous answer below.
There is not mechanism for that but the best way is to add in the post request a specific key. Like API key in Google, example : 94e631ba-9916-4490-a084-cde08dcc0757
For generating a key example here : https://codepen.io/corenominal/pen/rxOmMJ
Adapted code below :
function generateUUID()
{
var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c)
{
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
});
return uuid;
}
Then on your server your check this value. If API Key is valid you perform the request if not you return a 403.
If you want you can implement an OAuth flow to connect to your server like Google do for its API but from my point of view it is faster to use an API key. If you combine 2 key like the one above probability to find it is near 0.
Stéphane

Related

Why is doGet() failing without posting logs?

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

How to authenticate with Blockfrost.io API?

So I'm trying to import Cardano Blockchain data like address balance, amount staked, rewards etc into a Google Sheet. I found this project named Blockfrost.io which is an API for accessing Cardano blockchain info and import it into apps etc.
I think I can use this with Google Sheets. Problem is I don't know how to authenticate. I've searched all around on the documentation and it's not clear to me. It seems it's possible if your're building an app or using the terminal.
But I just want to authenticate in the easiest way possible like in the browser address bar that way it would be simple to get the JSON with the info I need and import the info to Google Sheets.
This is where it mentions the Authentication:
https://docs.blockfrost.io/#section/Authentication
I already have an API key to access. But how do I authenticate?
So if I want to check the blockchain metrics (mainnet1234567890 is a dummy key, I won't use mine here):
https://cardano-mainnet.blockfrost.io/api/v0/metrics/project_id:mainnet1234567890
The JSON will still output this:
status_code 403
error "Forbidden"
message "Missing project token. Please include project_id in your request."
Is there a correct way to authenticate on the browser address bar?
It's not clear which BlockFrost API you are using Go JavaScript etc...
the API key goes in as a header on the request object. I was manually trying to connect to the service and found for a request is what I had to do in C#...
var aWR = System.Net.WebRequest.Create(url);
aWR.Method = "GET";
aWR.Headers.Add("project_id", "mainnetTheRestOfMyKeyIsHidden");
var webResponse = aWR.GetResponse();
var webStream = webResponse.GetResponseStream();
var reader = new StreamReader(webStream);
var data = reader.ReadToEnd();
Later I realized I wanted to use their API cause they implement the rate limiter, something I would rather use than build... I use the following with the BlockFrost API in c#
const string apiKey = "mainnetPutYourKeyHere";
const string network = "mainnet";
// your key is set during the construction of the provider.
ServiceProvider provider = new ServiceCollection().AddBlockfrost(network, apiKey).BuildServiceProvider();
// from there individual services are created
var AddressService = provider.GetRequiredService<IAddressesService>();
// The call to get the data looked like
AddressTransactionsContentResponseCollection TXR = await AddressService.GetTransactionsAsync(sAddress, sHeightFrom, sHeightTo, 100, iAddressPage, ESortOrder.Desc, new System.Threading.CancellationToken());
// etc. your gonna need to set the bounds above in terms of block height
Try using postman and include the "project_id" header with api key as the value like this - it will clear up the concept for you I think:enter image description here

Connecting Google Apps Script to a Parse Database (ScriptDB Replacement)

With the recent news that ScriptDB is being deprecated, I'm searching for a suitable replacement. My particular use case is that I'm running Google Apps Script under Google Forms to process and store data relevant to the function of the form.
I've been through Google's migration guide (link), and I'm trying to connect a Google Apps Script running under a Google Form to a Parse Database (link). I've tried both methods listed on the migration guide (URL Fetch Service and ParseDB Library), and I can't get either to work correctly. I was able to write to the Parse Database using ParseDB, but the query function isn't working as expected. I also tried using the parseCom library from the Excel Liberation site (sorry, I'm out of links for this post, apparently), but that didn't work very well either.
I'm most interested in using Google's URL Fetch Service to connect to a Parse database, as that seems to be my most flexible option (i.e. to let me share data between forms - something I really couldn't do with scriptDB), but I feel like I'm in over my head just a bit. I'm open to other options as well. Thanks in advance!
I have the same 'query' problem using the MongoLab database as overstack-asked here. I thought Parse might work as an alternative, but then I saw this post having the same 'query' problem.
If you run your query from the browser directly to Parse it will probably work fine, just as it does for me using the MongoLab database.
Consequently, I strongly suspect the problem is in the URLFetchApp.fetch() function itself, not the Parse (nor MongoLab) database functions.
Here's what I have found to be the best way to query results using UrlFetchApp and Parse.
function query(key,value) {
var properties = getKeys();
var appId = properties.appId;
var restApi = properties.restApi;
var class = 'TestObject';
var url = 'https://api.parse.com/1/classes/' + class;
var query = 'where={"' + key + '":"' + value + '"}'
var encoded = encodeURIComponent(query);
var queryUrl = url + '?' + encoded;
var options = {
"method" : "get",
"headers" : {
"X-Parse-Application-Id": appId,
"X-Parse-REST-API-Key": restApi,
}
}
var data = UrlFetchApp.fetch(queryUrl, options);
return data;
}
I am also looking for this.
As a temp store ScriptDb has been very helpful. Sheets simply does not have the elegance of beautiful coding as in ScriptDb

Different IP while making two requets throught UrlFetchApp in same script

Can we relly that while Google App Script is executed by a Time Trigger, and makes two subsequest request using UrlFetchApp, both are made by same server with the same IP?
I need to ensure it, because in one request I query for an Access token for a remote service and with another I'm using this Access token. The remote service that I'm quering checks if the Access token was requested by the client with the same IP as requests that use this Access Token.
EDIT
I examined the behavior by time-triggering some dumb scripts with just few consecutive UrlFetchApp requests in them and checked server logs. I had two clear observations:
IP may vary in consecutive calls within one trigger
There is clear rotation of the IPs, sometimes there is a group of 7 consecutive calls with the same IP, sometimes 6. But in general there are always groups.
Because I wanted to only use Google infrastructure for my script and occasional failure was not a problem, I came up with a ugly ugly but working solution:
function batchRequest(userLogin, userPassword, webapiKey, resource, attributes, values ){
var token = requestToken(userLogin, userPassword, webapiKey ); // requestToken method uses UrlFetchApp.fetch
var result = request(resource, token, attributes, values); // requestToken method uses UrlFetchApp.fetch with options.muteHttpExceptions set to true so that we can read the response code
var i = 0;
while (result.getResponseCode() == 500 && i < 10){
token = requestToken(userLogin, userPassword, webapiKey ); // requestToken method uses UrlFetchApp.fetch
result = request(resource, token, attributes, values);
i++;
}
return result;
}
So I simply try hard max 10 times and hope to hook up to have the two requests — one for token and another for some bussiness logic — done in a same ‘IP group’.
I put more detailed description here: https://medium.com/p/dd0746642d7
Within the same trigger call yes. From another trigger no. Based on experience nce but i havent seen this docummented.

Cannot access the Box Application using API Key

I have created an application in Box and got an API Key, and then edited the redirect URL. But when I accessed through URL https://www.box.net/api/1.0/rest?action=get_ticket&api_key=APIKEY, I'm not getting a positive response, instead of I'm getting a response like:
<response>
<status>application_restricted</status>
</response>
Please provide me a solution to get access to the application.
Thanks in advance.
The V1 API has been deprecated and will no longer provide authentication or file access. You need to migrate your application to the V2 API, which is documented here.
For accessing access_token using java sdk , You need to have following key values
1) clienId > Use Application console
2) client_secret > Use application console
3) code > Code value you will get using below link
https://account.box.com/api/oauth2/authorize?response_type=code&client_id={your_client_id}&state=security_token%3DKnhMJatFipTAnM0nHlZA
Replace your client id with original value. client id you will get from your apps.
And Follow steps and authorize you application using your credential.
after that it will redirect to https://localhost/?state=security_token%3DKnhMJatFipTAnM0nHlZA&code=sdsdsd3sdsdC0oGqOS2WgaFipZBdj
Copy code value
String clienId = "your client id ";
String client_secret = "your secret id";
String code = "sdsdsd3sdsdC0oGqOS2WgaFipZBdj"; // use above extracted code value
BoxAPIConnection con = new BoxAPIConnection(clienId,client_secret,code);
String accessToken = con.getAccessToken();
System.out.println("Accss_Token : " +accessToken);