Apps Script webapp: how to authenticate bigquery? - authentication

I am trying to get my apps script webapp to execute as "user accessing the webapp", but its bigquery should run as me, the developer. (If I run the webapp as me, everything works...) I looked at the documentation at https://developers.google.com/bigquery/docs/authorization. There is no apps script example, so I tried to get the javascript example working.
<html>
<head>
<script src="https://apis.google.com/js/client.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script>
function auth() {
gapi.auth.authorize(config, function() {
gapi.client.load('bigquery', 'v2');
$('#client_initiated').html('BigQuery client authorized');
$('#auth_button').fadeOut();
$('#dataset_button').fadeIn();
});
}
// User Submitted Variables
var projectNumber = 'XXXXXXXXXX';
var clientId = 'XXXXXXXXXX.apps.googleusercontent.com';
var config = {
'client_id': clientId,
'scope': 'https://www.googleapis.com/auth/bigquery'
};
function listDatasets() {
var request = gapi.client.bigquery.datasets.list({
'projectId':projectNumber
});
request.execute(function(response) {
$('#result_box').html(JSON.stringify(response.result.datasets, null));
});
}
</script>
</head>
<body>
<button id="auth_button" onclick="auth();">Authorize</button>
<div id="client_initiated"></div>
<button id="dataset_button" style="display:none;" onclick="listDatasets();">Show datasets</button>
<div id="result_box"></div>
</body>
</html>
I generated a client id as a browser app with https://script.google.com as the server address. With the code above, I get this error: Cannot read property 'authorize_m___' of undefined.
My question is twofold: 1) Would an apps script webapp authenticate in the same way as the javascript app authenticates? I.e. can I use that code as a guide for my apps script?
And 2) any suggestions about how to debug the javascript sample code? Note that I ran this code as an apps script webapp.... That is probably an error....

The answer... or workaround answer is given here: How to pass parameters from one Google-Apps-Script to another and execute?
I can use two stage authentication in place of direct authentication: the user logs in as him/herself, I get the user's name to find their files, then switch to a webapp that uses BigQuery and execs as me, the developer.
A good workaround for advanced services authentication under apps scripts....

Related

Firebase GAS webapp Google popup disappears

I am trying to upgrade my firebase GAS webapp, and previously I had a popup which would log the user in with Google. I'm not sure what I'm doing wrong, but I have upgraded to the new firebase, and am now trying to get the same login working with the new code format.
What is happening, is that the popup comes up and immediately disappears. Can anyone see what I am doing wrong here?
Thanks for you help.
<html lang="en">
<head>
<script src="https://www.gstatic.com/firebasejs/3.1.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/3.1.0/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/3.1.0/firebase-database.js"></script>
<script>
var config = {
apiKey: "",
authDomain: "",
databaseURL: "",
storageBucket: ""
};
firebase.initializeApp(config);
</script>
</head>
<body>
<label id="name">First Name</label>
<script>
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider).then(function(result) {
var user = result.user;
document.getElementById("name").value = user;
}).catch(function(error) {
console.log(error);
});
</script>
</body>
</html>
The problem is solved. I got onto the wonderful people at Firebase, and Dane and I worked through it all till it worked. My code was fine, the issue was in the credentials. If you have the same issue, are all the steps we went through:
Google Configuration
Go to this link google developers console
Go to the credentials option on the left hand side of the screen
Click Browser API Key and copy it
Ensure the API key you copied is the same as the API key in your web app configuration. If it doesn't match, replace the api key in your code with the API key that you copied
Under 'OAuth 2.0 client IDs', click 'Web client (auto created by Google Service)'
Navigate to 'Authorised JavaScript origins' section
Add the full URL you're accessing when you're testing the page, or to be sure, a more general one like https://script.google.com/*
Click on the oAuth web application
For authorized javascript origins please add .firebaseapp.com
For authorized redirect URIs please add .firebaseapp.com/__/auth/handler
Click 'Web client (auto created by Google Service)'
Copy the Client ID and Client Secret
Go to the sign In method page of Firebase Console
Click Google
Click Web SDK configuration
Update the Client ID and Secret with the values you obtained
Click save
Firebase Configuration
Go to this firebase console
Select your project, click on the Auth option on the left hand side of the screen, then select sign in method at the top of the screen
Scroll down to the OAuth redirect domains and see if your custom domain is listed
If not, click Add domain, input your custom domain and click Add
Open Google Chrome and go to the page where your app is deployed.
Right click the page and select 'Inspect'
You will see a Console tab and a down arrow (looks like an inverted triangle). - Click the down arrow and select 'userHtmlFrame (...)'
In the terminal (blue arrow pointing to the right), paste 'window.location.hostname' and click enter
A string will be displayed. Copy it and add it to the authorized domains from your Firebase console (Auth > Sign-in Method> OAuth redirect domains).
I got the same problem , it worked fine while debugging in localhost, but when trying to login from "https://www.example.com" the popup disappear imediately, I fixed it by adding "mydomain.com" in firebase console -> Authentication ->SIGN-IN METHOD tab: scroll down to "OAuth redirect domains"
and click "ADD DOMAIN"
Here is the bare minimum I found needed to get client side auth working in Apps Script. I created a new firebase project. I enabled Google sign-in provider. I added the webapp's origin to the OAuth redirect domain list. I get this by launching the webapp and looking in dev tools inspector. Under the elements tab it will be the src of the top level iframe. It will look something like: n-rn4a4ioahvqauYhvzh2nktp3vxnuap6y7htmacq-0lu-script.googleusercontent.com
code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile('index')
}
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
</body>
<script src="https://www.gstatic.com/firebasejs/3.2.0/firebase.js"></script>
<script>
// Initialize Firebase
var config = {
apiKey: "AIzaSyADzG0boXmVUJb07K5NbCC0c6SrP8x3Msk",
authDomain: "testclientauth-57c09.firebaseapp.com",
databaseURL: "https://testclientauth-57c09.firebaseio.com",
storageBucket: "testclientauth-57c09.appspot.com",
};
firebase.initializeApp(config);
</script>
<script>
var provider = new firebase.auth.GoogleAuthProvider();
provider.addScope('https://www.googleapis.com/auth/plus.login');
firebase.auth().signInWithPopup(provider).then(function(result) {
var token = result.credential.accessToken;
var user = result.user;
console.log(user)
}).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
var email = error.email;
var credential = error.credential;
});
</script>
</html>
I was just struggling, try to debug around the code and find if there is any wrong in javascript but could find nothing. Then base on answer from Kat, I thought it could not be that complicated, and it might come from some configuration mistakes in firebase website. So go into firebase configuration for your app, in the tab Authentication, there is Sign-in method and there it is, all log-in methods to my app was disabled.
I enabled google authentication and boom, it works as expected. It is also amazed how easy it is for setup authentication system for my app using firebase. Thanks guys from firebase :-)

Sample Code for Google Translate API

I want to use the Google Translate API in eclipse and I've checked the Google API website, but there isn't any sample code for the Google Translate API in Java. Can someone provide some and explain how to get started. I have already read the documentation on this API.
https://cloud.google.com/translate/v2/quickstart#JSONP
<html>
<head>
<title>Translate API Example</title>
</head>
<body>
<div id="sourceText">Hello world</div>
<div id="translation"></div>
<script>
function translateText(response) {
document.getElementById("translation").innerHTML += "<br>" + response.data.translations[0].translatedText;
}
</script>
<script>
var newScript = document.createElement('script');
newScript.type = 'text/javascript';
var sourceText = escape(document.getElementById("sourceText").innerHTML);
// WARNING: Your API key will be visible in the page source.
// To prevent misuse, restrict your key to designated domains or use a
// proxy to hide your key.
var source = 'https://www.googleapis.com/language/translate/v2?key=YOUR_API_KEY&source=en&target=de&callback=translateText&q=' + sourceText;
newScript.src = source;
document.getElementsByTagName('head')[0].appendChild(newScript);
</script>
</body>
</html>
The Google Client Libraries are now the recommended APIs to use. You can now find Java specific Translation API Client Library examples in the documentation.
To report any issues with the new Java specific Client Libraries, you can directly file an issue on the GitHub Issue tracker.
you can installe this package for laravel
https://packagist.org/packages/charef/free-translate-api

Google SignIn State

I'm trying to build a Google signin button into my website. I'm trying to avoid using their built-in button. The code below works to sign in a user, but I can't figure out how to make my webpage remember that they're signed in when the user refreshes the page, or leaves the site and comes back.
Using Chrome's developer tools, I can see that there's an entry for https://accounts.google.com under both Local Storage and Session Storage. They seem to more or less contain the same information, including the user's validated token.
What I don't understand is how to get the gapi.auth2.init() function to recognize and use this token. The documentation doesn't seem to cover it.
<html>
<head>
<title>Login Test</title>
<script src="http://code.jquery.com/jquery-2.1.4.js"></script>
<script src="https://apis.google.com/js/platform.js?onload=renderButton" async defer></script>
</head>
<script>
var googleUser = {};
function renderButton() {
gapi.load('auth2', function(){
auth2 = gapi.auth2.init({
client_id: 'MY_CREDENTIALS.apps.googleusercontent.com',
});
attachSignin(document.getElementById('customBtn'));
});
};
function attachSignin(element) {
auth2.attachClickHandler(element, {},
function(googleUser) {
document.getElementById('name').innerText = "Signed in: " +
googleUser.getBasicProfile().getName();
}, function(error) {
alert(JSON.stringify(error, undefined, 2));
}
);
}
</script>
<body>
<div id="gSignInWrapper">
<span class="label">Sign in with:</span>
<input type="button" id="customBtn" value="Google"></input>
</div>
<p id="name"></p>
</body>
</html>
You can use listeners. This is the relevant part:
// Listen for sign-in state changes.
auth2.isSignedIn.listen(signinChanged);
// Listen for changes to current user.
auth2.currentUser.listen(userChanged);
You can also get up to date values by
var isSignedIn = auth2.isSignedIn.get();
var currentUser = auth2.currentUser.get();
To strictly detect returning users only you can do:
var auth2 = gapi.auth2.init(CONFIG);
auth2.then(function() {
// at this point initial authentication is done.
var currentUser = auth2.currentUser.get();
});
When it comes to your code I would do:
auth2 = gapi.auth2.init(CONFIG);
auth2.currentUser.listen(onUserChange);
auth2.attachClickHandler(element, {});
This way all changes in sign-in state are passed to onUserChange (this includes returning users, new sign-ins from attachClickHandler, new sign-ins from different tab).

Google-plus sign in: the code runs twice, user was logged out right after he logs in

this is a page built on example
<html>
<head>
<title>Demo: Getting an email address using the Google+ Sign-in button</title>
<style type="text/css">
html, body { margin: 0; padding: 0; }
.hide { display: none;}
.show { display: block;}
</style>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" ></script>
<!--<script src="https://apis.google.com/js/plusone.js" type="text/javascript"></script>-->
<script src="https://apis.google.com/js/client:plusone.js" type="text/javascript"></script>
<script type="text/javascript">
/*
* Triggered when the user accepts the sign in, cancels, or closes the
* authorization dialog.
*/
function loginFinishedCallback(authResult) {
if (authResult) {
console.log('authResult : ',authResult);
if (authResult['error'] == undefined){
gapi.auth.setToken(authResult); // Store the returned token.
toggleElement('signin-button'); // Hide the sign-in button after successfully signing in the user.
getEmail(); // Trigger request to get the email address.
} else {
console.log('An error occurred');
}
} else {
console.log('Empty authResult'); // Something went wrong
}
}
/*
* Initiates the request to the userinfo endpoint to get the user's email
* address. This function relies on the gapi.auth.setToken containing a valid
* OAuth access token.
*
* When the request completes, the getEmailCallback is triggered and passed
* the result of the request.
*/
function getEmail(){
// Load the oauth2 libraries to enable the userinfo methods.
gapi.client.load('oauth2', 'v2', function() {
var request = gapi.client.oauth2.userinfo.get();
request.execute(getEmailCallback);
});
}
function getEmailCallback(obj){
var el = document.getElementById('email');
var email = '';
console.log("OBJ = ",obj)
if (obj['email']) {
email = 'Email: ' + obj['email'];
}
//console.log(obj); // Uncomment to inspect the full object.
el.innerHTML = email;
toggleElement('email');
}
function toggleElement(id) {
var el = document.getElementById(id);
if (el.getAttribute('class') == 'hide') {
el.setAttribute('class', 'show');
} else {
el.setAttribute('class', 'hide');
}
}
</script>
</head>
<body>
<div id="signin-button" class="show">
<div class="g-signin" data-callback="loginFinishedCallback"
data-approvalprompt="auto"
data-clientId="751931329576.apps.googleusercontent.com"
data-scope="https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/userinfo.email"
data-height="short"
data-cookiepolicy="http://semicon-equip.com"
>
</div>
<!-- In most cases, you don't want to use approvalprompt=force. Specified
here to facilitate the demo.-->
</div>
<div id="email" class="hide"></div>
</body>
</html>
Question 1: It always fails with "Uncaught TypeError: Cannot read property 'load' of undefined",
until I use
<script src="https://apis.google.com/js/client:plusone.js" type="text/javascript"></script>
instead of the example code:
<script src="https://apis.google.com/js/plusone.js" type="text/javascript"></script>
What's the difference between plusone.js and client:plusone.js ?
Question 2: Why the code run twice per page loads ?
Qestion 3: the user was logged out after he just signed in, how to fix ?
error demo page for the above (all the errors are in the background console).
This is not really an answer to the question, but a step by step procedure to reproduce it.
Below the simple html page I'm using to test (similar to the example from Ray C Lin).
I've made it as simple as possible to avoid interactions with other part of the code :
<html>
<head>
<script type="text/javascript"
src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
</head>
<body>
<input type="button" id="signOut" value="Sign out"></button>
<span id="signinButton">
<span class="g-signin"
data-accesstype="offline"
data-callback="signinCallback"
data-clientid="YOUR_CLIENT_ID_HERE"
data-cookiepolicy="single_host_origin"
data-scope="email"
</span>
</span>
<script type="text/javascript">
$('#signOut').on('click', function() {
gapi.auth.signOut();
});
function signinCallback(authResult) {
console.log("signinCallback: ", authResult);
}
(function() {
var po = document.createElement('script');
po.type = 'text/javascript';
po.async = true;
po.src = 'https://apis.google.com/js/client:plusone.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(po, s);
})();
</script>
</body>
</html>
You will have to update data-clientid with your own google client id, and display this page from an authorized javascript origin.
Please note that this might not work from localhost, as Ian suggested in a comment to this post https://plus.google.com/102746521318753162868/posts/Z5Gkro9YXVs
First, sign in using your Google account : you will see a successful callback in the console.
If you click on Sign out, you will see a callback with 'user_signed_out' in the console.
So far so good.
Sign-in again, and wait 1 hour, until the token expires (this is awful to test, as I don't know how to reduced the token lifetime).
After one hour, click on the sign out button : no callback is called.
Click on the sign-in button again :
you get a successfull callback with an authorization code and access token
immediately after, you get a 'user_signed_out' callback.
Once a session has expired, there is no way to return to a "normal" situation, you always get this second callback with 'user_signed_out'.
Actually, there is one way to return to a "normal" situation: revoke the access to the app from the google dashboard.
This is not really an issue for me as I'm using Google+ only to sign-in the user to my app using the one time authorization code, and I'm not using the access token from the client.
But this prevent automatic login from working, as the user is immediately considered as "signed out" from a google perspective.
Q1: client:plusone.js is just telling the loader to automatically load the "client" module. It's basically automatically doing gapi.load("client"), except it is already packaged up for you in one download. You could do it the other way around as well, plusone:client.js!
Q2: Not sure, it may be some JS quirk. In general, try and make your code able to handle multiple callbacks, you may get another if the state changes (e.g. the user logs out of their google account).
Q3: I don't see that on the test page - I am signed OK, and still signed in on refresh! Check you're not blocking third party cookies in your browser or similar?

AngularJS authentication - Incompatibility between directives?

I'm really new on Angular JS development and I'm trying to implement the following authentication module : https://github.com/witoldsz/angular-http-auth (more info at http://www.espeo.pl/2012/02/26/authentication-in-angularjs-application), to my project .
THE MODULE
The module has been thought to allow the following scenario :
user asks for: something.com/secured/formXyz,
server sends a login form,
user logs in, fills a long and complicated form, but they are doing it so long that theirs session expires,
user submits a form, but since the session is not valid anymore, login screen appears,
once user logs in, server can process the submitted form, **no need to re-enter everything again**.
The solution to do such a thing is :
server side behavior :
for every /resources/* call, if user is not authorized, response a 401 status. Otherwise, when user is authorized or when not a /resources/* request, send what client asked for.
client side behavior :
capture 401 response,
save the request parameters, so in the future we can reconstruct original request,
create and return new object representing server’s future answer (instead of returning the original failed response),
broadcast that login is required, so application can react, in particular login form can appear,
listen to login successful events, so we can gather all the saved request parameters, resend them again and trigger all the ‘future’ objects (returned previously).
MY PROJECT
My project is a basic one, which uses the $route service. When I try to add the directive which catch the events :
scripts/directives/login-directive.js
angular.module('myApp', ['http-auth-interceptor','content-mocks'])
/**
* This directive will find itself inside HTML as a class,
* It is responsible for showing/hiding login form.
*/
.directive('authDirective', function() {
return {
restrict: 'C',
link: function(scope, elem, attrs) {
var login = elem.find('#login');
var main = elem.find('#main');
login.hide();
main.show();
scope.$on('event:auth-loginRequired', function() {
login.show();
main.hide();
});
scope.$on('event:auth-loginConfirmed', function() {
main.show();
login.hide();
});
}
}
});
to my index.html :
<body ng-app='myApp' class='auth-directive'>
<!--[if lt IE 7]>
<p class="chromeframe">You are using an outdated browser. Upgrade your browser today or install Google Chrome Frame to better experience this site.</p>
<![endif]-->
<!--[if lt IE 9]>
<script src="components/es5-shim/es5-shim.js"></script>
<script src="components/json3/lib/json3.min.js"></script>
<![endif]-->
<div id="login" name="login">
<p>login</p>
</div>
<div id="main" name="main">
<p>main</p>
<div class="container" ng-view></div>
</div>
<script src="components/angular/angular.js"></script>
<script src="components/angular-resource/angular-resource.js"></script>
<script src="components/angular-cookies/angular-cookies.js"></script>
<script src="components/angular-sanitize/angular-sanitize.js"></script>
<script src="components/angular-http-auth/angular-mocks-1.0.1.js" type="text/javascript"></script>
<script src="components/angular-http-auth/http-auth-interceptor.js" type="text/javascript"></script>
<script src="scripts/app.js"></script>
<script src="scripts/controllers/main.js"></script>
<script src="scripts/directives/login-directive.js"></script>
<script src="scripts/mocks/content-mocks.js" type="text/javascript"></script>
</body>
The directive ngView doesn't work anymore. Nothing appears on console log. Is there any incompatibility between the two directives ?
By the look of your login-directive files it seems that you are (improperly) reinitialising you Angular app by passing the dependencies array as a second parameter to your angular.module call:
angular.module('myApp', ['http-auth-interceptor','content-mocks'])
Dependencies should be defines only once, when app is initialised (probably in app.js), and when you want to reference your application in another file you should call the angular.module without the second parameter:
angular.module('myApp')
.directive('authDirective', function() { ...