6

I'm stuck trying to access a specific Google drive account from a MVC app. All I need is for the MVC web app to access my google drive scan for a few files and alter the database based on the contents of the google drive. The problem is when running in IIS the drive cannot be authenticated as GoogleWebAuthorizationBroker tries to open browser if its a windows app but doesn't seem to be able to do that through IIS and even if it did it would be server side.

Ideally I would not have to authenticate this app at all, but if it has do go through that then how do I make it work in IIS?

UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
    new ClientSecrets
            {
                ClientId = "MY_ID",
                ClientSecret = "My_Secret"
            },
            new[] { DriveService.Scope.Drive },
            "user",
            CancellationToken.None, dataStore: new FileDataStore(Server.MapPath("~/app_data/googledata"))).Result;
Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
Paul Johnson
  • 1,417
  • 1
  • 17
  • 26
  • try checking this out. https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth I'm not sure your doing it correctly. – Linda Lawton - DaImTo Mar 24 '14 at 08:27
  • Are you (1) trying to access your (personal) Google drive account? or (2) do you want to be able to access other peoples Drive Account? or (3) do you just want to access an account dedicated to the app (ie. NOT your personal Drive account)? – Linda Lawton - DaImTo Mar 24 '14 at 14:16
  • It is to access 1 single personal account yes. Not mine but the clients. The files should also be viewable in Google drive website and be sycable to the desktop computer. – Paul Johnson Apr 09 '14 at 11:12

3 Answers3

7

I got this to work, was able to enable the web site to access Google drive using my account without asking users to login or authorize.

First of all, follow this link to get Google API work with MVC:

https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web_applications

There is a problem in the Sample code, in HomeController

 public async Task IndexAsync(CancellationToken cancellationToken)

Should be:

 public async Task<ActionResult> IndexAsync(CancellationToken cancellationToken)

After that, I created a MemoryDataStore (see code at the end) that is a slightly modification from the MemoryDataStore posted here:

http://conficient.wordpress.com/2014/06/18/using-google-drive-api-with-c-part-2/

Once you do that, capture the refresh token of the account you are using, and replace the store with this store when authenticate:

    private static readonly IAuthorizationCodeFlow flow =
        new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
            {
                ClientSecrets = new ClientSecrets
                {
                    ClientId = clientID,
                    ClientSecret = clientSecret
                },
                Scopes = new[] { DriveService.Scope.Drive },
                //DataStore = new FileDataStore("Drive.Api.Auth.Store")
                DataStore = new GDriveMemoryDataStore(commonUser, refreshToken)
            });

Here commonUser is a predefined user id of your chosen. Please make sure to modify the GetUserID() method to return the same commonUser:

 public override string GetUserId(Controller controller)
    {
        return commonUser;
    }

Once this is done, Google drive will stop asking user to login and authorize the app.

Here is my MemoryDataStore code:

 /// <summary>
 /// Handles internal token storage, bypassing filesystem
 /// </summary>
internal class GDriveMemoryDataStore : IDataStore
 {
     private Dictionary<string, TokenResponse> _store;
     private Dictionary<string, string> _stringStore;

     //private key password: notasecret

     public GDriveMemoryDataStore()
     {
         _store = new Dictionary<string, TokenResponse>();
         _stringStore = new Dictionary<string, string>();
     }

     public GDriveMemoryDataStore(string key, string refreshToken)
     {
         if (string.IsNullOrEmpty(key))
             throw new ArgumentNullException("key");
         if (string.IsNullOrEmpty(refreshToken))
             throw new ArgumentNullException("refreshToken");

         _store = new Dictionary<string, TokenResponse>();

         // add new entry
         StoreAsync<TokenResponse>(key,
             new TokenResponse() { RefreshToken = refreshToken, TokenType = "Bearer" }).Wait();
     }

     /// <summary>
     /// Remove all items
     /// </summary>
     /// <returns></returns>
     public async Task ClearAsync()
     {
         await Task.Run(() =>
         {
             _store.Clear();
             _stringStore.Clear();
         });
     }

     /// <summary>
     /// Remove single entry
     /// </summary>
     /// <typeparam name="T"></typeparam>
     /// <param name="key"></param>
     /// <returns></returns>
     public async Task DeleteAsync<T>(string key)
     {
         await Task.Run(() =>
         {
            // check type
             AssertCorrectType<T>();

             if (typeof(T) == typeof(string))
             {
                 if (_stringStore.ContainsKey(key))
                     _stringStore.Remove(key);                 
             }
             else if (_store.ContainsKey(key))
             {
                 _store.Remove(key);
             }
         });
     }

     /// <summary>
     /// Obtain object
     /// </summary>
     /// <typeparam name="T"></typeparam>
     /// <param name="key"></param>
     /// <returns></returns>
     public async Task<T> GetAsync<T>(string key)
     {
         // check type
         AssertCorrectType<T>();

         if (typeof(T) == typeof(string))
         {
             if (_stringStore.ContainsKey(key))
                 return await Task.Run(() => { return (T)(object)_stringStore[key]; });
         }
         else if (_store.ContainsKey(key))
         {
             return await Task.Run(() => { return (T)(object)_store[key]; });
         }
         // key not found
         return default(T);
     }

     /// <summary>
     /// Add/update value for key/value
     /// </summary>
     /// <typeparam name="T"></typeparam>
     /// <param name="key"></param>
     /// <param name="value"></param>
     /// <returns></returns>
     public Task StoreAsync<T>(string key, T value)
     {
         return Task.Run(() =>
         {
             if (typeof(T) == typeof(string))
             {
                 if (_stringStore.ContainsKey(key))
                     _stringStore[key] = (string)(object)value;
                 else
                     _stringStore.Add(key, (string)(object)value);
             } else
             {
                 if (_store.ContainsKey(key))
                     _store[key] = (TokenResponse)(object)value;
                 else
                     _store.Add(key, (TokenResponse)(object)value);
             }
         });
     }

     /// <summary>
     /// Validate we can store this type
     /// </summary>
     /// <typeparam name="T"></typeparam>
     private void AssertCorrectType<T>()
     {
         if (typeof(T) != typeof(TokenResponse) && typeof(T) != typeof(string)) 
             throw new NotImplementedException(typeof(T).ToString());
     }
 }
Hongwei Yan
  • 498
  • 1
  • 4
  • 11
  • I actually eneded up just installing google drive on the server and periodically scanning the file system as it was looking a little difficult to do it any other way. This did seem to have the issue of slowing the server down though. I think mostly as it was just a bad virtual server. Your answer looks like it probably solves the original problem I posted though so I'll mark it as the answer. – Paul Johnson Sep 29 '14 at 19:34
  • To be clear, what fixed it here is that .GetUserId always returns your Gmail account. This way, Google knows you're only trying to authenticate against your gmail account, rather than with whoever is logged on at the moment. – Judah Gabriel Himango Apr 25 '16 at 21:32
2

I'm not familiar with C#, but the generic OAuth answer is that you need to request a refresh token (once only and you can do this in the OAuth playground), then store/embed that token somewhere in your server so you can use it to request an access token whenever your server app needs to access Drive.

See How do I authorise an app (web or installed) without user intervention? (canonical ?) for details on how to do this. You will of course either need to reverse engineer how the C# library stores its tokens, or create/modify an equivalent which uses the manually obtained refresh token.

Community
  • 1
  • 1
pinoyyid
  • 21,499
  • 14
  • 64
  • 115
  • -1 The client Lib handles all that form him. He doesn't need to deal with the tokens. – Linda Lawton - DaImTo Mar 24 '14 at 08:26
  • That's why I qualified my answer with "generic Oauth". To achieve what he is looking for, he needs to save a refresh token. Quite how he tells the client library that's what he wants to do is an exercise for a C# person. BUT he does need to direct the library in that direction else he'll be prompted for auth each time. Specifically he needs to read https://developers.google.com/accounts/docs/OAuth2WebServer#offline. Pointing him at a page he's undoubtedly already read doesn't help much. Don't downvote an answer just because you don't understand it. – pinoyyid Mar 24 '14 at 08:49
  • FileDataStore Handles where the refresh token is stored, He knows this because he's got it server mapped. User handles which "user" is selected from the store. I think your "answer" would have been better as a comment; as it doesn't "answer" his question. Which is related to MVC, IIS and GoogleWebAuthorizationBroker. I hate giving -1s if you want edit it and I will remove it. But I still don't feel its an answer to this question. – Linda Lawton - DaImTo Mar 24 '14 at 09:07
  • 2
    The question says "Ideally I would not have to authenticate this app at all". The closest to achieving this is to manually generate a refresh token on OAuth Playground, which he then stores in such a way that the client library can then make use of. Feel free to contribute a better answer if you can find one. – pinoyyid Mar 24 '14 at 12:10
0

I suspect what you are looking for is a Service account. A service account will allow you to set up your application to access your google drive with out requiring you to Autenticate.

Google APIs also support Service accounts. Unlike the scenario in which a client application requests access to an end-user's data, service accounts provide access to the client application's own data.

You can find Google's documentation on how to implement a service account here. Google APIs Client Library for .NET : Service Account

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • 1
    "A service account will allow you to set up your application to access your google drive". This is incorrect. A Service Account is a separate account dedicated to the app. The question says "access my google drive" and "access a specific Google drive account" – pinoyyid Mar 24 '14 at 12:50
  • That's why I added the question to him in the comment. I suspect this is what he's looking for. To me it sounds like he's got a "specific Google Drive" account that he wants to read from. If he will only ever be reading from this account then using a service account will be the best option as he doesn't want to have to login again. If he's accessing other users drive accounts (which it doesn't sound like) then he will have to use the Oauth2 method and probably create his own implementation of Idatastore. We need more information to be sure. – Linda Lawton - DaImTo Mar 24 '14 at 12:55
  • Not sure how you infer he wants a Service Account when the question says "**my** google drive". Of course, if he really does want a Drive account dedicated to the app, then you're quite right that a Service Account is the answer. HOWEVER, if by "my google drive" he actually does mean "my google drive", then my answer is correct. – pinoyyid Mar 24 '14 at 13:01
  • Edit yours so I can remove the -1 just for the simple reason that its been a fun debate. :) – Linda Lawton - DaImTo Mar 24 '14 at 13:04
  • sure. why not :-) What edit would you like? Bear in mind that as it stands, it *does* answer the question. – pinoyyid Mar 24 '14 at 13:10
  • No but I still hate to give -1s and seriously this has been fun :). We cant be sure if mine answers the question either. Its just a guess that this is what he meant. – Linda Lawton - DaImTo Mar 24 '14 at 13:13
  • You might want to adjust the question in your comment. Instead of "Are you (1) only trying to access your Google drive account? or (2) do you want to be able to access other peoples Drive Account?", you should probably add ", or (3) do you want to access an account dedicated to the app (ie. NOT your personal Drive account)?" Option 1 is best dealt with by my answer, option 2 requires him to implement all of the OAuth guff, and option 3 is a service account. Depending on the OP's response, one of us should delete our answer so as not to confuse those who may follow. – pinoyyid Mar 24 '14 at 13:52
  • Yes I thought about that (the Delete part). Edit on the comment was a good idea. – Linda Lawton - DaImTo Mar 24 '14 at 14:14