Explicit flow upload file to google drive - c#

I have an Angular 6 app that uses .net Core 2.x as backend
I have the External provider working for the explicit flow (lot of samples)
I use explicit flow since I do not whant to share the secret and client id with the web app.
I have a upload file service (domain.com/api/upload)
Witch works fine
Now I would like to save the file to a logged in user's google drive.
The google project has the google drive api permissions and all
But I can't get the drive api client to authenticate with the drive api
Controller action
private async Task<IFileStoreItem> SaveFile(IFormFile file, IFilePath path)
{
if (file == null) throw new Exception("File is null");
if (file.Length == 0) throw new Exception("File is empty");
if(path == null) throw new Exception("path is null");
IFileStoreItem item = file.Adapt<DocumentInfo>();
item.Container = path.Container;
item.Partition = path.Partition;
IFileStoreService fileStoreService = GetFileStoreService();
if (file.Length > 0)
{
using (Stream stream = new MemoryStream())
{
await file.CopyToAsync(stream);
item = await fileStoreService.Save(stream, item);
}
item.Link = Url.AbsoluteUri("DownloadFile", "File", new { container = item.Container, partition = item.Partition, id = item.Id });
}
return item;
}
Helper function
private IFileStoreService GetFileStoreService()
{
IFileStoreService ret = null;
string uploads = Path.Combine(_environment.WebRootPath, "uploads");
DocumentStorageConfiguration config = new StorageConfiguration().GetStoragetSettings();
ret = new GoogleDriveService(User.Claims.Where(c => c.Type == JwtRegisteredClaimNames.UniqueName).SingleOrDefault()?.Value);
return ret;
}
Drive service
public class GoogleDriveService : IFileStoreService
{
DriveService _service;
UserCredential credential;
public GoogleDriveService(string user)
{
string[] scopes = new string[] {
DriveService.Scope.Drive,
DriveService.Scope.DriveFile};
GoogleConfiguration config = new AuthentificationConfiguration().GetGoogleSettings();
var secrets = new ClientSecrets
{
ClientId = config.ClientId,
ClientSecret = config.ClientSecret
};
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(secrets, scopes, user, CancellationToken.None).Result;
_service = new DriveService(new Google.Apis.Services.BaseClientService.Initializer() {
HttpClientInitializer = credential
});
}
public async Task<IFileStoreItem> Save(System.IO.Stream stream, IFileStoreItem item)
{
File body = new File
{
Name = item.Name,
MimeType = item.ContentType,
Parents = new List<string>() { "Documents" }
};
FilesResource.CreateMediaUpload request = new FilesResource.CreateMediaUpload(_service,body,stream,item.ContentType);
IUploadProgress progress = await request.UploadAsync();
item.Id = body.Id;
return item;
}
}
The error I am getting is bad gateway 502
Question is if I can authenticate with google drive based on the currently logged in user (witch already is a google user)

Related

AndroidPublisherService - Play Developer API Client - Upload aab failes due to bad credentials

Trying to make use of the AndroidPublisherService from Play Developer API Client.
I can list active tracks and the releases in those tracks, but when I try to upload a new build there seems to be no way of attaching the authentication already made previously to read data.
I've authenticated using var googleCredentials = GoogleCredential.FromStream(keyDataStream) .CreateWithUser(serviceUsername); where serviceUsername is the email for my service account.
private static void Execute(string packageName, string aabfile, string credfile, string serviceUsername)
{
var credentialsFilename = credfile;
if (string.IsNullOrWhiteSpace(credentialsFilename))
{
// Check env. var
credentialsFilename =
Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS",
EnvironmentVariableTarget.Process);
}
Console.WriteLine($"Using credentials {credfile} with package {packageName} for aab file {aabfile}");
var keyDataStream = File.OpenRead(credentialsFilename);
var googleCredentials = GoogleCredential.FromStream(keyDataStream)
.CreateWithUser(serviceUsername);
var credentials = googleCredentials.UnderlyingCredential as ServiceAccountCredential;
var service = new AndroidPublisherService();
var edit = service.Edits.Insert(new AppEdit { ExpiryTimeSeconds = "3600" }, packageName);
edit.Credential = credentials;
var activeEditSession = edit.Execute();
Console.WriteLine($"Edits started with id {activeEditSession.Id}");
var tracksList = service.Edits.Tracks.List(packageName, activeEditSession.Id);
tracksList.Credential = credentials;
var tracksResponse = tracksList.Execute();
foreach (var track in tracksResponse.Tracks)
{
Console.WriteLine($"Track: {track.TrackValue}");
Console.WriteLine("Releases: ");
foreach (var rel in track.Releases)
Console.WriteLine($"{rel.Name} version: {rel.VersionCodes.FirstOrDefault()} - Status: {rel.Status}");
}
using var fileStream = File.OpenRead(aabfile);
var upload = service.Edits.Bundles.Upload(packageName, activeEditSession.Id, fileStream, "application/octet-stream");
var uploadProgress = upload.Upload();
if (uploadProgress == null || uploadProgress.Exception != null)
{
Console.WriteLine($"Failed to upload. Error: {uploadProgress?.Exception}");
return;
}
Console.WriteLine($"Upload {uploadProgress.Status}");
var tracksUpdate = service.Edits.Tracks.Update(new Track
{
Releases = new List<TrackRelease>(new[]
{
new TrackRelease
{
Name = "Roswell - Grenis Dev Test",
Status = "completed",
VersionCodes = new List<long?>(new[] {(long?) upload?.ResponseBody?.VersionCode})
}
})
}, packageName, activeEditSession.Id, "internal");
tracksUpdate.Credential = credentials;
var trackResult = tracksUpdate.Execute();
Console.WriteLine($"Track {trackResult?.TrackValue}");
var commitResult = service.Edits.Commit(packageName, activeEditSession.Id);
Console.WriteLine($"{commitResult.EditId} has been committed");
}
And as the code points out, all action objects such as tracksList.Credential = credentials; can be given the credentials generated from the service account.
BUT the actual upload action var upload = service.Edits.Bundles.Upload(packageName, activeEditSession.Id, fileStream, "application/octet-stream"); does not expose a .Credential object, and it always fails with:
The service androidpublisher has thrown an exception: Google.GoogleApiException: Google.Apis.Requests.RequestError
Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project. [401]
Errors [
Message[Login Required.] Location[Authorization - header] Reason[required] Domain[global]
]
at Google.Apis.Upload.ResumableUpload`1.InitiateSessionAsync(CancellationToken cancellationToken)
at Google.Apis.Upload.ResumableUpload.UploadAsync(CancellationToken cancellationToken)
So, how would I go about providing the actual Upload action with the given credentials here?
Managed to figure this out during the day, I was missing one call to CreateScoped() when creating the GoogleCredential object as well as a call to InitiateSession() on the upload object.
var googleCredentials = GoogleCredential.FromStream(keyDataStream)
.CreateWithUser(serviceUsername)
.CreateScoped(AndroidPublisherService.Scope.Androidpublisher);
Once that was done I could then get a valid oauth token by calling
var googleCredentials = GoogleCredential.FromStream(keyDataStream)
.CreateWithUser(serviceUsername)
.CreateScoped(AndroidPublisherService.Scope.Androidpublisher);
var credentials = googleCredentials.UnderlyingCredential as ServiceAccountCredential;
var oauthToken = credentials?.GetAccessTokenForRequestAsync(AndroidPublisherService.Scope.Androidpublisher).Result;
And I can now use that oauth token in the upload request:
upload.OauthToken = oauthToken;
_ = await upload.InitiateSessionAsync();
var uploadProgress = await upload.UploadAsync();
if (uploadProgress == null || uploadProgress.Exception != null)
{
Console.WriteLine($"Failed to upload. Error: {uploadProgress?.Exception}");
return;
}
The full code example for successfully uploading a new aab file to google play store internal test track thus looks something like this:
private async Task UploadGooglePlayRelease(string fileToUpload, string changeLogFile, string serviceUsername, string packageName)
{
var serviceAccountFile = ResolveServiceAccountCertificateInfoFile();
if (!serviceAccountFile.Exists)
throw new ApplicationException($"Failed to find the service account certificate file. {serviceAccountFile.FullName}");
var keyDataStream = File.OpenRead(serviceAccountFile.FullName);
var googleCredentials = GoogleCredential.FromStream(keyDataStream)
.CreateWithUser(serviceUsername)
.CreateScoped(AndroidPublisherService.Scope.Androidpublisher);
var credentials = googleCredentials.UnderlyingCredential as ServiceAccountCredential;
var oauthToken = credentials?.GetAccessTokenForRequestAsync(AndroidPublisherService.Scope.Androidpublisher).Result;
var service = new AndroidPublisherService();
var edit = service.Edits.Insert(new AppEdit { ExpiryTimeSeconds = "3600" }, packageName);
edit.Credential = credentials;
var activeEditSession = await edit.ExecuteAsync();
_logger.LogInformation($"Edits started with id {activeEditSession.Id}");
var tracksList = service.Edits.Tracks.List(packageName, activeEditSession.Id);
tracksList.Credential = credentials;
var tracksResponse = await tracksList.ExecuteAsync();
foreach (var track in tracksResponse.Tracks)
{
_logger.LogInformation($"Track: {track.TrackValue}");
_logger.LogInformation("Releases: ");
foreach (var rel in track.Releases)
_logger.LogInformation($"{rel.Name} version: {rel.VersionCodes.FirstOrDefault()} - Status: {rel.Status}");
}
var fileStream = File.OpenRead(fileToUpload);
var upload = service.Edits.Bundles.Upload(packageName, activeEditSession.Id, fileStream, "application/octet-stream");
upload.OauthToken = oauthToken;
_ = await upload.InitiateSessionAsync();
var uploadProgress = await upload.UploadAsync();
if (uploadProgress == null || uploadProgress.Exception != null)
{
Console.WriteLine($"Failed to upload. Error: {uploadProgress?.Exception}");
return;
}
_logger.LogInformation($"Upload {uploadProgress.Status}");
var releaseNotes = await File.ReadAllTextAsync(changeLogFile);
var tracksUpdate = service.Edits.Tracks.Update(new Track
{
Releases = new List<TrackRelease>(new[]
{
new TrackRelease
{
Name = $"{upload?.ResponseBody?.VersionCode}",
Status = "completed",
InAppUpdatePriority = 5,
CountryTargeting = new CountryTargeting { IncludeRestOfWorld = true },
ReleaseNotes = new List<LocalizedText>(new []{ new LocalizedText { Language = "en-US", Text = releaseNotes } }),
VersionCodes = new List<long?>(new[] {(long?) upload?.ResponseBody?.VersionCode})
}
})
}, packageName, activeEditSession.Id, "internal");
tracksUpdate.Credential = credentials;
var trackResult = await tracksUpdate.ExecuteAsync();
_logger.LogInformation($"Track {trackResult?.TrackValue}");
var commitResult = service.Edits.Commit(packageName, activeEditSession.Id);
commitResult.Credential = credentials;
await commitResult.ExecuteAsync();
_logger.LogInformation($"{commitResult.EditId} has been committed");
}

Google Drive API Files.List() Error invalid_grant Bad Request

I am using Google Apis Drive v3 with a simple request to find a folder with a specific ID and then to list all of the files in the folder.
However on request Execute it is creating the exception:
“Error: invalid_grant, Description: Bad Request, Uri:”
It used to work but has stopped sometime in the last month approx. while I was working on another project.
I have checked the API console and our Client ID is registered (I have hashed out the ID, Secret & refresh token) and everything seems OK with the creation of a service it is sending the request that is not working.
However the query used to work and I have tried changing the query and it still does not work.
Code is below, any suggestions on how to resolve this would be appreciated. Thanks
Request File List
List<File> fileList = new List<File>();
List<File> FileList_Lst_Ordered = new List<File>();
try
{
string pageToken = null;
do
{
request.Q = "'" + folderId + "' in parents";
//request.Q = String.Format("name='{0}'", "Release Scripts Folder");
//request.Q = "mimeType = 'application/vnd.google-apps.folder' + title='" + folderId +"'";
request.Spaces = "drive";
request.Fields = "nextPageToken, files(id, name, mimeType, trashed)";
request.PageToken = pageToken;
var result = request.Execute();
// Iterate through files
foreach (File file in result.Files)
{
// If it is a matching type (or we are retreiving all files)
if (!(bool)file.Trashed && (file.MimeType == type || type == "ALL"))
{
fileList.Add(file);
}
//RST_00001
else if (!(bool)file.Trashed && (file.MimeType == "sql" || file.MimeType == "text/x-sql"))
{
fileList.Add(file);
}
}
// Increment file token
pageToken = result.NextPageToken;
} while (pageToken != null);
//RST_00002: Order List Alphabetically
int Counter_int = fileList.Count();
for (int i = 1; i <= Counter_int; i++)
{
FileList_Lst_Ordered.Add(fileList.OrderBy(x => x.Name).FirstOrDefault());
fileList.Remove(fileList.OrderBy(x => x.Name).FirstOrDefault());
}
}
catch (Exception Ex)
{
errorString = Ex.Message;
Helpers.Helpers_TextFile.TextFile_WriteTo(Log_Filename, "Error Occured While Obtaining script list for folder " + folderId + ", full error: " + Ex.ToString());
}
Initialise Service In GoogUtils.cs Helper
public static DriveService InitService(ref string errorString)
{
try {
var token = new TokenResponse { RefreshToken = "###" };
var credentials = new UserCredential(new GoogleAuthorizationCodeFlow(
new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "###",
ClientSecret = "###"
}
}),
"user",
token);
// Create Drive API service.
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentials,
ApplicationName = ApplicationName,
});
return service;
}
catch (Exception e)
{
errorString = e.Message;
}
return null;
}
I have been able to resolve the problem. By re-writing the service so it uses GoogleWebAuthorizationBroker.AuthorizeAsync instead of GoogleAuthorizationCodeFlow, I also am now loading the credentials from a JSON. See blow.
Thanks to all for your contributions and help:
public static DriveService InitService(ref string errorString, string Log_Filename)
{
try
{
string[] Scopes = { DriveService.Scope.DriveReadonly };
UserCredential credential;
if (!System.IO.File.Exists(credential_json))
{
System.Windows.Forms.MessageBox.Show("Unable to Initialise Google Drives Service credentials not found.");
Helpers.Helpers_TextFile.TextFile_WriteTo(Log_Filename, "Google Drive Init - Unable to Initialise Google Drives Service credentials not found.");
}
using (var stream = new FileStream(credential_json, FileMode.Open, FileAccess.Read))
{
string credPath = "token.json";
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
if (credential.UserId == "user")
{
Helpers.Helpers_TextFile.TextFile_WriteTo(Log_Filename, "Google Drive Init - Credentials file saved to: " + credPath);
}
else {
Helpers.Helpers_TextFile.TextFile_WriteTo(Log_Filename, "Google Drive Init - Unable to verify credentials.");
return null;
}
}
// Create Drive API service
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
// Validate Service
if (service.Name == "drive" && service.ApplicationName == ApplicationName)
{
Helpers.Helpers_TextFile.TextFile_WriteTo(Log_Filename, "Google Drive Init - Google Service Created.");
}
else
{
Helpers.Helpers_TextFile.TextFile_WriteTo(Log_Filename, "Google Drive Init - Unable to create service.");
return null;
}
return service;
}
catch (Exception Ex)
{
System.Windows.Forms.MessageBox.Show("An Error Occured While Initialising Google Drives Service");
System.Windows.Forms.Clipboard.SetText(Ex.ToString());
Helpers.Helpers_TextFile.TextFile_WriteTo(Log_Filename, "Google Drive Init - Error Occurred: " + Ex.ToString());
errorString = Ex.Message;
}
return null;
}

Google Analytics(Custom Dimension update) Insufficient Permission 403

I am trying to update custom dimension fields(https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtReference/management/customDimensions/update) in google analytics by calling the analytics api from C#.
I created a project in https://console.developers.google.com, added a service account(downloaded the .p12, private key file),enabled the analytics api and linked the service account email in https://analytics.google.com
I am able to read the "analytics data"(like account summaries etc) but not insert or update. When I try to do that, I get the Insufficient permission 403 error. The service account added to google analytics has all the privileges.
class Program
{
static void Main(string[] args)
{
test();
}
public static void test() //takes clientid as input
{
string[] scopes = new string[] { AnalyticsService.Scope.Analytics }; // view and manage your Google Analytics data
var keyFilePath = #"C:\Users\xyz\Desktop\CustomDimUpdate\xxxxxxxx.p12"; // Downloaded from https://console.developers.google.com
var serviceAccountEmail = "xxxx.iam.gserviceaccount.com"; // found https://console.developers.google.com
//loading the Key file
var certificate = new X509Certificate2(keyFilePath, "notasecret", X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes
}.FromCertificate(certificate));
var service = new AnalyticsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential
//ApplicationName = "Analytics API Sample",
});
CustomDimension body = new CustomDimension();
body.Name = "Configurable"; //Found in https://analytics.google.com
body.Scope = "Product"; //Found in https://analytics.google.com
body.Active = true;
try
{
//analytics.management().customDimensions()
// .update("123456", "UA-123456-1", "ga:dimension2", body).execute();
ManagementResource.CustomDimensionsResource.UpdateRequest update = service.Management.CustomDimensions.Update(body, "123456", "UA-123456-1", "ga:dimension1");
update.Execute(); //Errors out here
ManagementResource.AccountsResource.ListRequest list = service.Management.Accounts.List();
list.MaxResults = 1000; // Maximum number of Accounts to return, per request.
Accounts feed1 = list.Execute(); //Works perfectly fine
foreach (Account account in feed1.Items)
{
// Account
Console.WriteLine(string.Format("Account: {0}({1})", account.Name, account.Id));
}
ManagementResource.ProfilesResource.ListRequest list1 = service.Management.Profiles.List("123456", "UA-123456-1");
Profiles feed = list1.Execute();
foreach (Profile profile in feed.Items)
{
Console.WriteLine(string.Format("\t\tProfile: {0}({1})", profile.Name, profile.Id));
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
I was able to resolve this by changing the below line of code
string[] scopes = new string[] { AnalyticsService.Scope.Analytics }; // view and manage your Google Analytics data
to
string[] scopes = new string[] { AnalyticsService.Scope.AnalyticsEdit }; // view and manage your Google Analytics data

Google Calendar API for UWP Windows 10 Application One or more errors occurred

I'm trying to use Google Calendar API v3, but i have problems while running the codes, it always gives me that error :
An exception of type 'System.AggregateException' occurred in mscorlib.ni.dll but was not handled in user code
Additional information: One or more errors occurred.
I don't know why it does, also It should work as well. Here is a screenshot for it :
Also my codes are :
UserCredential credential;
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new Uri("ms-appx:///Assets/client_secrets.json"),
Scopes,
"user",
CancellationToken.None).Result;
// Create Google Calendar API service.
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
var calendarService = new CalendarService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "Windows 10 Calendar sample"
});
var calendarListResource = await calendarService.CalendarList.List().ExecuteAsync();
If you can at least help with calling it through REST API, that would be great too, but you must consider that it's UWP, so it has another way to get it work as well.
As i already tried through REST API, but i always get "Request error code 400".
Thanks for your attention.
The Google API Client Library for .NET does not support UWP by now. So we can't use Google.Apis.Calendar.v3 Client Library in UWP apps now. For more info, please see the similar question: Universal Windows Platform App with google calendar.
To use Google Calendar API in UWP, we can call it through REST API. To use the REST API, we need to authorize requests first. For how to authorize requests, please see Authorizing Requests to the Google Calendar API and Using OAuth 2.0 for Mobile and Desktop Applications.
After we have the access token, we can call Calendar API like following:
var clientId = "{Your Client Id}";
var redirectURI = "pw.oauth2:/oauth2redirect";
var scope = "https://www.googleapis.com/auth/calendar.readonly";
var SpotifyUrl = $"https://accounts.google.com/o/oauth2/auth?client_id={clientId}&redirect_uri={Uri.EscapeDataString(redirectURI)}&response_type=code&scope={Uri.EscapeDataString(scope)}";
var StartUri = new Uri(SpotifyUrl);
var EndUri = new Uri(redirectURI);
// Get Authorization code
WebAuthenticationResult WebAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, StartUri, EndUri);
if (WebAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success)
{
var decoder = new WwwFormUrlDecoder(new Uri(WebAuthenticationResult.ResponseData).Query);
if (decoder[0].Name != "code")
{
System.Diagnostics.Debug.WriteLine($"OAuth authorization error: {decoder.GetFirstValueByName("error")}.");
return;
}
var autorizationCode = decoder.GetFirstValueByName("code");
//Get Access Token
var pairs = new Dictionary<string, string>();
pairs.Add("code", autorizationCode);
pairs.Add("client_id", clientId);
pairs.Add("redirect_uri", redirectURI);
pairs.Add("grant_type", "authorization_code");
var formContent = new Windows.Web.Http.HttpFormUrlEncodedContent(pairs);
var client = new Windows.Web.Http.HttpClient();
var httpResponseMessage = await client.PostAsync(new Uri("https://www.googleapis.com/oauth2/v4/token"), formContent);
if (!httpResponseMessage.IsSuccessStatusCode)
{
System.Diagnostics.Debug.WriteLine($"OAuth authorization error: {httpResponseMessage.StatusCode}.");
return;
}
string jsonString = await httpResponseMessage.Content.ReadAsStringAsync();
var jsonObject = Windows.Data.Json.JsonObject.Parse(jsonString);
var accessToken = jsonObject["access_token"].GetString();
//Call Google Calendar API
using (var httpRequest = new Windows.Web.Http.HttpRequestMessage())
{
string calendarAPI = "https://www.googleapis.com/calendar/v3/users/me/calendarList";
httpRequest.Method = Windows.Web.Http.HttpMethod.Get;
httpRequest.RequestUri = new Uri(calendarAPI);
httpRequest.Headers.Authorization = new Windows.Web.Http.Headers.HttpCredentialsHeaderValue("Bearer", accessToken);
var response = await client.SendRequestAsync(httpRequest);
if (response.IsSuccessStatusCode)
{
var listString = await response.Content.ReadAsStringAsync();
//TODO
}
}
}
I have the Google .NET Client working in my UWP app. The trick is that you have to put it in a .NET Standard 2.0 Class Library, expose the API services you need, and then reference that library from your UWP app.
Also, you have to handle the getting the auth token yourself. It's not that much work and the Drive APIs and Calendar APIs work just fine (the only ones I've tried). You can see that I pass in a simple class that contains the auth token and other auth details to a method called Initialize.
Here is the single class I used in the .NET Standard 2.0 class library:
namespace GoogleProxy
{
public class GoogleService
{
public CalendarService calendarService { get; private set; }
public DriveService driveService { get; private set; }
public GoogleService()
{
}
public void Initialize(AuthResult authResult)
{
var credential = GetCredentialForApi(authResult);
var baseInitializer = new BaseClientService.Initializer { HttpClientInitializer = credential, ApplicationName = "{your app name here}" };
calendarService = new Google.Apis.Calendar.v3.CalendarService(baseInitializer);
driveService = new Google.Apis.Drive.v3.DriveService(baseInitializer);
}
private UserCredential GetCredentialForApi(AuthResult authResult)
{
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "{your app client id here}",
ClientSecret = "",
},
Scopes = new string[] { "openid", "email", "profile", "https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/calendar.events.readonly", "https://www.googleapis.com/auth/drive.readonly" },
};
var flow = new GoogleAuthorizationCodeFlow(initializer);
var token = new TokenResponse()
{
AccessToken = authResult.AccessToken,
RefreshToken = authResult.RefreshToken,
ExpiresInSeconds = authResult.ExpirationInSeconds,
IdToken = authResult.IdToken,
IssuedUtc = authResult.IssueDateTime,
Scope = "openid email profile https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events.readonly https://www.googleapis.com/auth/drive.readonly",
TokenType = "bearer" };
return new UserCredential(flow, authResult.Id, token);
}
}
}
In order to get the Auth token from google, you have to use custom schemes. Register your app as an 'iOS' app on the google services console and put in a URI scheme (something unique). Then add this scheme to your UWP manifest under Declarations->Protocol. Handle it in your App.xaml.cs:
protected override void OnActivated(IActivatedEventArgs args)
{
base.OnActivated(args);
if (args.Kind == ActivationKind.Protocol)
{
ProtocolActivatedEventArgs protocolArgs = (ProtocolActivatedEventArgs)args;
Uri uri = protocolArgs.Uri;
Debug.WriteLine("Authorization Response: " + uri.AbsoluteUri);
locator.AccountsService.GoogleExternalAuthWait.Set(uri.Query);
}
}
That GoogleExternalAuthWait comes from some magical code I found about how to create an asynchronous ManualResetEvent. https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-1-asyncmanualresetevent/ It looks like this (I only converted it to generic).
public class AsyncManualResetEvent<T>
{
private volatile TaskCompletionSource<T> m_tcs = new TaskCompletionSource<T>();
public Task<T> WaitAsync() { return m_tcs.Task; }
public void Set(T TResult) { m_tcs.TrySetResult(TResult); }
public bool IsReset => !m_tcs.Task.IsCompleted;
public void Reset()
{
while (true)
{
var tcs = m_tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<T>(), tcs) == tcs)
return;
}
}
}
This is how you start the Google Authorization. What happens is it launches an external browser to begin the google signing process and then wait (that's what the AsyncManualResetEvent does). When you're done, Google will launch a URI using your custom scheme. You should get a message dialog saying the browser is trying to open an app... click ok and the AsyncManualResetEvent continues and finishes the auth process. You'll need to make a class that contains all the auth info to pass to your class library.
private async Task<AuthResult> AuthenticateGoogleAsync()
{
try
{
var stateGuid = Guid.NewGuid().ToString();
var expiration = DateTimeOffset.Now;
var url = $"{GoogleAuthorizationEndpoint}?client_id={WebUtility.UrlEncode(GoogleAccountClientId)}&redirect_uri={WebUtility.UrlEncode(GoogleRedirectURI)}&state={stateGuid}&scope={WebUtility.UrlEncode(GoogleScopes)}&display=popup&response_type=code";
var success = Windows.System.Launcher.LaunchUriAsync(new Uri(url));
GoogleExternalAuthWait = new AsyncManualResetEvent<string>();
var query = await GoogleExternalAuthWait.WaitAsync();
var dictionary = query.Substring(1).Split('&').ToDictionary(x => x.Split('=')[0], x => Uri.UnescapeDataString(x.Split('=')[1]));
if (dictionary.ContainsKey("error"))
{
return null;
}
if (!dictionary.ContainsKey("code") || !dictionary.ContainsKey("state"))
{
return null;
}
if (dictionary["state"] != stateGuid)
return null;
string tokenRequestBody = $"code={dictionary["code"]}&redirect_uri={Uri.EscapeDataString(GoogleRedirectURI)}&client_id={GoogleAccountClientId}&access_type=offline&scope=&grant_type=authorization_code";
StringContent content = new StringContent(tokenRequestBody, Encoding.UTF8, "application/x-www-form-urlencoded");
// Performs the authorization code exchange.
using (HttpClientHandler handler = new HttpClientHandler())
{
handler.AllowAutoRedirect = true;
using (HttpClient client = new HttpClient(handler))
{
HttpResponseMessage response = await client.PostAsync(GoogleTokenEndpoint, content);
if (response.IsSuccessStatusCode)
{
var stringResponse = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(stringResponse);
var id = DecodeIdFromJWT((string)json["id_token"]);
var oauthToken = new AuthResult()
{
Provider = AccountType.Google,
AccessToken = (string)json["access_token"],
Expiration = DateTimeOffset.Now + TimeSpan.FromSeconds(int.Parse((string)json["expires_in"])),
Id = id,
IdToken = (string)json["id_token"],
ExpirationInSeconds = long.Parse((string)json["expires_in"]),
IssueDateTime = DateTime.Now,
RefreshToken = (string)json["refresh_token"]
};
return oauthToken;
}
else
{
return null;
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return null;
}
}

Using Google APIs Client Library for .NET in portable library

They write:"Supported Platforms: Portable Class Libraries".
But then I write this code in portable class, I have a error:
public async void MyFuncrion()
{
UserCredential credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
//new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read),
new ClientSecrets
{
ClientId = "", //"PUT_CLIENT_ID_HERE",
ClientSecret = "" //"PUT_CLIENT_SECRETS_HERE"
},
new[] { TasksService.Scope.Tasks },
"user",
CancellationToken.None);
var service = new TasksService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Tasks API Sample",
});
TaskLists results = await service.Tasklists.List().ExecuteAsync();
foreach (var tasklist in results.Items)
{
TasklistTitlesCollection.Add(tasklist.Title + " " + tasklist.Updated);
// You can get data from the file (using file.Title for example)
// and append it to a TextBox, List, etc.
}
}
Error here: "UserCredential credential = await GoogleWebAuthorizationBroker.AuthorizeAsync()", He doen't work in portable library. How can I use library, get tasks without GoogleWebAuthorizationBroker. I already get access token myself and I need only TasksService, maybe I can past my access token and other in the constructor TasksService?
I use this article to breake this wall google .net library
Here i past some my code from PCL and windows8.
PCL:
You need provide DataStore
private async Task<GoogleAuthorizationCodeFlow.Initializer> InitInitializer()
{
_iDataStore = await _userCredential.GetDataStore(); //new StorageDataStore()
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = ClientId, //"PUT_CLIENT_ID_HERE",
ClientSecret = ClientSecret, //"PUT_CLIENT_SECRET_HERE"
},
Scopes = new[] { TasksService.Scope.Tasks },
DataStore = (Google.Apis.Util.Store.IDataStore)_iDataStore //new StorageDataStore()
};
return initializer;
}
Then
public async Task<TasksService> Prepare()
{
GoogleAuthorizationCodeFlow.Initializer initializer = await InitInitializer();
Object credential = new Object();
if (String.IsNullOrEmpty(_username))
{
return null;
}
TasksService service = null;
try
{
credential = await _userCredential.GetCredential(initializer, _username);
}
catch (Google.Apis.Auth.OAuth2.Responses.TokenResponseException e)
{
service = null;
return service;
}
catch
{
return null;
}
service = new TasksService(new BaseClientService.Initializer
{
HttpClientInitializer = (UserCredential)credential,
ApplicationName = _applicationName,
});
return service;
}
1) In Windows store you need provide StorageDataStore and traverse it to pcl.
2) Use
AuthorizationCodeWinRTInstalledApp(initializer).AuthorizeAsync(username, new CancellationToken(false))
from google library (Google.Apis.Auth.OAuth2) to get your credential and traverse it to pcl
You have two options:
1.Declare foo as async method:
async void Foo()
2.Remove await, and get the result of your task, so your code should look something like:
serCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
//new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read),
new ClientSecrets
{
ClientId = "", //"PUT_CLIENT_ID_HERE",
ClientSecret = "" //"PUT_CLIENT_SECRETS_HERE"
},
new[] { TasksService.Scope.Tasks },
"user",
CancellationToken.None).Result;

Categories