raphaeloliveira / google-api-objectivec-client

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

Google Drive Service does not handle invalid token situation(@"invalid_grant") #16

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
What steps will reproduce the problem?
1.I use the auth vc (touch) to auth and did get the auth and save the 
authentication to keychain
2.On Google->Account->Security->Authorizing applications and sites page to 
revoke the app
3.Using the sdk and the GTMOAuth2Authentication object's canAuthorize method 
does not know it's token has already been revoked. And If I try to use 
GTLDriveService to upload file to Google Drive using the block based API:
 exucuteQuery: completionHandler:
the handler will never be called.

What is the expected output? What do you see instead?
I expect that the completionHandler get called and from the error I can know 
that this is due to the token is not valid. But the handler just don't get 
called at all!(It does get called if the token is valid).

And it does print out "invalid_grant" in the console. I tracked that and found 
this is in GTMOAuth2Authtication 's tokenFetcher: finishedWithData: error 
method 's #if DEBUG block, hope this information will help.

What version of the product are you using? On what operating system?
I'm using the lateset version since the Google Drive Service is just released. 
On Xcode 4.5 and iOS 5.

Please provide any additional information below
The normal call tree and the error call tree is very different and too 
complicated so I can't figure this out myself. Hope you guys can fix this issue.

Original issue reported on code.google.com by snsdev.i...@gmail.com on 11 Jul 2012 at 8:48

GoogleCodeExporter commented 9 years ago
In testing with the DriveSample app, I am not able to reproduce this. The app 
is being called back with a 401 status when trying to upload after the token is 
revoked.

Can you set a breakpoint at GTLService's handleParsedObjectForFetcher: method? 
That is called after the upload attempt with the fetcher responsible for 
uploading the data. If that method is hit, step through that method to see if 
the fetcher contains an error. If it is not hit, try putting a breakpoint at 
GTLService's objectFetcher:finishedWithData:error: method.

Original comment by grobb...@google.com on 12 Jul 2012 at 1:39

GoogleCodeExporter commented 9 years ago
They all not got hit.

I try to do something in the GTMHTTPFetcher's 
invokeFetchCallbacksWithData:error method
In the #if NS_BLOCKS_AVAILABLE block there is
if(completionBlock) {
    completionBlock(data,error);
}

I added:
 void (^block)(id service,id obj,NError *) = [self 
propertyForKey:@"_completionHandler"];
if (block)
{
   block(nil,data,error);
}

And this will make the callback get called.(When the token is invalid)

But if the token is valid, this will make the callback get called twice, so 
this might a bad fix.

By the way, my code is here, hope this will help:
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"22222" 
ofType:@"pdf"];
        NSFileHandle *fileHandle = [NSFileHandle 
fileHandleForReadingAtPath:filePath];
        if (fileHandle) {
            GTLServiceDrive *service = [[GTLServiceDrive alloc] init];
            
            service.authorizer = [GTMOAuth2ViewControllerTouch 
authForGoogleFromKeychainForName:kGoogleAPIKeychainItem 
clientID:kGoogleAPIClientID clientSecret:kGoogleAPIClientSecret];
            NSString *mimeType = @"application/pdf";
            GTLUploadParameters *uploadParameters =
            [GTLUploadParameters uploadParametersWithFileHandle:fileHandle
                                                    
   MIMEType:mimeType];
            GTLDriveFile *newFile = [GTLDriveFile object];

            
            newFile.title = [filePath lastPathComponent];
            
            GTLQueryDrive *query = [GTLQueryDrive 
queryForFilesInsertWithObject:newFile
                                                    
           uploadParameters:uploadParameters];
            [service executeQuery:query 
completionHandler:^(GTLServiceTicket *ticket,id  obj,NSError *error)
             {
                 if (error) {
                     if ( [[error.userInfo objectForKey:@"error"] 
isEqualToString:@"invalid_grant"]) {
                         NSLog(@"token invalid");
                     }
                     NSLog(@"GDoc failed error:%@",error);
                 }
                 else
                 {
                 }
             }];
        }

Original comment by snsdev.i...@gmail.com on 12 Jul 2012 at 1:56

GoogleCodeExporter commented 9 years ago
Your response confuses me. The fetcher's invokeFetchCallbacksWithData:error: is 
called often. When it is called AND its finishedSel_ ivar has the selector 
"objectFetcher:finishedWithData:error:", the data parameter should contain a 
JSON string with the auth error, and the error parameter should be nil.

Please turn on the fetcher's http logging and look at the logs of the upload 
fetches, both for the successful upload and for the failure.

http://code.google.com/p/gtm-http-fetcher/wiki/GTMHTTPFetcherIntroduction#HTTP_L
ogging

Original comment by grobb...@google.com on 12 Jul 2012 at 2:13

GoogleCodeExporter commented 9 years ago
I think it's might be some issue in the error block callback implementation. 
Because when the token is not valid, the method you mentioned like 
objectFetcher:finishedWithData:error: just doesn't get called at all. 

The delegate and selector way may be ok, but the block callback is not. Have 
you tried the block based API?

Original comment by snsdev.i...@gmail.com on 12 Jul 2012 at 3:37

GoogleCodeExporter commented 9 years ago
My test case is the DriveSample app, which does use block callbacks.

But the kind of callback provided by the app is unrelated to the callbacks 
internal to the library. Uploading always uses selector callbacks internally, 
since it works even when compiling without blocks supported.  Only the final 
callback to the app can ever be done with a block. 

You'll need to look at the http logs to determine what the uploading fetches 
are returning from the server.

Original comment by grobb...@google.com on 12 Jul 2012 at 4:25

GoogleCodeExporter commented 9 years ago
Here's the log:
First is the log when the token is valid and upload successed and the callback 
is called.

valid:
2012-07-12 06:11:23 +0000      refresh token for accounts.google.com 
     request/response log
request: POST https://accounts.google.com/o/oauth2/token
   headers: 2 
   data: 175 bytes, application/x-www-form-urlencoded
response:  status 200
   headers: 10 
   data: 679 bytes, application/json 
2012-07-12 06:11:24 +0000      drive.files.insert 
     request/response log
request: POST https://www.googleapis.com/upload/rpc?uploadType=resumable&prett
yPrint=false
   headers: 7    authorized
   data: 122 bytes, application/json-rpc; charset=utf-8
response:  status 200
   headers: 8 
   data: 0 bytes, text/html 
2012-07-12 06:11:26 +0000      drive.files.insert (bytes 0-24906/24907) 
     request/response log
request: PUT https://www.googleapis.com/upload/rpc?uploadType=resumable&pretty
Print=false&upload_id=AEnB2Uof8orscKyQ0cFCYja2ZIl3PP86NNtkS76hDt9jc-F9R3eJZuHoHc
8hNElaqy5D63xyhrKmY0b9ABH3BgX7drRYoceykg
   headers: 4 
   data: 24907 bytes, application/pdf
response:  status 200
   headers: 7 
   data: 1309 bytes, application/json 

Now is the invalid token's log.(I revoke the access in 
 Google->Account->Security->Authorizing applications and sites page), and the 
callback is not called.
invalid:

2012-07-12 06:12:53 +0000      refresh token for accounts.google.com 
     request/response log
request: POST https://accounts.google.com/o/oauth2/token
   headers: 2 
   data: 175 bytes, application/x-www-form-urlencoded
response:  status 400  ⚑
   headers: 10 
   data: 31 bytes, application/json  

Original comment by snsdev.i...@gmail.com on 12 Jul 2012 at 6:18

GoogleCodeExporter commented 9 years ago

Original comment by Jimmy...@gmail.com on 12 Jul 2012 at 6:20

Attachments:

GoogleCodeExporter commented 9 years ago
You will have to single-step through the library when the fetch fails to 
determine what callback is not being invoked.  Try setting breakpoints at these 
callback methods, as they are called in order when the refresh fetch returns a 
400:

GTMOAuth2Authentication's tokenFetcher:finishedWithData:error:
GTMHTTPFetcher's authorizer:request:finishedWithError:
GTLService's objectFetcher:finishedWithData:error:

Original comment by grobb...@google.com on 17 Jul 2012 at 7:55

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
I tried to set 3 breakpoints for that 3 point.
When getting a 400

1 GTMOAuth2Authentication's tokenFetcher:finishedWithData:error:
is get called and follow that

2 GTMOAuth2Authentication's tokenFetcher:finishedWithData:error:
is get called which is good. In this method ,it has error, so it calls [self 
failToBeginFetchWithError:error]

And in failToBeginFetchWithError: it calls [self 
invokeFetchCallbackWithData:nil error:error]

And in invokeFetchCallbackWithData:error it does 2 things.
(1) [self invokeFetchCallback:finishedSel_ target:delegate_ data:data 
error:error]
the delegate_ is a GTLService instance but the finishSel_ is nil.(Which I think 
is good because the callback is a block not a SEL)

(2)
#if NS_BLOCKS_AVAILABLE
   if(completionBlock_)
   {
     completionBlock_(data,error);
   }
#endif

the completionBlock_ is also nil! And the GTLService's 
objectFetcher:finishedWithData:error: is not get called!

I found that when the GTMHTTPFetcher is created with block, the callback block 
is set by using setProperty:forKey: when a key named @"_completionHandler", 
it's ivar completionBlock_ is not set at the first time.

That why I did something in "Comment 2".(By the way, the GTLService's callback 
block takes 3 arguments, so the "completionBlock_(data,error);" is wrong, even 
if the completionBlock_ is not nil).

Hope these information will help you find the bug.

Original comment by snsdev.i...@gmail.com on 18 Jul 2012 at 1:54

GoogleCodeExporter commented 9 years ago
no response anymore?

Original comment by snsdev.i...@gmail.com on 26 Jul 2012 at 1:08

GoogleCodeExporter commented 9 years ago
Hey?

Original comment by snsdev.i...@gmail.com on 2 Aug 2012 at 1:00

GoogleCodeExporter commented 9 years ago
I haven't had time to try again to reproduce this problem.

When you're looking at the code, remember that the library does not use block 
completion handlers internally, even if the app supplied one (since the library 
can run on systems without block support.) So seeing completion blocks set to 
nil internally is normal; blocks are present only at the external APIs, but 
selectors are always used by the library for internal calls to other parts of 
the library.

Original comment by grobb...@google.com on 2 Aug 2012 at 1:32

GoogleCodeExporter commented 9 years ago
FWIW I am experiencing a similar problem where the refresh token is not deemed 
valid during an upload, and a 400 error "invalid_grant" is logged to the 
console, yet the callback (with the relevant error) never occurs. 

I found this comment in GTMHTTPUploadFetcher suggesting that the finish 
selector is essentially being discarded, because connectionDidFinishLoading has 
been overridden -- however, in this type of error, -connectionDidFinishLoading 
is never called either, so the error is never sent to anyone as a callback. See 
the NULL parameter:

>  // we don't need a finish selector since we're overriding
> // -connectionDidFinishLoading
> return [super beginFetchWithDelegate:delegate  didFinishSelector:NULL];

When I comment this out and replace with [super beginFetchWithDelegate:delegate 
didFinishSelector: finishedSEL], everything starts working and I get the 
appropriate error callback. However, I'm afraid this could break something 
else...

Original comment by pedmons...@gmail.com on 16 Nov 2012 at 8:14