3rd party Authentication Issues in Google Appscript - javascript

I am using an internal service to Authenticate my Gsuite add-on. The Issue is we cannot find any request made from the Add-on to internal servers on Prod (or Dev environments). These are public facing authentication endpoints.
Currently this Addon only runs in Gmail. Unsure If there are any bandwidth limits we might be reaching but Google support said they couldn't help.
We followed the docs Google provides for 3rd Part Oauth.
auth File:
function accessProtectedResource(url, method_opt, headers_opt, body) {
var service = getOAuthService();
var maybeAuthorized = service.hasAccess();
if (maybeAuthorized) {
// A token is present, but it may be expired or invalid. Make a
// request and check the response code to be sure.
// Make the UrlFetch request and return the result.
var accessToken = service.getAccessToken();
var method = method_opt || 'get';
var headers = headers_opt || {};
headers['Authorization'] = Utilities.formatString('Bearer %s', accessToken);
var resp = UrlFetchApp.fetch(url, {
'headers': headers,
'method' : method,
'contentType': 'application/json',
'muteHttpExceptions': true, // Prevents thrown HTTP exceptions.
'payload': JSON.stringify(body)
});
var code = resp.getResponseCode();
if (code >= 200 && code < 300) {
return resp.getContentText('utf-8'); // Success
} else if (code >= 400 && code <= 403) {
// Not fully authorized for this action.
maybeAuthorized = false;
} else {
// Handle other response codes by logging them and throwing an exception.
Logger.log('Server error (%s): %s', code.toString(),
resp.getContentText('utf-8'));
throw ('Backend server error: ' + code);
}
}
if (!maybeAuthorized) {
// Invoke the authorization flow using the default authorization
// prompt card.
CardService.newAuthorizationException()
.setAuthorizationUrl(service.getAuthorizationUrl())
.setCustomUiCallback('createCompanyeAuthorizationUi')
.setResourceDisplayName('Company')
.throwException();
}
}
function getOAuthService() {
// added Console.log for debug
var service = OAuth2.createService('COMPANY_AUTH')
var servicewithurl = service.setAuthorizationBaseUrl(urlBase + '/oauth2/authenticate/');
var setauthBase = servicewithurl.setAuthorizationBaseUrl(urlBase + '/oauth2/authenticate/');
console.log('setauthBase ', setauthBase);
var setToken = setauthBase.setTokenUrl(urlBase + '/oauth/token/');
console.log('setToken ',setToken);
var setCallback = setToken.setCallbackFunction('authCallback');
console.log('setCallback ', setCallback);
setCallback.setClientId('2222TW9LzuT4AgAMN')
.setClientSecret('WZN5tyDKfMYoDLNnC')
.setPropertyStore(PropertiesService.getUserProperties());
.setCache(CacheService.getUserCache())
console.log('setCallback2 ', setCallback);
return setCallback;
}
function authCallback(callbackRequest) {
var authorized = getOAuthService().handleCallback(callbackRequest);
if (authorized) {
return HtmlService.createHtmlOutput(
'Success! <script>setTimeout(function() { top.window.close() }, 1);</script>');
} else {
return HtmlService.createHtmlOutput('Denied');
}
}
function createCompanyAuthorizationUi() {
var service = getOAuthService();
var authUrl = service.getAuthorizationUrl();
var authAction = CardService.newAuthorizationAction().setAuthorizationUrl(authUrl);
var CompanyLogo = CardService.newImage().setAltText("Company Logo").setImageUrl("image_url_replaced");
var headerText = 'Create actions in Company from your email.';
var mainImage = CardService.newImage().setAltText("Company Mail").setImageUrl("image_url_replaced");
var loginButton = CardService.newImage().setAltText("Login").setImageUrl("image_url_replaced").setAuthorizationAction(authAction);
var signupLink = CardService.newOpenLink()
.setUrl(urlBase + "/join")
.setOpenAs(CardService.OpenAs.FULL_SIZE)
.setOnClose(CardService.OnClose.NOTHING);
var signupButton = CardService.newImage().setAltText("Sign up").setImageUrl("image_url_replaced").setOpenLink(signupLink);
var featureOne = CardService.newKeyValue()
.setIconUrl("image_url_replaced")
.setContent("Company is the best way to plan, execute, and monitor all of your teams's projects in one place.")
.setMultiline(true);
var featureTwo = CardService.newKeyValue()
.setIconUrl("image_url_replaced")
.setContent("Content here")
.setMultiline(true);
var card = CardService.newCardBuilder()
.addSection(CardService.newCardSection()
.addWidget(CardService.newTextParagraph())
.addWidget(CompanyLogo)
.addWidget(CardService.newTextParagraph().setText(headerText))
.addWidget(mainImage)
.addWidget(signupButton)
.addWidget(loginButton)
.addWidget(featureOne)
.addWidget(featureTwo)
).build();
return [card];
}
function resetOAuth() {
getOAuthService().reset();
}
function getCompanyResourceUrls() {
accessProtectedResource(urlBase + '/api/v1/books/');
}
function makeRequest(url, params) {
var oauthService = getOAuthService();
var response = UrlFetchApp.fetch(url, {
headers: {
Authorization: 'Bearer ' + oauthService.getAccessToken()
},
body: params
});
return response;
}
function getUserId() {
var cache = CacheService.getUserCache();
var userId = cache.get('userId');
if(userId) {
return userId;
}
var response = accessProtectedResource(urlBase + '/oauth/getIdentity');
var data = JSON.parse(response);
var userId = data.data.id;
cache.put('userId', userId);
return userId;
}
appsscript.json
{
"timeZone": "America/New_York",
"dependencies": {
"libraries": [{
"userSymbol": "OAuth2",
"libraryId": "",
"version": "37",
"developmentMode": true
}]
},
"webapp": {
"access": "ANYONE",
"executeAs": "USER_ACCESSING"
},
"oauthScopes": ["https://www.googleapis.com/auth/gmail.addons.execute", "https://www.googleapis.com/auth/gmail.addons.current.message.readonly", "https://www.googleapis.com/auth/script.external_request"],
"urlFetchWhitelist": ["https://company.com/", "https://company.com/oauth/token/", "https://company.com/api/v1/books/", "https://companycom/oauth/revoke"],
"runtimeVersion": "V8",
"gmail": {
"name": "Name",
"logoUrl": "https://logourl.com",
"contextualTriggers": [{
"unconditional": {
},
"onTriggerFunction": "buildAddOn"
}],
"universalActions": [{
"text": "Logout",
"runFunction": "logout"
}],
"primaryColor": "#ffffff",
"secondaryColor": "#ffffff",
"authorizationCheckFunction": "getCompanyResourceUrls",
"openLinkUrlPrefixes": ["https://company.com/"]
}
}

You made mistake:
.setCustomUiCallback('createCompanyeAuthorizationUi')
but you have
createCompanyAuthorizationUi
I am not sure, but it can help you.

Related

Handle pagination REST API

I implemented a function calling a REST API. I defined my query, header, parameters.
I wasn't able to manage pagination with loadData() method. How can I achieve my second call to get the page 2 for example ? I tried the recursive way but it didn't work.
Here is an response example of HTTP GET request to REST API:
{
"data": [
{
...
}
],
"pageSize": 1000,
"currentPage": 1,
"lastPage": 3,
"totalObjectCount": 2789,
"truncated": false
}
The function I implemented :
readRestAPI: function (sQuery) {
var oDataModel = new JSONModel();
oDataModel.setSizeLimit(2000);
var oAuthModel = this.getOwnerComponent().getModel("AuthToken");
var oUserModel = this.getOwnerComponent().getModel("User");
var sCompanyName = oUserModel.getData().User[0].userCompanyName;
var sAccountName = oAuthModel.getData().accountName;
var sToken = oAuthModel.getData().token;
var iPage = 1;
var iPageSize = 1000; // Max object for one page in Query API is 1000
var mParams = {
"account": sAccountName,
"company": sCompanyName,
"query": sQuery,
"page": iPage,
"pageSize": iPageSize
};
var sURLQueryAPI = "/API/query/v1";
var sType = "GET";
var mHeaders = {
"Authorization": sToken,
"Content-Type": "application/json",
"X-Client-ID": sAccountName,
"X-Client-Version": "1.0.0"
};
return oDataModel.loadData(sURLQueryAPI, $.param(mParams, true), true, sType, false, true, mHeaders).then(
function (resData) {
return oDataModel;
});
}
Could you please explain me how can I handle the pagination ( 1 to 3 for instance )? Thanks

Handle multiple gmail accounts

I am using Google Calendar JavaScript API.It works fine for single account.Problem is when user is logged-in into two of my gmail accounts, It asks user every time to select which account user want to use.
I want API to remember my last selection at least for the session or most preferably till user disconnect himself.
Here is my JS file.
var clientId = $("#CalendarClientId").val();
var scopes = 'https://www.googleapis.com/auth/calendar';
var objEventList = [];
var calendarIdAtt;
function InitilizeCalendarAPI() {
// Step 2: Reference the API key
window.setTimeout(checkAuth, 5);
}
function CheckGoogleCalAuth() {
gapi.auth.authorize({ client_id: clientId, scope: scopes }, HandleAuthCallBack);
}
function checkAuth() {
gapi.auth.authorize({ client_id: clientId, scope: scopes }, HandelCalAuthResult);
}
function CheckAuthentication() {
gapi.auth.checkSessionState({ client_id: clientId }, CheckAuthCallback);
}
function CheckAuthCallback(loginDataCallback) {
if (loginDataCallback == false) {
checkAuth();
$("#googleAuthorize-Button").hide();
}
}
function HandelCalAuthResult(authResult) {
if (authResult && !authResult.error) {
/*authorizeButton.style.visibility = 'hidden';*/
$("#googleAuthorize-Button").hide();
if ($("#googleConnected").length > 0) {
$("#googleConnected").show();
}
/*bind listing of google calendar name in addevent page*/
if ($("#divGoogleCalendarNameList").length > 0) {
GetListOfCalendarNameFromGoogle();
}
} else {
$("#googleAuthorize-Button").show();
/*authorizeButton.style.visibility = '';*/
if ($("#googleConnected").length > 0) {
$("#googleConnected").hide();
}
}
if ($('#googleAuthorized-Button').length > 0) {
$("#googleAuthorize-Button").hide();
$('#googleAuthorized-Button').show();
$('#calenderSettingIcon').attr('data-original-title', 'Connected To Google');
}
}
function CheckAutheForEventList() {
gapi.auth.checkSessionState({ client_id: clientId }, CheckAuthCallbackListEvent);
}
function CheckAuthCallbackListEvent(loginDataCallback)
{
gapi.auth.authorize({ client_id: clientId, scope: scopes }, LoadEventListinAPI);
}
function checkAuthEventList() {
ConfirmConnectionToGoogleCalendar(function (isConnect) {
if (isConnect) {
gapi.auth.authorize({ client_id: clientId, scope: scopes }, LoadEventListinAPI);
}
});
}
function LoadEventListinAPI() {
gapi.client.load('calendar', 'v3', GetAllCalendar);
}
function GetAllCalendar() {
gapi.client.load('calendar', 'v3', GetCalendarAndTheirEvents);
}
function GetCalendarAndTheirEvents() {
objEventList = [];
try {
var objDate = GetMinAndMaxDate();
var finalMinDate = (objDate.MinDate).toISOString();;
var finalMaxDate = (objDate.MaxDate).toISOString();;
var request = gapi.client.calendar.calendarList.list();
var tempCount = 0;
request.execute(function (resp) {
var calLength = resp.items.length;
$.each(resp.items, function (index, item) {
if (item.accessRole == "owner") {
try {
calendarIdAtt = item.id;
var request = gapi.client.calendar.events.list({
'calendarId': item.id,
'showDeleted': false,
'singleEvents': true,
'orderBy': 'startTime',
'maxResults': 100,
'timeMin': finalMinDate,
'timeMax': finalMaxDate
});
request.execute(function (resp) {
tempCount = tempCount + 1;
var events = resp.items;
if (events.length > 0) {
objEventList.push(events);
}
if (tempCount == calLength) {
//Binding Kendo Scheduler with Google Calenda Events
var eventsList = BindEventToCalendar();
LoadEventsScheduler(eventsList);
}
});
}
catch (error) {
tempCount = tempCount + 1;
if (tempCount == calLength) {
BindEventToCalendar();
}
}
}
else {
tempCount = tempCount + 1;
if (tempCount == calLength) {
BindEventToCalendar();
}
}
});
});
return objEventList;
}
catch (exception) {
BindEventToCalendar();
return null;
}
}
//Function will display google calendar events in the Kendo Scheduler
function LoadEventsScheduler(eventsList) {
//This will retrun all the google calendar events to the server side for the scheduler
var request = {
EventsList: eventsList, CalendarAccount: calendarIdAtt
};
$.ajax({
type: "POST",
data: JSON.stringify(request),
url: $_SetEventScheduler,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
var data;
if (msg.hasOwnProperty("d")) {
data = msg.d;
}
else
data = msg;
if (data.success) {
$("#scheduler").data("kendoScheduler").dataSource.read();
}
else {
}
}
});
}
I noticed that API show me logged in but calendar is not stored in case of multiple accounts,so it asks me again to select the account.
Summary: I want to save the complete scenario (may be calendar_id or any unique identifier) which can tell API which account and which calendar should be picked up.
If you need more details, please let me know.
I simply used the auth2 implementation and it is working perfect.
Basically current session state function do not work properly in old API. That's why it was asking every time for selecting the account and calendar.

Chrome Extension: "No resource with given identifier found" when trying to Network.getResponseBody

I'm writing a Chrome Extension that can get HTTP response for a site. I try to use debugger for getting response body:
var gCurrentTab;
chrome.debugger.onEvent.addListener(function (source, method, params) {
if (gCurrentTab.id != source.tabId) {
return;
}
if (method == "Network.loadingFinished") {
var tabId = source.tabId;
var requestId = params.requestId;
chrome.debugger.sendCommand(
source,
"Network.getResponseBody",
{"requestId": requestId},
function (body) {
console.log(body);
chrome.debugger.detach(source);
});
}
}
);
chrome.webRequest.onBeforeRequest.addListener(function (details) {
var url = details.url;
if (url.indexOf('/mypage') >= 0) {
chrome.tabs.query({
currentWindow: true,
active: true
}, function (tabs) {
gCurrentTab = tabs[0];
chrome.debugger.attach({
tabId: gCurrentTab.id
}, "1.0", function () {
chrome.debugger.sendCommand({
tabId: gCurrentTab.id
}, "Network.enable");
});
});
}
},
{urls: []}, ["requestBody", "blocking"]);
But I always get
Unchecked runtime.lastError while running debugger.sendCommand: {"code":-32000,"message":"No resource with given identifier found"}
at chrome-extension://ikphgobkghdkjkfplgokmapjlbdfeegl/background.js:11:29
error, and the body is undefined.
Does anyone have idea about why this happen? Thanks!
It was because the website sends many responses, and this code will see another request other than I want, then detach the debugger so I can't get the result.
To solve this, just use a single debugger and do not detach it, or only detach when it's safe to.
var gAttached = false;
var gRequests = [];
var gObjects = [];
chrome.debugger.onEvent.addListener(function (source, method, params) {
if (method == "Network.requestWillBeSent") {
// If we see a url need to be handled, push it into index queue
var rUrl = params.request.url;
if (getTarget(rUrl) >= 0) {
gRequests.push(rUrl);
}
}
if (method == "Network.responseReceived") {
// We get its request id here, write it down to object queue
var eUrl = params.response.url;
var target = getTarget(eUrl);
if (target >= 0) {
gObjects.push({
requestId: params.requestId,
target: target,
url: eUrl
});
}
}
if (method == "Network.loadingFinished" && gObjects.length > 0) {
// Pop out the request object from both object queue and request queue
var requestId = params.requestId;
var object = null;
for (var o in gObjects) {
if (requestId == gObjects[o].requestId) {
object = gObjects.splice(o, 1)[0];
break;
}
}
// Usually loadingFinished will be immediately after responseReceived
if (object == null) {
console.log('Failed!!');
return;
}
gRequests.splice(gRequests.indexOf(object.url), 1);
chrome.debugger.sendCommand(
source,
"Network.getResponseBody",
{"requestId": requestId},
function (response) {
if (response) {
dispatch(source.tabId, object.target, JSON.parse(response.body));
} else {
console.log("Empty response for " + object.url);
}
// If we don't have any request waiting for response, re-attach debugger
// since without this step it will lead to memory leak.
if (gRequests.length == 0) {
chrome.debugger.detach({
tabId: source.tabId
}, function () {
chrome.debugger.attach({
tabId: source.tabId
}, "1.0", function () {
chrome.debugger.sendCommand({
tabId: source.tabId
}, "Network.enable");
});
});
}
});
}
}
);
var initialListener = function (details) {
if (gAttached) return; // Only need once at the very first request, so block all following requests
var tabId = details.tabId;
if (tabId > 0) {
gAttached = true;
chrome.debugger.attach({
tabId: tabId
}, "1.0", function () {
chrome.debugger.sendCommand({
tabId: tabId
}, "Network.enable");
});
// Remove self since the debugger is attached already
chrome.webRequest.onBeforeRequest.removeListener(initialListener);
}
};
// Attach debugger on startup
chrome.webRequest.onBeforeRequest.addListener(initialListener, {urls: ["<all_urls>"]}, ["blocking"]);
// Filter if the url is what we want
function getTarget(url) {
for (var i in TARGETS) {
var target = TARGETS[i];
if (url.match(target.url)) {
return i;
}
}
return -1;
}
const TARGETS = [
{url: '/path1', desc: 'target1'},
{url: '/path2', desc: 'target2'}
]
I am facing similar issue. I figured that sendCommand was not executing immediately. I was facing the issue for the requests which are sent before sending "Network.enable" was complete. Try adding completion for
chrome.debugger.sendCommand({
tabId: gCurrentTab.id
}, "Network.enable")

ExtJs minify Gets ignored

We have a CMS so I don't have access to the header of the HTML page which gets rendered for our extjs implementation. So I had to make a workaround which is like this:
Ext.local = {};
var lang = {
initLang: function (revisionNr) {
var local = localStorage.getItem('localLang')
if (!local) {
AjaxHandlerByClass('ajax/lang/webuser/init', {}, this.loadLangRemote);
} else {
local = JSON.parse(local);
if (local.revisionNr == config.revisionNr && local.lang == config.lang) {
console.log('loading local lang variables');
if (local.date < new Date().getTime() - (24 * 60 * 60 * 1000) * 2) {//2 day caching time before retry
delete window.localStorage.localLangBackup;
}
this.loadLangLocal(local);
} else {
delete window.localStorage.localLang;
AjaxHandlerByClass('ajax/lang/webuser/init', {}, this.loadLangRemote);
}
}
},
loadLangRemote: function (data) {
data.revisionNr = config.revisionNr;
data.lang = config.lang;
data.date = new Date().getTime();
lang.loadLangLocal(data);
localStorage.setItem('localLang', JSON.stringify(data));
},
loadLangLocal: function (data) {
var jsElm = document.createElement("script");
jsElm.type = "application/javascript";
jsElm.src = 'js/freetext-deploy.min.js?rev={/literal}{$revisionNr}{literal}';
document.getElementsByTagName('head')[0].appendChild(jsElm);
Ext.Date.defaultFormat = 'd-m-Y';
if (!debug) {
Ext.Loader.config.disableCaching = true;
}
Ext.application({
name: 'freetextOrder',
appFolder: 'modules/extjs/freetextOrder/app',
controllers: [
'Main'
],
launch: function () {
var freetextOrder = Ext.create('Ext.container.Container', {
renderTo: Ext.get('freetextOrderDiv'),
layout: 'fit',
id: 'catalogAdministrationDiv_ext',
height: 800,
cls: 'x-dig-override',
items: [Ext.create('freetextOrder.view.base.MainView', {})],
layout:'fit'
});
}
});
Ext.local = data;
}
};
lang.initLang();
The problem I'm having is that the minified version gets ignored completely. I see it load on the http request but extjs ignores them.... even though I can see the objects are being created after include (via console log)
Anyone any idea how I can achieve this?
as i see none found the answer so i post my own here wich i came up with.
Since i could for the love of god not load the damn thing i refactored the loader and exported it into a Js. file. wich i reqired and called later on in code.
exported lang.js file:
Ext.define('Lang', {
singleton: true,
ApplicationConf: null,
Launch: function (launchConfig) {
this.ApplicationConf = launchConfig;
var local = localStorage.getItem('localLang');
var me = this;
this.loadLangRemote = function (data) {
debugger;
data.revisionNr = config.revisionNr;
data.lang = config.lang;
data.date = new Date().getTime();
me.loadLangLocal(data);
localStorage.setItem('localLang', JSON.stringify(data));
};
this.loadLangLocal = function (data) {
Ext.local = data;
Ext.lang = function (langId) {
if (Ext.local[langId]) {
return Ext.local[langId];
}
delete window.localStorage.localLang;
localStorage.setItem('localLangBackup', true);
return langId;
}
Ext.application(me.ApplicationConf);
};
if (!local) {
Ext.Ajax.request({
url: 'ajax/lang/webuser/init',
params: {
sid: sid,
},
success: function (data) {
debugger;
me.loadLangRemote(Ext.JSON.decode(data.responseText));
}
})
} else {
local = JSON.parse(local);
if (local.revisionNr == config.revisionNr && local.lang == config.lang) {
console.log('loading local lang variables');
if (local.date < new Date().getTime() - (24 * 60 * 60 * 1000) * 2) {//2 day caching time before retry
delete window.localStorage.localLangBackup;
}
debugger;
me.loadLangLocal(local);
} else {
delete window.localStorage.localLang;
Ext.Ajax.request({
url: 'ajax/lang/webuser/init',
params: {
sid: sid,
},
success: function (data) {
me.loadLangRemote(Ext.JSON.decode(data.responseText));
}
})
}
}
},
})
And IMPORTANT was to add the
Ext.onReady(function () {
Lang.Launch({
name: 'catalogAdministration',
appFold....
To the call of the Launch function in code, bacause it would have been not defined at run time. i added the file to the minified file first and call the Lang.Launch instead Ext.Application.
Hope somone has use of my solution :)

Post Facebook event via javascript SDK, not getting any response

I am trying to create event via my website.I am not getting any response while post event.
Below is my code
<div id="fb-root"></div>
<span id='fbinfo'><fb:name uid='loggedinuser' useyou='false'></fb:name></span>
<div id="fb-root"></div>
<span id='fbinfo'><fb:name uid='loggedinuser' useyou='false'></fb:name></span>
<!-- USE 'Asynchronous Loading' version, for IE8 to work
http://developers.facebook.com/docs/reference/javascript/FB.init/
<script type="text/javascript" src="<%=path%>/js/socialNetwork.js"></script> -->
<script>
FB.init({appId: appid, status: true, cookie: true, xfbml: true});
FB.Event.subscribe('auth.sessionChange', function(response) {
if (response.status === 'connected') {
// var session = FB.getSession();
fbtoken = response.authResponse.accessToken;
fbuserid = response.authResponse.userID;;
}
// alert("fbtoken"+fbtoken);
});
FB.getLoginStatus(function(response) {
if (response.status === 'connected') {
// var session = FB.getSession();
fbtoken = response.authResponse.accessToken;
fbuserid = response.authResponse.userID;;
}
else{
loginFB();
}
// alert("fbtoken 2"+fbtoken);
});
function loginFB() {
FB.login(function(response) {
if (response.status === 'connected') {
// var session = FB.getSession();
fbtoken = response.authResponse.accessToken;
fbuserid = response.authResponse.userID;;
}
}, {scope:’create_event'});
// alert("fbtoken"+fbtoken);
}
function logoutFB() {
FB.logout(function(response) {
// user is now logged out
});
}
function createEvent(name, startTime, endTime, location, description) {
var eventData = {
"access_token": fbtoken,
"start_time" : startTime,
"end_time": endTime,
"location" : location,
"name" : name,
"description": description,
"privacy":"OPEN"
};
FB.api("/me/events","post",eventData,function(response){
alert(response.error);
if (!response || response.error) {
alert("Response "+response.id);
} else {
alert("Post ID: " + response.id);
}
});
}
function createMyEvent(){
var name = "My Amazing Event";
var startTime = "10/29/2015 12:00 PM";
var endTime = "10/29/2015 06:00 PM";
var location = "Dhaka";
var description = "It will be freaking awesome";
createEvent(name, startTime,endTime, location, description);
alert(name);
}
</script>
I am getting the response.id as invalid.
So because of that i think event is not created on my FB wall..
Can someone please help me on this.
You cannot create events via the Graph API.
Source: https://developers.facebook.com/docs/graph-api/reference/v2.3/event#publish
Changelog: https://developers.facebook.com/docs/apps/changelog#v2_0_games

Categories

Resources