Set multiple cookies in google app script with UrlFetchApp - cookies

I am using the following script to access a web page that requires user to be logged-in. The script works up to the point where I retrieve the login cookies I need help with using the cookies in the last part of the following code.
function login(strUsername, strPassword) {
//request Page
var loginPage = UrlFetchApp.fetch("https://login.url.org/");
//strip out "authState" value from response
var sessionDetails = loginPage.getContentText()
var searchString = sessionDetails.indexOf('"authState":"');
var newStringRight = sessionDetails.substring(searchString+13, searchString+6000);
var authState = newStringRight.substring(0, newStringRight.indexOf('"'));
//Logger.log(authState);
//set payload
var payload =
{"authState":authState,
"username":strUsername,
"password":strPassword
};
var options =
{
"method" : "post",
"payload" : JSON.stringify(payload),
"followRedirects" : false,
muteHttpExceptions: true,
"contentType": "application/json"
};
//Logger.log(options);
var loginResponse = UrlFetchApp.fetch("https://login.url.com/api/authenticate/credentials", options);
var loginHtml = loginResponse.getContentText();
Logger.log(loginHtml);
// Log has the following response
//{"output":{"method":"SUCCESS","cookies":{"ObSSOCookie":"xxx","ChurchSSO":"yyy"}}}
if (loginHtml.indexOf("SUCCESS")>0){
Browser.msgBox('Pass', 'Successfully loged in.', Browser.Buttons.OK);
//strip out cookies
searchStringStart = loginHtml.indexOf('{"ObSSOCookie":"');
searchStringEnd = loginHtml.indexOf('"}}');
var Cookie = loginHtml.substring(searchStringStart, searchStringEnd+2);
Logger.log(Cookie);
/*
Logger.log(cookie) returns
{"ObSSOCookie":"xxxx","ChurchSSO":"yyyy"}
*/
////////////////////////////Works up to here/////////////////////////////////////////
var options =
{
"headers" : {"cookies" : Cookie},
muteHttpExceptions: true,
"contentType": "application/json"
};
var adminpage= UrlFetchApp.fetch("https://url.com/admin",options );
Logger.log(adminpage);
/*
<h1>Access Denied</h1>
<p>You are not authorized to access this page.</p>
<p>Retry
*/
}else{
Browser.msgBox('Fail', 'Sign in failed. Please try again.', Browser.Buttons.OK);
}
}
I think it may have to do with the fact that I am trying to use multiple cookies {"ObSSOCookie":"xxxx","ChurchSSO":"yyyy"}not sure how to approperatly pass them into the new page call. Any help would be great

Cookies should be sent inside a single Cookie header separated by a ;(semicolon and a space):
Cookie: name1=value1; name2=value2;
Snippet:
var options =
{
"headers" : {"Cookie" : "ObSSOCookie=xxxx; ChurchSSO=yyyy"}},
};
Reference:
HTTP Cookies

Related

Sending CORS headers from AWS Lambda to Gateway prevents my Lambda function from executing correctly

I'm slowly inching my way towards a rudimentary ability to use or understand AWS. I have a Gateway API set up to Post a string to a (Node 10.x)Lambda function that then gets sent to a Dynamo table. I've been having issues with CORS when trying to make API calls from webpage javascript, and found out it had something to do with the CORS in the handler in the Lambda function. Here is what I have right now:
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'});
exports.handler = async function(event, context) {
var responseCode = 200;
var response = {
statusCode: responseCode,
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify(event)
};
context.succeed(response);
console.log("Made it past context succeed");
var characterData = JSON.parse(event.body);
return await db.put(characterData).promise();
};
I run tests from the AWS API page just sending it strings to Post like
{
"TableName" : "characterTable",
"Item" : {
"userID" : "123",
"characterName" : "Alan",
"race" : "human"
}
}
When I comment out
context.succeed(response);
my function adds the data to the Dynamo table, but does not show the CORS headers in the Gateway log, meaning they weren't received, and if I try it on a webpage, the webpage will error telling me I'm missing the Access-Control-Allow-Origin header. If I leave that snippet in, the Gateway log shows the proper CORS header, and the webpage console moves past the Access-Control-Allow-Origin error onto a different error (missing token ‘content-type’ in CORS header but that's a problem for me in the future) but the data does not get passed onto the Dynamo table, even though the console.log statement right above it triggers properly.
I'm not sure how or why this is happening, so I would appreciate any insight into what might be wrong!
EDIT: Here is my webpage JS
//Default AWS sdk object
var lambda = new AWS.Lambda();
//api sdk stuff
var apigClient = apigClientFactory.newClient({
apiKey: 'iHadMyAPIKeyHere ' //placeholder for my actual API Key
});
function makeJSON(){
var userID = "";
var name = document.forms["characterForm"]["characterName"].value;
var race = document.forms["characterForm"]["race"].value;
var playerClass = document.forms["characterForm"]["class"].value;
var strength = document.forms["characterForm"]["strength"].value;
var dexterity = document.forms["characterForm"]["dexterity"].value;
var constitution = document.forms["characterForm"]["constitution"].value;
var intelligence = document.forms["characterForm"]["intelligence"].value;
var wisdom = document.forms["characterForm"]["wisdom"].value;
var charisma = document.forms["characterForm"]["charisma"].value;
characterSheetObj = {"userID": userID, "name": name, "race": race, "class": playerClass, "strength": strength, "dexterity": dexterity, "constitution": constitution, "intelligence": intelligence, "wisdom": wisdom, "charisma": charisma}
characterSheetJSON = JSON.stringify(characterSheetObj);
alert(characterSheetJSON);
var params = {
}
var body = {
"TableName" : "characterTable",
"Item" : {
"userID" : userID,
"name" : name,
"race" : race
}
}
var additionalParams = {
}
apigClient.myresourcePost(null, body);
}
Welcome to StackOverflow so you're problem is you need to use callback like so...
callback is called as the eventual result of the function, you want to return callback(....) to ensure your tests work properly later down line.
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'});
exports.handler = async function(event, context, callback) {
var responseCode = 200;
var response = {
statusCode: responseCode,
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify(event)
};
console.log("Made it past context succeed");
var characterData = JSON.parse(event.body);
await db.put(characterData).promise();
return callback(null, response)
};
NOTE Your dynamo DB call is in the wrong format and will generate an exception.
Context succeed is the old way, please see this answer for more info on returning proper http responses.

How to handle expired access token in asp.net core using refresh token with OpenId Connect

I have configured an ASOS OpenIdConnect Server using and an asp.net core mvc app that uses the "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0 and "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0". I have tested the "Authorization Code" workflow and everything works.
The client web app processes the authentication as expected and creates a cookie storing the id_token, access_token, and refresh_token.
How do I force Microsoft.AspNetCore.Authentication.OpenIdConnect to request a new access_token when it expires?
The asp.net core mvc app ignores the expired access_token.
I would like to have openidconnect see the expired access_token then make a call using the refresh token to get a new access_token. It should also update the cookie values. If the refresh token request fails I would expect openidconnect to "sign out" the cookie (remove it or something).
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthenticationScheme = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = "myClient",
ClientSecret = "secret_secret_secret",
PostLogoutRedirectUri = "http://localhost:27933/",
RequireHttpsMetadata = false,
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true,
ResponseType = OpenIdConnectResponseType.Code,
AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet,
Authority = http://localhost:27933,
MetadataAddress = "http://localhost:27933/connect/config",
Scope = { "email", "roles", "offline_access" },
});
It seems there is no programming in the openidconnect authentication for asp.net core to manage the access_token on the server after received.
I found that I can intercept the cookie validation event and check if the access token has expired. If so, make a manual HTTP call to the token endpoint with the grant_type=refresh_token.
By calling context.ShouldRenew = true; this will cause the cookie to be updated and sent back to the client in the response.
I have provided the basis of what I have done and will work to update this answer once all work as been resolved.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthenticationScheme = "Cookies",
ExpireTimeSpan = new TimeSpan(0, 0, 20),
SlidingExpiration = false,
CookieName = "WebAuth",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = context =>
{
if (context.Properties.Items.ContainsKey(".Token.expires_at"))
{
var expire = DateTime.Parse(context.Properties.Items[".Token.expires_at"]);
if (expire > DateTime.Now) //TODO:change to check expires in next 5 mintues.
{
logger.Warn($"Access token has expired, user: {context.HttpContext.User.Identity.Name}");
//TODO: send refresh token to ASOS. Update tokens in context.Properties.Items
//context.Properties.Items["Token.access_token"] = newToken;
context.ShouldRenew = true;
}
}
return Task.FromResult(0);
}
}
});
You must enable the generation of refresh_token by setting in startup.cs:
Setting values to AuthorizationEndpointPath = "/connect/authorize"; // needed for refreshtoken
Setting values to TokenEndpointPath = "/connect/token"; // standard token endpoint name
In your token provider, before validating the token request at the end of the HandleTokenrequest method, make sure you have set the offline scope:
// Call SetScopes with the list of scopes you want to grant
// (specify offline_access to issue a refresh token).
ticket.SetScopes(
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess);
If that is setup properly, you should receive a refresh_token back when you login with a password grant_type.
Then from your client you must issue the following request (I'm using Aurelia):
refreshToken() {
let baseUrl = yourbaseUrl;
let data = "client_id=" + this.appState.clientId
+ "&grant_type=refresh_token"
+ "&refresh_token=myRefreshToken";
return this.http.fetch(baseUrl + 'connect/token', {
method: 'post',
body : data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}
});
}
and that's it, make sure that your auth provider in HandleRequestToken is not trying to manipulate the request that is of type refresh_token:
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
if (context.Request.IsPasswordGrantType())
{
// Password type request processing only
// code that shall not touch any refresh_token request
}
else if(!context.Request.IsRefreshTokenGrantType())
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid grant type.");
return;
}
return;
}
The refresh_token shall just be able to pass through this method and is handled by another piece of middleware that handles refresh_token.
If you want more in depth knowledge about what the auth server is doing, you can have a look at the code of the OpenIdConnectServerHandler:
https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/master/src/AspNet.Security.OpenIdConnect.Server/OpenIdConnectServerHandler.Exchange.cs
On the client side you must also be able to handle the auto refresh of the token, here is an example of an http interceptor for Angular 1.X, where one handles 401 reponses, refresh the token, then retry the request:
'use strict';
app.factory('authInterceptorService',
['$q', '$injector', '$location', 'localStorageService',
function ($q, $injector, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var $http;
var _request = function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
};
var _responseError = function (rejection) {
var deferred = $q.defer();
if (rejection.status === 401) {
var authService = $injector.get('authService');
console.log("calling authService.refreshToken()");
authService.refreshToken().then(function (response) {
console.log("token refreshed, retrying to connect");
_retryHttpRequest(rejection.config, deferred);
}, function () {
console.log("that didn't work, logging out.");
authService.logOut();
$location.path('/login');
deferred.reject(rejection);
});
} else {
deferred.reject(rejection);
}
return deferred.promise;
};
var _retryHttpRequest = function (config, deferred) {
console.log('autorefresh');
$http = $http || $injector.get('$http');
$http(config).then(function (response) {
deferred.resolve(response);
},
function (response) {
deferred.reject(response);
});
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
authInterceptorServiceFactory.retryHttpRequest = _retryHttpRequest;
return authInterceptorServiceFactory;
}]);
And here is an example I just did for Aurelia, this time I wrapped my http client into an http handler that checks if the token is expired or not. If it is expired it will first refresh the token, then perform the request. It uses a promise to keep the interface with the client-side data services consistent. This handler exposes the same interface as the aurelia-fetch client.
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
import {AuthService} from './authService';
#inject(HttpClient, AuthService)
export class HttpHandler {
constructor(httpClient, authService) {
this.http = httpClient;
this.authService = authService;
}
fetch(url, options){
let _this = this;
if(this.authService.tokenExpired()){
console.log("token expired");
return new Promise(
function(resolve, reject) {
console.log("refreshing");
_this.authService.refreshToken()
.then(
function (response) {
console.log("token refreshed");
_this.http.fetch(url, options).then(
function (success) {
console.log("call success", url);
resolve(success);
},
function (error) {
console.log("call failed", url);
reject(error);
});
}, function (error) {
console.log("token refresh failed");
reject(error);
});
}
);
}
else {
// token is not expired, we return the promise from the fetch client
return this.http.fetch(url, options);
}
}
}
For jquery you can look a jquery oAuth:
https://github.com/esbenp/jquery-oauth
Hope this helps.
Following on from #longday's answer, I have had success in using this code to force a client refresh without having to manually query an open id endpoint:
OnValidatePrincipal = context =>
{
if (context.Properties.Items.ContainsKey(".Token.expires_at"))
{
var expire = DateTime.Parse(context.Properties.Items[".Token.expires_at"]);
if (expire > DateTime.Now) //TODO:change to check expires in next 5 mintues.
{
context.ShouldRenew = true;
context.RejectPrincipal();
}
}
return Task.FromResult(0);
}

pass google spreadsheet data to a soap web service

I am going to develop a google app script to read data from a google spreadsheet and pass the data to a soap webservice.
Here is the script,
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
// Get the range of cells that store employee data.
var studentDataRange = ss.getActiveRange();
// For every row of student data, generate an student object.
var studentObjects = getRowsData(sheet, studentDataRange);
for (var i=0; i<studentObjects.length; i++)
{
var student = studentObjects[i];
var options = {
"studentId" : student.studentId,
"Marks" : student.marks,
"url" : ss.getUrl(),
} ;
//Here i want to pass the options values to a web service.
}
Any one can help me to figure this out.
Thanks
To begin with you will need to build the soap Input XML as defined by the WSDL i.e the options must be converted to an XML of defined format then URL Fetch needs to be used to call the SOAP WS. Sample to get exchange rate
function WS_Currency() {
var soapIn = XmlService.parse(
'<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/soap/envelope/"></soap12:Envelope>'); // We cannot build the XML from scratch as GAS XML does not allow multiple NS for root
var soapEnv = soapIn.getRootElement();
//Build your soap message
var soapNS = soapEnv.getNamespace("soap12");
var apiNS = XmlService.getNamespace("http://www.webserviceX.NET/");
var soapBody = XmlService.createElement("Body", soapNS);
var ConversionRate = XmlService.createElement("ConversionRate", apiNS);
var FromCurrency = XmlService.createElement("FromCurrency", apiNS).setText('USD');
var ToCurrency = XmlService.createElement("ToCurrency", apiNS).setText('GBP');
ConversionRate.addContent(FromCurrency);
ConversionRate.addContent(ToCurrency);
soapBody.addContent(ConversionRate);
soapEnv.addContent(soapBody);
// Set the http options here
var options =
{
"method" : "post",
"contentType" : "text/xml; charset=utf-8",
"payload" : XmlService.getRawFormat().format(soapIn),
"muteHttpExceptions" : true
};
// Call the WS
var soapCall= UrlFetchApp.fetch("http://www.webservicex.net/CurrencyConvertor.asmx?WSDL", options);
// Extract the output, yeah this is the only way we need to traverse the received XML :(
var cRate = XmlService.parse(soapCall.getContentText()).getRootElement().getChild("Body", soapNS).getChild("ConversionRateResponse", apiNS).getChild("ConversionRateResult", apiNS).getValue();
Logger.log(cRate);
}

Facebook Login Button: Workaround for bug of Chrome on iOS

Currently, I am trying to solve a bug logged in Facebook Developer:
https://developers.facebook.com/bugs/325086340912814/
I am using ASP.NET MVC 3 with Facebook C# SDK and Facebook Javascript SDK.
Here is javascript code:
window.fbAsyncInit = function () {
// init the FB JS SDK
FB.init({
appId: '298365513556623', // App ID from the App Dashboard
channelUrl: 'http://www.christianinfoportal.com/channel.html', // Channel File for x-domain communication
status: true, // check the login status upon init?
cookie: true, // set sessions cookies to allow your server to access the session?
xfbml: true // parse XFBML tags on this page?
});
// Additional initialization code such as adding Event Listeners goes here
FB.Event.subscribe('auth.authResponseChange', function (response) {
if ((response.status == 'connected')) {
if (fbIgnoreFirstEventFire)
fbIgnoreFirstEventFire = false;
else {
// the user is logged in and has authenticated your
// app, and response.authResponse supplies
// the user's ID, a valid access token, a signed
// request, and the time the access token
// and signed request each expire
var uid = response.authResponse.userID;
var accessToken = response.authResponse.accessToken;
// TODO: Handle the access token
// Do a post to the server to finish the logon
// This is a form post since we don't want to use AJAX
var form = document.createElement("form");
form.setAttribute("method", 'post');
form.setAttribute("action", '/FBLogin');
var field = document.createElement("input");
field.setAttribute("type", "hidden");
field.setAttribute("name", 'accessToken');
field.setAttribute("value", accessToken);
form.appendChild(field);
var field = document.createElement("input");
field.setAttribute("type", "hidden");
field.setAttribute("name", 'returnUrl');
field.setAttribute("value", window.location.href);
form.appendChild(field);
document.body.appendChild(form);
form.submit();
}
} else if (response.status == 'not_authorized') {
// the user is logged in to Facebook,
// but has not authenticated your app
} else {
// the user isn't logged in to Facebook.
}
});
};
// Load the SDK's source Asynchronously
(function (d, debug) {
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) { return; }
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all" + (debug ? "/debug" : "") + ".js";
ref.parentNode.insertBefore(js, ref);
}(document, /*debug*/ false));
The View code is pretty simple:
<div id="fbLoginButton" class="fb-login-button" data-show-faces="false" data-width="50" data-max-rows="1" data-scope="email"></div>
I basically forward the access token to a server side code page:
public ActionResult FBLogin(String returnUrl)
{
var accessToken = Request["accessToken"];
//FB fires even without the user clicking the Log In button
Session["FBLoginFirstClick"] = true;
//Session["AccessToken"] = accessToken;
//this.lblAccessToken.Text = accessToken;
//Response.Redirect("/MyUrlHere");
var client = new Facebook.FacebookClient(accessToken);
dynamic me = client.Get("me");
String email, fullName;
fullName = me.first_name + " " + me.last_name;
email = me.email;
Models.User.ThirdPartyLogin(email, fullName, Session);
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
Solved by using Facebook authentication redirect.
https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/

Upload an image to Drupal 7 / Services 3 from Titanium?

This must be close but I can't figure out what's causing the error.
In my Titanium app, I have a Webview with a canvas element and this code:
function getImageData() {
return canvas.toDataURL('image/png');
}
I am moving that data to the Titanium app like this:
var imageBase64data = webview.evalJS('getImageData()')
The data looks good starting with "data:image/png;base64,"...
Then in Titanium, I have a logged-in drupal session and call this function:
function uploadImage(imageBase64data, callback) {
var url = REST_PATH + "file.json";
var file = {
filename: utils.createRandomString() + ".png",
file: imageBase64data
// uid: Titanium.App.Properties.getString("userUid"),
// filesize: ""+Titanium.Utils.base64decode(imageBase64data).length,
};
var xhr = Titanium.Network.createHTTPClient({timeout: 30000});
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
var authString = Titanium.App.Properties.getString("userSessionName")+'='+Titanium.App.Properties.getString("userSessionId");
xhr.setRequestHeader("Cookie", authString);
xhr.onload = function() {
if(xhr.status == 200) {
var response = xhr.responseText;
callback(response);
}
};
xhr.onerror = function(e) {
alert("There was an error: " + e.error);
Ti.API.info(JSON.stringify(this));
};
xhr.open("POST", url);
xhr.send(file);
}
xhr.onerror is being called with e.error = "undefined"
The trace looks like this:
{
"responseData":{},
"readyState":4,
"connected":true,"UNSENT":0,"DONE":4,"HEADERS_RECEIVED":2,"OPENED":1,"LOADING":3,
"responseText":null,"status":406
}
I think authentication is working because I was previously getting a "need authentication" error until I added the Cookie header.
That was with the installation provided by Drupanium. I just did a fresh Drupal and fresh Services 3 install and my file is uploading nicely.