API works on insecure but not secure - json

So im running through a Freecodecamp challenge and this seems to be a common thing.
requesting Json from unsecure host openweathermap.org
On my domain http:// I can now get it to work but at https:// it will not (also codepen).
I found a few workarounds but none worked.
Any help would be great.
http://codepen.io/Middi/pen/peLMBQ
function weather() {
// Get Location
$.getJSON('http://ip-api.com/json', function (response) {
var city = response.city;
var country = response.country;
displayWeather(city, country);
});
// Make API URL
function displayWeather(city, country) {
var weatherAPI = "http://api.openweathermap.org/data/2.5/weather?q=";
var API_Key = "&APPID=1f3e30098d59daa0ee84d36dca533728";
var full_API_Link = weatherAPI + city + units + API_Key;
$.getJSON(full_API_Link, function (response) {
// Interpret data
var temp_c = Math.round(response.main.temp);
var description = response.weather[0].description;
var icon = response.weather[0].icon;
//Switch Icons and send to DOM
replace(icon, city, country, temp_c, endUnit, description);
});
}
}

Due to browser security policy, you cannot load insecure resource (HTTP) in secure page (HTTPS) via Ajax request.
There is one workaround: make a forward layer in your backend. The steps would be:
When you want to load data from http://ip-api.com/json or http://api.openweathermap.org, send Ajax request to your own backend API, via HTTPS.
In backend, accept the previous HTTPS request, parse the parameter, and send real HTTP request to ip-api.com or api.openweathermap.org. Return the data to browser after it is retrieved.
In browser, get the data and do corresponding operation.
In codepen, as the page is loaded via HTTP, your Ajax code works well. The request/response can be observed in debug window.

Related

Error "302 Moved Temporarily" in "Test as add-on" mode (Telegram)

The spreadsheet contains project 1, deployed as a webapp with permissions: Execute as: Me, Who has access: Anyone.
Webapp
function doPost(e) {
myLog('Received from Addon: ' + JSON.stringify(e));
// console.log('parameters from caller ' + JSON.stringify(e));
return ContentService.createTextOutput(JSON.stringify(e));
}
A webhook aTelegram-bot and this webapp is set.
I am using this spreadsheet for testing (as add-on) of another project 2.
Add-on
function sendPost() {
var sheetURL = SpreadsheetApp.getActiveSpreadsheet().getUrl();
// var webAppUrl = "https://script.google.com/macros/s/#####/exec"; // 7: Part_1 - WebApp: My
var webAppUrl = "https://script.google.com/macros/s/###/exec"; // 7: Part_1 - WebApp: Tester
// var auth = ScriptApp.getOAuthToken();
// var header = { 'Authorization': 'Bearer ' + auth };
var payload = { scriptName: 'updateData', sheetURL: 'sheetURL' };
var options = {
method: 'post',
// headers: header,
muteHttpExceptions: true,
payload: payload
};
var resp = UrlFetchApp.fetch(webAppUrl, options);
var respCode = resp.getResponseCode();
console.log('resp: ' + respCode);
myLog(respCode);
var respTxt = resp.getContentText();
myLog('Response from webApp: ' + respTxt);
console.log('resp: ' + respTxt);
}
Here is a short video of the process (EN-subtitles).
I run sendPost() and everything works fine. Project 2 sends data to the webapp, which returns it. Since this is a Container-bound script and not a standalone one, I cannot watch the logs in the GCC logger. Therefore, I look at them in the custom logger and the entries are added normally.
Also https://api.telegram.org/bot{API_token}/getWebhookInfo shows that there are no errors:
{"ok":true,"result": {"url":"https://script.google.com/macros/s/###/exec", "has_custom_certificate":false, "pending_update_count":0, "max_connections":40,"ip_address":"142.250.***.***"}}
Now I am sending a message from the chat with the bot. The doPost(e) function in the webapp accepts it and writes it to the spreadsheet.
However, everything is not limited to one message. Requests from the bot come and come, and the logger creates more and more new rows in the spreadsheet. This happens until I redeploy the webapp with the doPost () function commented out. I tried to figure out if this is a limited loop or not. My patience was only enough for 20 such iterations, because as a result, the messages start repeating at intervals of about 1 minute. Then I have to reinstall the webhook.
In any case, it interferes with testing the addon.
GetWebhookInfo is now showing that there is a "Wrong response from the webhook: 302 Moved Temporarily" error:
{"ok":true,"result": {"url":"https://script.google.com/macros/s/###/exec", "has_custom_certificate":false, "pending_update_count":1, "last_error_date":1635501472, "last_error_message":"Wrong response from the webhook: 302 Moved Temporarily", "max_connections":40,"ip_address":"142.250.1***.***"}}
Googling revealed several possible reasons. From url to the script has changed to MITM in your network.
I do not really believe in MITM and I suppose that this is due to the fact that the spreadsheet is open in testing mode as add-on and the URL of the webapp has changed in this mode. If so, then I'm not sure if this is the correct behavior of the testing system. In theory, such a situation should have been provided for and the webap url should remain unchanged. But maybe I'm wrong and the reason is different, so
QUESTION:
Has anyone come across such a situation and will suggest a workaround on how to test a script as an addon in such conditions?
http-status-code-302 refers to redirection. If ContentService is used, Google temporarily redirects the resource to a another domain to serve the content. This redirection is not performed when using HtmlService. So, if the issue is related to redirection, use HtmlService instead.

When using aqueduct is oauth appropriate to authorize access to html pages

I am serving up HTML pages with Aqueduct and I would like the authorization of accessed pages to work without setting manually the authorization header with javascript for each link. How is this done?
The only way I see it is possible is to use a cookie. I tried putting the OAuth Bearer token in a cookie so it gets sent with each request but I get stuck trying to sneak it back from the cookie into the request header (where it is expected by the standard authorizer at the server end) as the request headers are not mutable.
Do I have to write a new authorizer to use the token from the cookie? I have read that one shouldn't use cookies with OAuth at all. So how to do it? Surely I am missing something as this seems to be a common need.
Another idea (still using cookies) is to extract the token from the cookie at the server and forward the request back to the (same) server with the correct authorization header.
What is the way authorization of aqueduct web pages is best handled?
The authorization needs to be sent in the header and the browser isn't going to do this for me except by cookie which is not recommended. The answer I came up with is that the html page should be retrieved (minus content) and the content for the web page requested using authorization using js with the authorization header included in the request.
My path forward was to replace the whole web page because i didn't want to change much of what was working. So in a wrapper html page I do this:
var uri = NEWURI;
var xhr = new XMLHttpRequest();
xhr.open('GET', uri);
xhr.onreadystatechange = handler;
xhr.responseType = 'text';
var authorization = localStorage.getItem("authorization"); // setup by by login page
if (authorization != null)
{
xhr.setRequestHeader('Authorization', authorization);
}
xhr.send();
and then in the handler I write over the document:
if (this.readyState === this.DONE) {
if (this.status === 200) {
var newHTML = document.open("text/html", "replace");
newHTML.write(this.response);
newHTML.close();
} else if (this.status === 401)
{
}

Calling a Google Apps Script web app with access token

I need to execute a GAS service on behalf of a user that is logged to my system. So I have her/his access token. I would like somehow to transfer the token to the web app and without having to authorize again the user to use it for some activities. Can this be accomplished? Thank you.
EDIT: I think I didn't explain right what I try to accomplish. Here is the work flow I try to achieve:
We authorize a user visiting our website using OAuth2 and Google;
We get hold of her/his access token that Google returns;
There is a Google Apps Script web app that is executed as the user running the web app;
We want to call this app (3) by providing the access token (2) so Google not to ask again for authorization;
Actually, we want to call this app (3) not by redirecting the user to it but by calling it as a web service.
Thanks
Martin's answer worked for me in the end, but when I was making a prototype there was a major hurdle.
I needed to add the following scope manually, as the "automatic scope detection system" of google apps script did not ask for it: "https://www.googleapis.com/auth/drive.readonly". This resulted in UrlFetchApp.fetch always giving 401 with additional information I did not understand. Logging this additional information would show html, including the following string
Sorry, unable to open the file at this time.</p><p> Please check the address and try again.
I still don't really understand why "https://www.googleapis.com/auth/drive.readonly" would be necessary. It may have to do with the fact that we can use the /dev url, but who may use the /dev url is managed is checked using the drive permissions of the script file.
That said, the following setup then works for me (it also works with doGet etc, but I chose doPost). I chose to list the minimally needed scopes explicitly in the manifest file, but you can also make sure the calling script will ask for permissions to access drive in different ways. We have two google apps script projects, Caller and WebApp.
In the manifest file of Caller, i.e. appsscript.json
{
...
"oauthScopes":
[
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/script.external_request"]
}
In Code.gs of Caller
function controlCallSimpleService(){
var webAppUrl ='https://script.google.com/a/DOMAIN/macros/s/id123123123/exec';
// var webAppUrl =
// 'https://script.google.com/a/DOMAIN/macros/s/id1212121212/dev'
var token = ScriptApp.getOAuthToken();
var options = {
'method' : 'post'
, 'headers': {'Authorization': 'Bearer '+ token}
, muteHttpExceptions: true
};
var response = UrlFetchApp.fetch(webAppUrl, options);
Logger.log(response.getContentText());
}
In Code.gs of WebApp (the web app being called)
function doPost(event){
return ContentService.createTextOutput("Hello World");
}
The hard answer is NO you can't use the built-in services of Apps Script with a service token. But if you already have the token for a user generated by a service account, access to the users data is pretty similar to any other language. All calls would be to the REST interface of the service your token is scoped for.
Take this small script for example. It will build a list of all the user's folders and return them as JSON:
function doGet(e){
var token = e.parameter.token;
var folderArray = [];
var pageToken = "";
var query = encodeURIComponent("mimeType = 'application/vnd.google-apps.folder'");
var params = {method:"GET",
contentType:'application/json',
headers:{Authorization:"Bearer "+token},
muteHttpExceptions:true
};
var url = "https://www.googleapis.com/drive/v2/files?q="+query;
do{
var results = UrlFetchApp.fetch(url,params);
if(results.getResponseCode() != 200){
Logger.log(results);
break;
}
var folders = JSON.parse(results.getContentText());
url = "https://www.googleapis.com/drive/v2/files?q="+query;
for(var i in folders.items){
folderArray.push({"name":folders.items[i].title, "id":folders.items[i].id})
}
pageToken = folders.nextPageToken;
url += "&pageToken="+encodeURIComponent(pageToken);
}while(pageToken != undefined)
var folderObj = {};
folderObj["folders"] = folderArray;
return ContentService.createTextOutput(JSON.stringify(folderObj)).setMimeType(ContentService.MimeType.JSON);
}
You do miss out on a lot of the convenience that makes Apps Script so powerful, mainly the built in services, but all functionality is available through the Google REST APIs.
I found a way! Just include the following header in the request:
Authorization: Bearer <user's_access_token>

Save ip camera video to google drive using apps script UrlFetchApp

My idea was to setup a script with GAS to record videos of my ip camera to google drive. Currently it can access the camera videostream, take data and save it do drive. It's not working because instead of getting a limited amount of data due to the http request header range parameter it takes the maximum size the http request could receive. Furthermore the asf video seems to get corrupted, and can't be played on VLC.
Any idea for making the script download a defined video size and in the correct format?
function myFunction() {
var URL = 'http://201.17.122.01:82/videostream.asf?user=xxxx&pwd=xxxxxx&resolution=32&rate=1'; // file to backup
var chunkSize = 1048576; // read the file in pieces of 1MB
var chunkStart = 0, chunkEnd = chunkStart + chunkSize;
var chunkHTTP = UrlFetchApp.fetch(URL, {
method: "get",
contentType: "video/x-ms-asf",
headers: {
"Range": "bytes=" + chunkStart + "-" + chunkEnd
}
})
var chunk = chunkHTTP.getContentText();
// write to Drive
try {
var folder = DocsList.getFolder('bakfolder');
} catch(err) {
var folder = DocsList.createFolder('bakfolder');
}
fileOnDrive = folder.createFile('ipcamera.asf', chunk);
Logger.log(" %s bytes written to drive", chunk.length);
}
I do not have an ip camera, so can't test and help you with the chunking of received data. But I suspect that you are using wrong methods for extracting received video data and saving it to Drive:
a) .getContentText() method gets the content of an HTTP response encoded as a string - probably wrong for working with video stream data. Try using .getBlob() or .getContent() instead;
b) folder.createFile(name, content) method creates a text file in the current folder. Use .createFile(blob) method instead.
You will most likely have t experiment with the stream bytes to get it as a blob Apps Script can work with. If I find an ip camera or another video stream I can access, I will test it out and update this.

Use Google Script's Web App as Webhook to receive Push Notification directly

My Goal: Changes in Google Drive => Push Notification to https://script.google.com/a/macros/my-domain/... => App is pushed to take action.
I don't want to setup an middle Webhook agent for receiving notification. Instead, let the Web App (by Google Script) to receive it and be pushed directly.
Since the relevant function is quite undocumented (just here: https://developers.google.com/drive/web/push) , below is the code I tried but failure.
1. Is above idea feasible??
2. My code doPost(R) seems cannot receive notification (R parameter) properly. Anyway, no response after I change the Google Drive. Any problem? (I have tried to log the input parameter R so as to see its real structure and decide if the parameter Obj for OAuth is the same as normal Drive App, but error occur before log)
function SetWatchByOnce(){
var Channel = {
'address': 'https://script.google.com/a/macros/my-domain/.../exec',
'type': 'web_hook',
'id': 'my-UUID'
};
var Result = Drive.Changes.watch(Channel);
...
}
function doPost(R) {
var SysEmail = "My Email";
MailApp.sendEmail(SysEmail, 'Testing ', 'Successfully to received Push Notification');
var Response = JSON.parse(R.parameters);
if (Response.kind == "drive#add") {
var FileId = Response.fileId;
MyFile = DriveApp.getFolderById(FileId);
...
}
}
function doGet(e) {
var HTMLToOutput;
var SysEmail = "My Email";
if (e.parameters.kind) {
//I think this part is not needed, since Push Notification by Drive is via Post, not Get. I should use onPost() to receive it. Right?
} else if (e.parameters.code) {
getAndStoreAccessToken(e.parameters.code);
HTMLToOutput = '<html><h1>App is successfully installed.</h1></html>';
} else { //we are starting from scratch or resetting
HTMLToOutput = "<html><h1>Install this App now...!</h1><a href='" + getURLForAuthorization() + "'>click here to start</a></html>";
}
return HtmlService.createHtmlOutput(HTMLToOutput);
}
....
Cloud Functions HTTP trigger(s) might also be an option ...
(which not yet existed at time of this question). this just requires setting the trigger URL as the notification URL, in the Google Drive settings - and adding some NodeJS code for the trigger; whatever it shall do. one can eg. send emails and/or FCM push notifications alike that. that trigger could also be triggered from App Script, with UrlFetchApp and there is the App Script API. one can have several triggers, which are performing different tasks (App Script is only one possibilty).
Cicada,
We have done similar functions to receive webhooks/API calls many times. Notes:
to get R, you need: var Response = R.parameters and then you can do Response.kind, Response.id, etc.
Logger will not work with doGet() and doPost(). I set it up a write to spreadsheet -- before any serious code. That way I know if it is getting triggered.