문제

This is my first time in working with Google's Drive api, so I apologize ahead of time if I've gotten something wrong.

Here's what I've got:

I used a web app to manually walk through the OAuth2 dance so that I could get an AccessToken, RefreshToken, and TokenType in a TokenResponse. Those values all came back successfully, and I stored them.

Now, I'm wanting to use that same information, along with the same username of a Google acct I created explicitly for this purpose, in a Windows Service that will monitor files within a specific directory of that user's Drive.

I manually create a TokenResponse, GoogleAuthorizationCodeFlow, UserCredential and DriveService as shown here:

//Populate the token response with the stored values
_tokenResponse = new TokenResponse
{
    AccessToken = AppConfiguration.GetSetting("StoredAccessToken"),
    TokenType = AppConfiguration.GetSetting("StoredTokenType"),
    RefreshToken = AppConfiguration.GetSetting("StoredRefreshToken"),
    Issued = DateTime.Parse(AppConfiguration.GetSetting("StoredTokenIssuedDate")),
    ExpiresInSeconds = long.Parse(AppConfiguration.GetSetting("StoredExpiresInSeconds"))
};

//Next, build our code flow
_apiCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
    {
        ClientSecrets = new ClientSecrets
            {
                ClientId = AppConfiguration.GetSetting("ApiClientId"),
                ClientSecret = AppConfiguration.GetSetting("ApiClientSecret")
            },
        Scopes = new[] { DriveService.Scope.Drive },
        DataStore = new FileDataStore(AppConfiguration.GetSetting("RegisteredApplicationName"))
    });
//Now create the UserCredential with those stored values        
_credential = new UserCredential(_apiCodeFlow, AppConfiguration.GetSetting("DriveApiServiceAccountEmail"), _tokenResponse);
//And generate a DriveService instance using the UserCredential
_driveService = new DriveService(new BaseClientService.Initializer
{
    HttpClientInitializer = _credential,
    ApplicationName = AppConfiguration.GetSetting("RegisteredApplicationName")
});

This all appears to work okay, but when I try to do something like

DriveService.Files.List.Execute()

I get an exception:

"Error:\"invalid_grant\", Description:\"\", Uri:\"\""

Now, I've been digging into the Google documentation for several days, and am pretty much at a loss. I started looking at the DrEdit solution they provide, but it's referencing objects that I don't see in their most recent API namespaces such as: IAuthorizationState, NativeApplicationClient. I'm using Google.Apis.Drive.v2, Google.Apis.Auth.OAuth2, Google.Apis.Auth.OAuth2.Flows, Google.Apis.OAuth2.Responses, Google.Apis.Services, Google.Apis.Util, and Google.Apis.Util.Store namespaces. Is there something I'm missing?

I'm wondering if it has anything to do with the url the request is coming from, as you have to register all redirect urls in the google console for registering these apps. However, I don't see a way for me to specify what redirect url to use when doing this from a windows service. That may not even be the issue though, just a thought.

My need is fairly simple - I just need to be able to use the refresh token I've saved, along with the username of my new user created only for this purpose, and get a DriveService instance that will let me traverse the files that this user can access.

Any help would be sincerely appreciated. Thanks in advance!!

EDIT

Using Fiddler to monitor the network traffic, it looks like the request is going out as it should. The grant_type is marked as "refresh_token", and all other items are populated with the correct values. See the attached image: Token Refresh Request Body from Fiddler

EDIT 2

In the GoogleAuthorizationCodeFlow class, there's a method named "RefreshTokenAsync" that takes a username, a refresh token, and a cancellation token. It produces the same results in Fiddler as does calling any method in the DriveService class, so I'm thinking that it is automatically trying to refresh the token behind the scenes when making a call to the DriveService. Not sure if this is helpful to those who may be able to answer this, but I thought I'd mention anyways.

12/18/2013 Update based on DaImTo's Suggestion

I created my own implementation of IDataStore, and here is the code for the GetAsync method, which I've stepped through and it returns a Token object as it should:

    public Task<T> GetAsync<T>(string key)
    {
        TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
        try
        {
            //First, load the token from our local json file
            //NOTE: If using the built in FileDataStore, Google would store this same json info in the local users appdata\roaming\{appname} folder
            Stream stream = new FileStream("StoredToken.json", FileMode.Open, FileAccess.Read);
            //Read the file contents into a string
            string sToken = (new StreamReader(stream)).ReadToEnd();
            //Close the stream so that the file isn't locked
            stream.Close();
            stream.Dispose();
            //Deserialize into the type that Google requests
            var token = Google.Apis.Json.NewtonsoftJsonSerializer.Instance.Deserialize<T>(sToken);
            //Set the result of the TaskCompletionSource to our value
            tcs.SetResult(token);
        }
        catch (Exception exc)
        {
            //No need to throw exception here - whatever is calling this method would want to handle exceptions.  
            //So, just set the Exception of the TaskCompletionSource and return it
            tcs.SetException(exc);
        }

        return tcs.Task;
    }

Now, here's my updated way of getting a UserCredential object and using it to populate my DriveService instance:

        _credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
            clientSecrets: new ClientSecrets
                {
                    ClientId = AppConfiguration.GetSetting("ApiClientId"),
                    ClientSecret = AppConfiguration.GetSetting("ApiClientSecret")
                },
            scopes: new[] {DriveService.Scope.Drive},
            dataStore: new CustomTokenDataStore(),
            user: AppConfiguration.GetSetting("DriveApiServiceAccountEmail"),
            taskCancellationToken: CancellationToken.None).Result;

        _driveService = new DriveService(new BaseClientService.Initializer
        {
            HttpClientInitializer = _credential,
            ApplicationName = AppConfiguration.GetSetting("RegisteredApplicationName")
        });

I then tried to do a simple query with the DriveService API:

            using (GoogleDriveApiUtils util = new GoogleDriveApiUtils())
            {
                FilesResource.ListRequest request = util.DriveService.Files.List();

                /*Documentation for using the search parameters:
                 * https://developers.google.com/drive/search-parameters
                 */

                //Add this to the query to get only folders
                request.Q = "mimeType='application/vnd.google-apps.folder'";
                
                FileList fList = request.Execute();
            }

And here is where I get the same exception - "invalid_grant". From what I've been able to research, this is to do with the url the response is coming from. However, I'm doing this from a Windows Service - so I'm clueless how I can make it come from the registered url on the request to the DriveService. However, I may be totally on the wrong track.

Any other ideas? Again, all help is greatly appreciated :)

도움이 되었습니까?

해결책 2

I figured out what it was, and I feel like an idiot.

Back-story - This started out as being an MVC app and I had the app registered in the Google Cloud Console and have OAuth dance working in it. I then was told that we need a windows service to parse some of the data in some specific locations, and so I created a new user for this and manually did the OAuth dance and stored the refresh token... However, I totally neglected to register a new app in the Cloud Console as an "Installed Application", whereas my first one is a "Web Application", and therein lies the problem. I didn't even realize until this afternoon that I was just re-using the same ClientId and ClientSecret values from the web app... Once I registered a new app as an installed app, then manually walked through the OAuth dance to get new tokens, I updated all my references in the win service to point to these things and totally accessed everything as I should be able to...

Many thanks to DaImTo, who pointed out that I should be using my own IDataStore implementation - that was most useful information which I'll still be using.

My apologies if I wasted anyone's time, hopefully this helps someone :)

다른 팁

FileDataStore stores the information in %appdata% and thats where it gets it from. To use stored data you need to make your own implimitation of iDataStore then you can send that insted of the FileDataStore which is also just another implimitation of iDataStore.

This is just copied from my GoogleAnalytics code you can just change it to your scopes and it will work once you have created your own implimitation of iDatastore();

IDataStore StoredRefreshToken = new myDataStore();
// Oauth2 Autentication.
using (var stream = new System.IO.FileStream("client_secret.json", System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
 credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
 GoogleClientSecrets.Load(stream).Secrets,
 new[] { AnalyticsService.Scope.AnalyticsReadonly },
 "user", CancellationToken.None, StoredRefreshToken).Result;
}

You can find a basic example of it here http://daimto.com/google-oauth2-csharp/#Loading_Stored_refresh-Token

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top