How to read data using the spreadsheets API without using oauth2 - google-api

This is Yet another error 403 on Google Sheets API. Um trying to read the data from a Spreadsheet and expose it on a website, using some custom HTML filters.
So, there is this question:
Error 403 on Google Sheets API
Still, I cannot figure out what to do. I have setup my service account in the google cloud platform, granted it permissions to access my Spreadsheet - I have even made my SpreadSheet accessible via link (but that is restricted to emails from my organization, so it seems useless anyway). I've also activated domain-wide delegation for my service account - didn't make any difference at all. I'm pretty sure the issue is not related to a wrong key or some typo because, when I remove the access to the service email from the sheet, I steel get a 403 error, but it comes with a message saying the caller doesn't have access to that spreadsheet.
People keep mentioning OAuth, but I don't want to use that, since I'm intending to use a simple API access, as in this Google GitHub example. I'm using my business account, so there might be some issue related to that.
Here is the HTML (which I'm running from a simple HTTP server on python, not directly, given the fact the GAPI doesn't handle localhost/ sources very well):
<!DOCTYPE html>
<html>
<head>
<title>Google Sheets API Quickstart</title>
<meta charset="utf-8" />
</head>
<body>
<p>Google Sheets API Quickstart</p>
<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize_button" style="display: none;">Authorize</button>
<button id="signout_button" style="display: none;">Sign Out</button>
<pre id="content" style="white-space: pre-wrap;"></pre>
<script type="text/javascript">
// Client ID and API key from the Developer Console
var API_KEY = 'My-API-KEY';
var DISCOVERY_DOCS = ["https://sheets.googleapis.com/$discovery/rest?version=v4"];
var SCOPES = "https://www.googleapis.com/auth/spreadsheets.readonly";
function handleClientLoad() {
gapi.load('client', initClient);
}
function initClient() {
gapi.client.init({
apiKey: API_KEY,
discoveryDocs: DISCOVERY_DOCS
}).then(function(){listMajors();})
}
function appendPre(message) {
var pre = document.getElementById('content');
var textContent = document.createTextNode(message + '\n');
pre.appendChild(textContent);
}
function listMajors() {
gapi.client.sheets.spreadsheets.values.get({
spreadsheetId: 'my-spreadsheet-id',
range: 'Sheet1!A1:A10',
}).then(function(response) {
var range = response.result;
if (range.values.length > 0) {
appendPre('Name, Major:');
for (i = 0; i < range.values.length; i++) {
var row = range.values[i];
// Print columns A and E, which correspond to indices 0 and 4.
appendPre(row[0] + ', ' + row[4]);
}
} else {
appendPre('No data found.');
}
}, function(response) {
appendPre('Error: ' + response.result.error.message);
});
}
</script>
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function(){};handleClientLoad()"
onreadystatechange="if (this.readyState === lete') this.onload()">
</script>
</body>
</html>
Does anyone have anyclue on what I'm missing, or what else can I try?
Edit: sharing the spreadsheet publicly is not an option. The is a business email, and, therefore, the only option I've got here is sharing it domain wide, as I've already stated above. Also, this is a business spreadsheet, and therefofe contains sentitive data. Doesn't really make sense to expose it publicly. Besides that, I've already shared the spreadsheet with the service email. What I'm trying to achieve, after all, is to read the data from the spreadsheet without using oauth, and emdding it on a website (I know there is a built-in embed tool, but that doesn't suit me because I need to add a html filter).

If you try to access a Spreadsheet that is not public, you will have to use OAuth 2.0. Since it is not a public resource, you have to use the credentials of an account that has access to this resource.
In the example you provided, they are accessing a public resource, so the API key is enough. That's not your case.
Reference:
Using OAuth 2.0 to Access Google APIs

Related

Handling errors with reCAPTCHA v3

I have reCAPTCHA v3 set up on my page:
<script src="https://www.google.com/recaptcha/api.js?render=MY_KEY"></script>
If internet connectivity becomes unavailable or unreliable, reCAPTCHA will insert this HTML at the bottom of my page:
<div>
<div>
Could not connect to the reCAPTCHA service. Please check your internet connection and reload to get a reCAPTCHA challenge.
</div>
</div>
I would much rather handle this error in my JavaScript so I can display it to the user in a better way. However, the documentation for v3 doesn't indicate how to do this.
Any thoughts on how to catch this error?
You can put an onerror handler in the script tag itself:
<script src="https://www.google.com/recaptcha/api.js?render=MY_KEY" onerror="error_handler()">
But I think what you want is more along the lines of what reCAPTCHA v2 offers:
widgetId = grecaptcha.render('example1', {
'sitekey' : 'your_site_key',
'theme' : 'light',
'error-callback' : error_handler()
});
I doubt that works but there is another way. You just need to monitor the DOM for changes since you know that when there is an error Google will add a div to your page. You can do that with MutationObserver.
var m = new MutationObserver(function (mutationRecords) {
this.disconnect();
error_handler();
});
m.observe(document.body, {childList: true});
{childList: true} tells the MutationObserver to detect nodes added to the DOM and passes them to the callback function as the parameter mutationRecords (which contains array of MutationRecord objects). From there it's up to you what to do next.
This should at least be enough to get you started!

Display external application forms within Microsoft Dynamics 365

We have our own system which we need to integrate with MS Dynamics 365.For Example : In Accounts section we need to add an extra tab that loads IFrame or something that retrieves some extra information from our system.
The following are the things that I reached :
Inserting IFrame within a new Dashboard: (but it will not fetch specific account information, it will only pass the currently logged in user along with the organization name)
Unified Service Desk (USD): (we may add customization but this is a desktop app and we need it to be on web)
Microsoft flow: this would only work in the background when you create or edit an account (not sure if it has another functionality)
Extensions: Not sure how to use it to achieve the same functionality, I believe the solution may be here but I just need from where to start.
Has anybody done a similar thing before?
Thank you
You can definitely do it,
Here is how I just tried on one of my Trail Instance.
I added new Tab as you need, I called it "HTML Page"
On this Tab I added Webresource, you can add Iframe as well and call your external WebPage.
For my simple use case I created a simple HTML page as webresource in CRM and configured it to Webresource tab as below
Sample code for HTML. Dont worry about long html file. Mostly it is bla bla. What is of our importance is <body onload="myFunction()"> and then in
<script>
function myFunction() {
debugger;
alert("Account Id when from fromcontext is ");
alert(parent.Xrm.getformContext().data.entity.getId());
}
</script>
complete HTML code below
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<title>My first styled page</title>
</head>
<body onload="myFunction()">
<!-- Site navigation menu -->
<ul class="navbar">
<li>Home page
<li>Musings
<li>My town
<li>Links
</ul>
<!-- Main content -->
<h1>My first styled page</h1>
<p>Welcome to my styled page!
<p>It lacks images, but at least it has style.
And it has links, even if they don't go
anywhere…
<p>There should be more here, but I don't know
what yet.
<!-- Sign and date the page, it's only polite! -->
<address>Made 5 April 2004<br>
by myself.</address>
<script>
function myFunction() {
debugger;
alert("Account Id when from fromcontext is ", parent.Xrm.getformContext().data.entity.getId());
}
</script>
</body>
</html>
Also on Form Load of account I added additional Javascript. This javascript will create global variable which could be called from your webresource.
Article Link for additional Javascript
Sample code used for Javascript below
formContext=null;
function onload(executionContext){
debugger;
var formContext = executionContext.getFormContext();
Xrm.getformContext = function (){
return formContext;
};
Xrm.getParentAttribute = function (attrName) {
debugger;
return formContext.getAttribute(attrName);
};
Xrm.getParentControl = function (attrName) {
debugger;
return formContext.getControl(attrName);
};
}
Final Result will be something like below
Summary:
Create Wberesource/Iframe
Create Additiona Js on Load
Use global variable in your webresource.

Combining reCAPTCHA v2 and v3

I'm keen to use reCAPTCHA v3 for logins and stuff, but I'm unsure what to do with a 'low rating', it doesn't feel safe to deny access with no way for the user to move forward. What feels like a more complete solution would be to combine the "rating" from v3 with a puzzle challenge from v2 if the score is too low. How are other people approaching this issue?
Also, it appears that v3's grecaptcha.execute returns a similar result to v2, that is too say that it's not returning a rating, just a TOKEN which is verified in a similar way to v2?
I've code i found to demonstrate that they can both be used in the same HTML...
<!-- https://github.com/google/recaptcha/issues/279 -->
<script src="https://www.google.com/recaptcha/api.js?onload=v2_onload"></script>
<script src="https://www.google.com/recaptcha/api.js?onload=v3_onload&render=V3_SITE_KEY"></script>
<script src='https://www.google.com/recaptcha/api.js?render=V3_SITE_KEY'></script>
<div class="g-recaptcha" data-size="invisible" data-sitekey="V2_SITE_KEY" data-callback="v2_callback"></div>
<script type="text/javascript">
function v2_onload() { console.log('v2 loaded'); }
function v3_onload() { console.log('v3 loaded'); }
function v2_callback(token) { console.log('v2 token: ' + token); }
function v3_callback(token, score) { console.log('v3 token: ' + token + " ----- " + score); }
// call these manually
function test_v2() { grecaptcha.execute(); }
function test_v3() {
grecaptcha.execute('V3_SITE_KEY' , {action:'thisIsATest' }).then(v3_callback);
}
I have concerns then that if v3 requires sever-side validation, in order to implement v2 as well, either a page reload to invoke v2 (when server-side says "low rating" then reload and enable v2) OR v3 sever-side validation could be done via an ajax call, but that feels like something that can be inspected and manipulated by a bot (grab ajax response, change 'no' to 'yes' and then have the bot call the 'callback' function itself to gain access).
Any help or suggestions would be appreciated.
It looks like there is an answer to this question on the official Frequently Asked Questions website of reCAPTCHA.
Can I run reCAPTCHA v2 and v3 on the same page?
To do this, load the v3 site key as documented, and then explicitly render v2 using grecaptcha.render.
<html>
<head>
<title>reCAPTCHA demo: Running both v2 and v3</title>
<script src="https://www.google.com/recaptcha/api.js?render=v3_site_key"></script>
<script>
grecaptcha.ready(() => {
grecaptcha.render('html_element', {
'sitekey' : 'v2_site_key'
});
});
</script>
<script>
function onSubmit() {
grecaptcha.ready(() => {
grecaptcha.execute('v3_site_key', {action: 'homepage'}).then((token) => {
...
});
});
}
</script>
</head>
</html>
Wouldn't it be simplest to just send the token with your form post and double check it server side? I know you're still possibly allowing a bot to post data into your system, but a bot that can sneak by google should be pretty rare. And the first thing your sever side logic should do is verify the token, which can't easily be faked. That said in my initial analysis of google V3(10K requests) the bot detection was solidly binary, in that all the scores were above or below .5 . Google in their documentation recommends different strategies for how to deal with suspicious traffic based on the scenario.
https://developers.google.com/recaptcha/docs/v3.

From GoogleYolo (Google Smart Lock), what does "Invalid audience" mean?

The error/warning message (written by obfuscated code from https://www.gstatic.com//mss/boq-identity//js/k=boq-identity.IdentityYoloWebModuleset.en.5jYiE0Kzqd0.O/m=yolo_frame_library/rt=j/d=1/rs=AOaEmlHvGDn8UcjjSaG2zLiaoxdbMowW7g to the console, Chrome Version 65.0.3325.162) is, "Invalid audience. Please use the Google Cloud console (https://console.developers.google.com), and create a valid OAuth2 web client."
I was following https://developers.google.com/identity/one-tap/web/get-started -- does anyone know if a single-archive fully-complete example exists anywhere? (I looked.)
My code goes as follows, and I did set https://localhost:8000 and a personal website with https as allowed origins for the client ID in question, which are where I'm testing this.
<html>
<head>
<script src="https://smartlock.google.com/client"></script>
</head>
<body>
<script>
const clientID = "...";
const authUri = "https://accounts.google.com";
window.onGoogleYoloLoad = (googleyolo) =>
{
const retrievePromise = googleyolo.retrieve(
{
supportedAuthMethods: [ authUri ],
supportedIdTokenProviders: [ { uri: authUri, clientId: clientID } ]
});
}
</script>
</body>
</html>
Moreover, I tested similar code except with googleyolo.hint then googleyolo.retrieve and the other way around. Both ways, each time I refreshed the page, it was as if I have never visited before -- and that's whether or not I was logged in to Google in Chrome. So after the hint completes, what needs done to save or store that status?
I was able to find some working examples located here -
https://www.codeproject.com/Articles/579564/JavaScript-oAuth
https://developers.google.com/identity/sign-in/web/sign-in
I setup a test script using the examples from the CodeProject link, and just commented out the pieces I did not need.
<div class="loginDiv">
<div id="googlelogin">Google</div>
</div>
<div style="clear:both;" id="userinfo"></div>
var clientId = '.....';
var scopes = 'https://www.googleapis.com/auth/plus.me';
Enjoy!

Google Analytics - Embed API - Getting an OAuth Error

I am trying to follow the example here to embed some Google Analytics statistics in my web app.
My HTML looks as follows:
<div class="page-header">
<h3 class="text-center">WAND Sessions</h3>
</div>
<br/>
<br/>
<div id="embed-api-auth-container"></div>
<div id="chart-container"></div>
<div id="view-selector-container"></div>
#section Scripts {
<script>
(function(w, d, s, g, js, fs) {
g = w.gapi || (w.gapi = {});
g.analytics = { q: [], ready: function(f) { this.q.push(f); } };
js = d.createElement(s);
fs = d.getElementsByTagName(s)[0];
js.src = 'https://apis.google.com/js/platform.js';
fs.parentNode.insertBefore(js, fs);
js.onload = function() { g.load('analytics'); };
}(window, document, 'script'));
</script>
<script src="~/Scripts/GoogleAnalytics/viewSessions.js"></script>
}
My viewSessions.js file looks like this:
$(document).ready(function () {
gapi.analytics.ready(function () {
/**
* Authorize the user immediately if the user has already granted access.
* If no access has been created, render an authorize button inside the
* element with the ID "embed-api-auth-container".
*/
gapi.analytics.auth.authorize({
container: 'embed-api-auth-container',
clientid: 'xxxxxxxxx'
});
/**
* Create a new ViewSelector instance to be rendered inside of an
* element with the id "view-selector-container".
*/
var viewSelector = new gapi.analytics.ViewSelector({
container: 'view-selector-container'
});
// Render the view selector to the page.
viewSelector.execute();
/**
* Create a new DataChart instance with the given query parameters
* and Google chart options. It will be rendered inside an element
* with the id "chart-container".
*/
var dataChart = new gapi.analytics.googleCharts.DataChart({
query: {
metrics: 'ga:sessions',
dimensions: 'ga:date',
'start-date': '30daysAgo',
'end-date': 'yesterday'
},
chart: {
container: 'chart-container',
type: 'LINE',
options: {
width: '100%'
}
}
});
/**
* Render the dataChart on the page whenever a new view is selected.
*/
viewSelector.on('change', function (ids) {
dataChart.set({ query: { ids: ids } }).execute();
});
});
});
When I load my page, all I see is this:
If I click on this, I get a Google error dialog saying
401 Error: invalid_client
The OAuth client was not found.
Any idea what's wrong?
The documentation on that page is not very good. I have mentioned this to Google in the past.
You need to go to Google Developers console and create your own client id using an OAuth credentials.
The Google Analytics Embeded API getting started page tells you how to do that.
Create a New Client ID
The Embed API handles almost all of the authorization process for you
by providing a one-click sign-in component that uses the familiar
OAuth 2.0 flow. In order to get this button working on your page
you'll need a client ID.
If you've never created a client ID, you can do so by following these
instructions.
As you go through the instructions, it's important that you not
overlook these two critical steps: • Enable the Analytics API • Set the
correct origins
The origins control what domains are allowed to use this code to
authorize users. This prevents other people from copying your code and
running it on their site.
For example, if your website is hosted on the domain example.com, make
sure to set the following origin for your client ID
http://example.com. If you want to test your code locally, you'll need
to add the origin for your local server as well, for example:
http://localhost:8080.

Resources