Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GoogleWebAuthorizationBroker in MVC For Google Drive Access

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;
like image 899
Paul Johnson Avatar asked Mar 23 '14 23:03

Paul Johnson


2 Answers

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());
     }
 }
like image 176
Hongwei Yan Avatar answered Nov 15 '22 00:11

Hongwei Yan


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.

like image 41
pinoyyid Avatar answered Nov 14 '22 23:11

pinoyyid