dintorf / gdata-javascript-client

Automatically exported from code.google.com/p/gdata-javascript-client
0 stars 0 forks source link

JS AuthSub Broken #42

Open GoogleCodeExporter opened 8 years ago

GoogleCodeExporter commented 8 years ago
What steps will reproduce the problem?
1. 
http://gdata-javascript-client.googlecode.com/svn/trunk/samples/core/authsubjs/i
ndex.html
2. Check the browsers cookies - remains pending forever - cookie is not 
successfully created.

Expected output is a cookie that contains authentication data.

Tested on IE 8, Chrome, FF, Safari

As per the documentation here 
(http://code.google.com/apis/gdata/docs/js-authsub.html):

...The client library stores the token as a cookie in the user's browser, under 
the web application's domain...

This is not happening resulting in the browser not knowing its logged in.

Original issue reported on code.google.com by kev...@ca.com.au on 9 Feb 2012 at 7:25

GoogleCodeExporter commented 8 years ago
I can also repeat this error.  I am seeing this failure on 100% of the example 
code from google that uses javascript api. I am also seeing this failure on 
both Mac and PC and Firefox Safari web browser.

<http://gdata-javascript-client.googlecode.com/svn/trunk/samples/>

What is happening is that when you click on the Grant Access page and grant 
access, the temporary cookie
g314-pending
is added to my domain.  When google returns to my page, that cookie is not 
deleted.  I look and see that the permission has been granted at the google 
end, however within the javascript code, it believes that authentication (the 
final leg of the 3 leg process) was not completed.  This causes the whole thing 
to go back again and loop again - asking you to grant permission one more time.

Original comment by SSchmit...@gmail.com on 9 Feb 2012 at 5:35

GoogleCodeExporter commented 8 years ago
I think this is only happen recently. My code was working last week in FF and 
Chrome. But this week it's failing in all browsers. I am also only seeing the 
g314-pending cookies this week, and also no token returned from 
google.accounts.user.checkLogin() after clicking the grant access button on the 
Grant Access page process.

Original comment by jli...@gmail.com on 10 Feb 2012 at 2:55

GoogleCodeExporter commented 8 years ago
Same issue is happening for me from yesterday. Let me know if anyone found the 
solution. Thanks. 

Original comment by jaksonl...@gmail.com on 10 Feb 2012 at 4:24

GoogleCodeExporter commented 8 years ago
I've been doing some testing trying to get this to work and it seems like it is 
not only a problem with the cookie not being created, but also a problem with 
the session token being retrieved.

These are the steps I took to confirm:

1. Try to grab a token using your implemention that use to work 
(http://gdata-javascript-client.googlecode.com/svn/trunk/samples/core/authsubjs/
index.html).

2. Sign in and allow the browser to redirect back to your implementation. It 
will fail to create the cookie, but will pass back the token which will be 
visible in the returned URL. URI decode it and use it in step 3.

3. Use openssl s_client or similar to make the following request:
openssl s_client -connect www.google.com:443

GET /accounts/AuthSubSessionToken HTTP/1.1
Host: www.google.com
Authorization: AuthSub token="TOKEN_HERE"

HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
Date: Sat, 11 Feb 2012 00:51:57 GMT
Expires: Sat, 11 Feb 2012 00:51:57 GMT
Cache-Control: private, max-age=0
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Length: 52
Server: GSE

Token=SESSION_TOKEN_HERE

4. Grab the SESSION_TOKEN_HERE value and use to create a cookie in the browser 
(eg - document.cookie = 'g314-scope-0=scope=' + encodeURIComponent(YOUR_SCOPE) 
+ '&token=' + encodeURIComponent(SESSION_TOKEN_HERE);)

5. Try out your implementation again - now that you have the cookie stored in 
the browser it should work.

Original comment by kev...@ca.com.au on 11 Feb 2012 at 1:07

GoogleCodeExporter commented 8 years ago
I am wondering - would it be possible to write a little snippet of php which 
would take the temporary token (passed as a parameter) and generate the session 
token cookie in return?

If that is the case, we might be able to patch things together to get things 
working while we are waiting for Google to fix their implementation.

Original comment by administ...@testrealtyjuggler.com on 11 Feb 2012 at 1:16

GoogleCodeExporter commented 8 years ago
I believe the problem manifested Tuesday evening (Pacific time) as that is when 
I first got report from customers that this was broken.
I wish Google could just back-out whatever "update" they made.
Any sort of work around would be appreciated.

Original comment by qle...@gmail.com on 11 Feb 2012 at 1:31

GoogleCodeExporter commented 8 years ago
Yes it would be possible. Just have the calling script check the URL for the 
token and if it exists then action the call above to grab the session token, 
then use JavaScript to create the cookie. I think this is the approach I'm 
going to take until they fix it, but I am using Perl, not PHP.

Original comment by kev...@ca.com.au on 11 Feb 2012 at 5:03

GoogleCodeExporter commented 8 years ago
I would love to see your code once you get it working.  I am not familiar with 
openssl.

thanks,

Scott.

Original comment by SSchmit...@gmail.com on 11 Feb 2012 at 6:51

GoogleCodeExporter commented 8 years ago
I have just finished and have it all working again.

Don't get too caught up with openssl - all I was using it for was to show the 
output of a HTTPS request.

Not sure what language you're programming in, but I have achieved what I needed 
to in Perl by using the LWP::UserAgent and HTTP::Request::Common CPAN modules.

In short:
- I direct the user to the authentication page, which redirects to my calling 
page.
- I grab the token from the URL (JavaScript - document.location) then make an 
AJAX call to a separate script, sending it this token.
- This script makes a HTTPS request to 
https://www.google.com/accounts/AuthSubSessionToken with the header 
'Authorization: AuthSub token="TOKEN_HERE"'.
- Request returns the session token, passes back to my original calling script, 
and I use JavaScript to create the cookie.
- Done.

Original comment by kev...@ca.com.au on 11 Feb 2012 at 7:21

GoogleCodeExporter commented 8 years ago
WOW!  Thanks for your excellent information.  OK, I did the same thing for our 
code.  We are using PHP+Javascript.  I am going to attach the PHP code we use 
to do the GET request to trade the temp token for the long-term one.  I'll also 
include some javascript for a function called RepairToken(theScope).  Just call 
that function in the page that Google calls after grant access has happened.  
This will call that PHP code using AJAX and fix up the bad cookie and get 
things going again.  I have tested for both Google Apps and plain old Gmail and 
it is working for both!

Scott.

Original comment by SSchmit...@gmail.com on 11 Feb 2012 at 9:13

Attachments:

GoogleCodeExporter commented 8 years ago
No problem Scott.

So when the developers finally get around to looking at this it seems that the 
problem lies within the google.accounts.user.login() function only. I would be 
interested to see what the problem actually is once its resolved - pretty 
disappointing the lack of care shown when making a change to log in functions.

Original comment by kev...@ca.com.au on 12 Feb 2012 at 2:14

GoogleCodeExporter commented 8 years ago
One of the problems seems to be that google.accounts.user.Va.TOKEN_DELIMITER is 
set to "#2" even though the token in the URL starts with "#1". I tried setting 
it to "#1" and that at least seemed to trigger some auth mechanism, but returns 
an "Could not sign in, invalid response from server. Please try again." error 
in the console.

Maybe this helps someone to create some JS only workaround?

I've got the feeling Google no longer cares for JavaScript in their GData API.

Original comment by splitbr...@gmail.com on 13 Feb 2012 at 8:30

GoogleCodeExporter commented 8 years ago
@ SSchmit -  much thanks for these snippets, although GetCookie is undefined? 
Never had to deal with cookies in such a way and I am a bit cloudy with a cold 
so I am probably missing something right in front of me.

Also within my app, the post and pre-auth pages are the same, guess I can 
figure something via window.location :)

Thanks

I don't know why google would change something that was working just fine, has 
anyone been able to find the changelog?

Original comment by fruitwe...@gmail.com on 13 Feb 2012 at 11:23

GoogleCodeExporter commented 8 years ago
Change log is here - 
http://code.google.com/p/gdata-javascript-client/source/list but nothing around 
the time it broke.

See here for retrieving cookies in JS - 
http://www.w3schools.com/js/js_cookies.asp

Original comment by kev...@ca.com.au on 13 Feb 2012 at 11:31

GoogleCodeExporter commented 8 years ago
Sorry guys, getting ahead cold or something 'theScope' is? In my context I am 
pulling contact feeds. I can supply some of my coded if needed. My app is ExtJS 
based and I don't recall exactly how it all works anymore :)

Original comment by fruitwe...@gmail.com on 13 Feb 2012 at 11:56

GoogleCodeExporter commented 8 years ago
Here's the GetCookie code:

function GetCookie(cookieName) {
    var theName = cookieName + "=";
    var nameLength = theName.length;
    var theDocument = this.document;
    var theCookies = theDocument.cookie;
    var startPos = theCookies.indexOf(theName);
    if (startPos < 0) return null;
    var endValue = theCookies.indexOf(";", startPos + nameLength);
    if (endValue == -1) endValue = theCookies.length + 1;
    var returnData = unescape(theCookies.substring(startPos + nameLength, endValue));
    return returnData;
};

Original comment by administ...@testrealtyjuggler.com on 14 Feb 2012 at 12:29

GoogleCodeExporter commented 8 years ago
The initial work done with GooglePatch.php is a valiant effort. However, for 
those of us who intentionally did not write our solutions using anything other 
than JavaScript and HTML, it is an unwelcome elixir. No disrespect toward 
SSchmit. You have given us hope.

However, we need something that stays with just JavaScript. Please continue 
your efforts.

In the meantime I am posting what used to be a working code block. If you know 
how to use RepairToken to fix it I would greatly appreciate the insight. My 
pre-auth and post-auth page are also the same.

/**
 * This is called once the Google Data JavaScript library has been loaded.
 * It creates a new AnalyticsService object, adds a click handler to the
 * authentication button and updates the button text depending on the status.
 */
function initBIFAnalytics() {
    analyticsService = new google.gdata.analytics.AnalyticsService('BIF Analytics for GA (Web)');
    // setScope('https://www.google.com/analytics/feeds');
    setScope('https://www.googleapis.com/auth/analytics.readonly');
    var buttonAuthentication = document.getElementById('buttonAuthentication');

    /* Add a click handler to the Authentication button. */
    buttonAuthentication.onclick = function() {
        /* Test if the user is not authenticated. */
        if (!google.accounts.user.checkLogin(getScope())) {     
            /* Authenticate the user. */
            google.accounts.user.login(getScope());
        } else {
            /* Log the user out. */
            google.accounts.user.logout();
        }
    }
    getStatus();
}

/**
 * Utility method to display the user controls if the user is 
 * logged in. If user is logged in, get Account data and
 * get Report Data buttons are displayed.
 */
function getStatus() {
    //RepairToken(getScope());
    var divDataControls = document.getElementById('divDataControls');
    var buttonAuthentication = document.getElementById('buttonAuthentication');
    if (!google.accounts.user.checkLogin(getScope())) {
        buttonAuthentication.innerHTML = 'Access Google Analytics';
    } else {
        divDataControls.style.display = 'inline';
        buttonAuthentication.innerHTML = 'Logout';
        getAccountFeed();
    }
}

Original comment by roderick...@gmail.com on 14 Feb 2012 at 3:34

GoogleCodeExporter commented 8 years ago
Just had a quick look at the jQuery.ajax() documentation - GooglePatch.php 
should be able to be converted to JavaScript.

See http://api.jquery.com/jQuery.ajax/ function beforeSend(jqXHR, settings)

Original comment by kev...@ca.com.au on 14 Feb 2012 at 3:42

GoogleCodeExporter commented 8 years ago
This is encouraging. If we could get a JavaScript only solution it would get 
many back on track until they can convert everything to version 3. Let me know 
if I can help.

Roderick

Original comment by roderick...@gmail.com on 14 Feb 2012 at 4:42

GoogleCodeExporter commented 8 years ago
I am also looking for JS only solution. But I am having issue with cross
site ajax call. I found this https://github.com/borisreitman/CrossXHR but
it requires a file at the Google authentication site to allow access from
other site.

I tried to use window.location, or open another window for login processes,
but it didn't work out. Appreciate if there's any other suggestion or some
idea on how this can proceed.

Thanks!

P/S : Just wonder if anyone know when this authentication issue would be
fixed?

Original comment by jli...@gmail.com on 14 Feb 2012 at 9:29

GoogleCodeExporter commented 8 years ago
Hi everyone,

I have the same error in my project.
Waiting for a full Javascript solution, I sent a post to 
https://groups.google.com/forum/?fromgroups#!forum/google-data-javascript-client
 group (not yet approved).

Cheers,
Marius

Original comment by mapope...@gmail.com on 14 Feb 2012 at 1:04

GoogleCodeExporter commented 8 years ago
I also tried converting the code to an all JavaScript solution. I tried working 
with the temp token that comes back in the URL in a modified version of 
RepairToken. It the PHP code could not parse the token that was returned. 
However, if I understand things correctly this is what is needed:

1) invoke the authentication process by calling the google authentication 
services. This can be done with google.accounts.user.login(getScope());
2) google will provide a page for the user asking if access to their account 
(analytics in my case) data is to be granted. if a grant happens the temporary 
token (stringTokenTemp) will be added to the URL of the "next" page.
3) in my case the "next" page is the same as the "pre-auth" page. when the 
pre-auth page is loaded it should be aware of the fact that the URL contains a 
a temporary token (stringTokenTemp). a primitive check against a prior token 
would suffice as a detection method.
4) instead of calling out to PHP (glorious solution that I really respect but a 
departure from the all JavaScript direction) we could use a REST-based approach 
to call the authentication server with a callback handler for dealing with the 
response.
5) in the callback handler the received token should replace the temporary 
token. This is the point at which a cookie should be written. 

I am willing to construct a version of this if we can get a few eyes to look at 
it.

Roderick

Original comment by roderick...@gmail.com on 14 Feb 2012 at 3:10

GoogleCodeExporter commented 8 years ago
I also want to keep it on client side, gave it a try, but didn't get too far... 

<html>
    <head>
        <script src="jquery.js" type="text/javascript"></script>
        <script type="text/javascript" src="http://www.google.com/jsapi"></script>
    </head>
    <body>
        <script type="text/javascript">

            var g_token = null;

            function getToken() {
                var auth = /#1(.*)/.exec(window.location.href);
                $.ajax({
                    url: "https://www.google.com/accounts/AuthSubSessionToken",
                    type: "GET",
                    headers: {     
                        "Host": "www.google.com",
                        "Content-Type": "application/x-www-form-urlencoded",
                        "Authorization": "AuthSub token=\""+auth[1]+"\"",
                        "User-Agent": "Java/1.5.0_06",
                        "Accept": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2",
                        "Connection": "keep-alive"
                    },
                    success: function(data) {
                        alert('Data Loaded: ' + data);
                    },
                    complete: function (jx, text) {
                        alert(text);
                    },
                    type: 'GET',
                    //dataType: "jsonp",
                    crossDomain: true
                });
            }

            function login(){
                var scope = "http://www.google.com/calendar/feeds";
                g_token = google.accounts.user.checkLogin(scope);
                if (g_token > "") {
                    alert(g_token);
                }
                else {
                    if (window.location.hash.length > 2) {
                        getToken();
                    }
                    else {
                        google.accounts.user.login(scope);
                    }
                }
            }

            google.load('gdata', '2.x');
            google.setOnLoadCallback(login);
        </script>
    </body>
</html>

The above code fails with:

Refused to set unsafe header "Host"
Refused to set unsafe header "User-Agent"
Refused to set unsafe header "Connection"
OPTIONS https://www.google.com/accounts/AuthSubSessionToken 405 (Method Not 
Allowed)
XMLHttpRequest cannot load https://www.google.com/accounts/AuthSubSessionToken. 
Origin http://localhost is not allowed by Access-Control-Allow-Origin.

The code is primitive, in that it detects if hash contains anything longer than 
two chars, and decides that it has to be a temporary token, but the idea is 
there.

Hope someone can make something out of it, but I doubt that anything other than 
google-side fix will do :)

Original comment by borivoje...@gmail.com on 14 Feb 2012 at 3:37

GoogleCodeExporter commented 8 years ago
I can assist with a js only implementation if someone can explain the 
'theScope' value of the RepairToken function.

Thanks

Original comment by fruitwe...@gmail.com on 14 Feb 2012 at 5:01

GoogleCodeExporter commented 8 years ago
I got Scott's code working (in part by adding a GetCookie function). One thing 
I had to do in the PHP was add a line for the curl to work over SSL:

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

I added that above the curl_exec line

Original comment by cgdwebst...@gmail.com on 14 Feb 2012 at 6:21

GoogleCodeExporter commented 8 years ago
I also added the code for handling windows.location.hash results that are null. 
I added the code to RepairToken. I will let you know how this turns out. I have 
incorporated your change in the PHP file as well. However, I think that a 
callback is needed to handle the call to the PHP properly. Otherwise the 
calling JavaScript goes on as if things are fixed when they are actually in 
process.

That is, the AJAX call to the GooglePatch.php should be done with the 
expectation that results are not immediate. I am looking into this.

NOTE: I am not a PHP developer and do not mind being corrected. 

Original comment by roderick...@gmail.com on 14 Feb 2012 at 8:10

GoogleCodeExporter commented 8 years ago
it worked! it worked! It is flaky but it works. I am now packaging up my poor 
coding to pass it on to others in hope that it helps.

Original comment by roderick...@gmail.com on 14 Feb 2012 at 8:34

GoogleCodeExporter commented 8 years ago
Hi all,

I just saw the samples work again (e.g. 
http://gdata-javascript-client.googlecode.com/svn/trunk/samples/calendar/meeting
_notes/meeting_notes.html).
I also tried my small app and it also works, so it seems to have been fixed!

--
Marius

Original comment by mapope...@gmail.com on 15 Feb 2012 at 7:54

GoogleCodeExporter commented 8 years ago
[deleted comment]
GoogleCodeExporter commented 8 years ago
Hi Roderick, when you mentioned it worked, do you mean your pure JS solution?

I am still seeing the issue with Safari. Chrome and FF seemed work fine now. I 
am putting IE as last of my priority here.

Original comment by jli...@gmail.com on 16 Feb 2012 at 7:12

GoogleCodeExporter commented 8 years ago
I mean that it seems to be fixed. Did my code changes fix it or was it fixed by 
Google? Yes. I think that my changes are redundant but that it also addressed 
the issue that arose out of the Google code change. Here below are my sorry 
attempts to save my application. My solution is a modification of changes 
mentioned earlier. I pass on my version of RepairToken (including GetCookie).

// Bug in Googe Javascript API Feb 11, 2012.  The temp token is not exchanged 
for the long-term one
// correctly.  So, we do that little dance and save the cookie as needed.
function RepairToken(theScope) {
    var cookieName = 'g314-scope-0=scope';

    function GetCookie(cookieName) {
        var theName = cookieName + "=";
        var nameLength = theName.length;
        var theDocument = this.document;
        var theCookies = theDocument.cookie;
        var startPos = theCookies.indexOf(theName);
        if (startPos < 0) return null;

        var endValue = theCookies.indexOf(";", startPos + nameLength);
        if (endValue == -1) endValue = theCookies.length + 1;
        var returnData = unescape(theCookies.substring(startPos + nameLength, endValue));
        return returnData;
    };

    function SaveRepairCookie() {
        var today = new Date();
        var expires_date = new Date(today.getTime() + (365 * 4 * 1000 * 60 * 60 * 24)); // 4 years
        var cookieData = encodeURIComponent(theScope) + '&token=' + encodeURIComponent(theToken) + ";expires=" + expires_date.toGMTString();
        window.document.cookie = cookieName + '=' + cookieData;
        booleanRepairComplete = true;
    };

    function dataFetchedFunc() {
        if ((oxmlhttp.readyState == this.DONE) && (oxmlhttp.status == 200) && (oxmlhttp.responseText != null)) {
            theToken = '';
            var backText = oxmlhttp.responseText || '';
            var searchStr = 'Token='
            var tokenPos = backText.indexOf(searchStr);
            if (tokenPos >= 0) {
                theToken = backText.substring(tokenPos + searchStr.length);
                theToken = theToken.replace(/^\s+|\s+$/g, ""); // trim leading and trailing newlines and spaces
            }
            if (theToken) {
                SaveRepairCookie();
            }
        } else {
        }
    };

    // declare a variable for working with the temporary token
    var tempToken = '';

    // get everything after the hash symbol in the URL including the hash. 
    // if the url is roderickbarnes.com/blog/bif-analytics#testing this function will return #testing
    var theHash = ''; window.location.hash;
    if (window.location.hash.length > 2) {
        theHash = window.location.hash;
        tempToken = (theHash.charAt(0) == '#') ? theHash.substring(1) : theHash; // get rid of # part
    } else {
        google.accounts.user.login(theScope);
        return;
    }

    var oldCookie = GetCookie(cookieName);
    if (oldCookie) {
        return; // no problems
    } else {
    }
    var oxmlhttp = null;
    try {
        oxmlhttp = new XMLHttpRequest();
        if (oxmlhttp) {
            oxmlhttp.onreadystatechange = dataFetchedFunc;
            var params = "scope=" + encodeURIComponent(theScope) + '&tempToken=' + encodeURIComponent(tempToken);
            oxmlhttp.open("GET", "http://roderickbarnes.com/code/GooglePatch.php?" + params, false);
            oxmlhttp.send(null);
            //window.location = "http://roderickbarnes.com/code/GooglePatch.php?" + params;
        }
    } catch (e) {
    }
};

/**
 * This is called once the Google Data JavaScript library has been loaded.
 * It creates a new AnalyticsService object, adds a click handler to the
 * authentication button and updates the button text depending on the status.
 */
function initBIFAnalytics() {
    analyticsService = new google.gdata.analytics.AnalyticsService('BIF Analytics for GA (Web)');
    setScope('https://www.google.com/analytics/feeds');
    var buttonAuthentication = document.getElementById('buttonAuthentication');

    /* Add a click handler to the Authentication button. */
    buttonAuthentication.onclick = function() {
        /* Test if the user is not authenticated. */
        if (!google.accounts.user.checkLogin(getScope())) {     
            /* Authenticate the user. */
            google.accounts.user.login(getScope());
        } else {
            /* Log the user out. */
            google.accounts.user.logout();
        }
    }
    getStatus();
}

/**
 * Utility method to display the user controls if the user is 
 * logged in. If user is logged in, get Account data and
 * get Report Data buttons are displayed.
 */
function getStatus() {
    if (booleanRepairComplete != true) {
        RepairToken(getScope());
    }
    var divDataControls = document.getElementById('divDataControls');
    var buttonAuthentication = document.getElementById('buttonAuthentication');
    if (!google.accounts.user.checkLogin(getScope())) {
        buttonAuthentication.innerHTML = 'Access Google Analytics';
    } else {
        divDataControls.style.display = 'inline';
        buttonAuthentication.innerHTML = 'Logout';
        getAccountFeed();
    }
}

Original comment by roderick...@gmail.com on 16 Feb 2012 at 8:14

GoogleCodeExporter commented 8 years ago
I do not own a Mac. And I did not want to install Safari just to test. So I 
stopped by a Best Buy. The Safari browser on one of the machines in the store 
was able to leverage the code to successfully authenticate. I hope the code 
snippet helps.

In His grip by His grace,
Roderick
P.S. I did not write the original code and am indebted to the developers who 
kept workin' it. I made modifications and worked to get it assimilated.

Original comment by roderick...@gmail.com on 16 Feb 2012 at 10:41

GoogleCodeExporter commented 8 years ago
Thanks Roderick and all. I'll try this out. Have a great day! :)

Original comment by jli...@gmail.com on 17 Feb 2012 at 1:27