AssertionFlowClient depreceated, trying to use ServiceAccountCredential, but it won't work - google-drive-api

I've tried creating a DriveService using service account acting on behalf of another user.
I've copied this code from google documentation found here https://developers.google.com/drive/delegation
static DriveService BuildService() {
X509Certificate2 certificate = new X509Certificate2(SERVICE_ACCOUNT_PKCS12_FILE_PATH, "notasecret", X509KeyStorageFlags.Exportable);
var provider = new AssertionFlowClient(GoogleAuthenticationServer.Description, certificate)
{
ServiceAccountId = SERVICE_ACCOUNT_EMAIL,
Scope = DriveService.Scopes.Drive.GetStringValue(),
};
var auth = new OAuth2Authenticator<AssertionFlowClient>(provider, AssertionFlowClient.GetState);
return new DriveService(auth);
}
but I get this warning when trying to build the project:
Warning 4 'Google.Apis.Authentication.OAuth2.DotNetOpenAuth.AssertionFlowClient' is obsolete: 'AssertionFlowClient is not supported any more and it's going to be removed in 1.7.0-beta. Consider using ServiceAccountCredential from the new Google.Apis.Auth NuGet package.'
and I also get this error:
Error 11 Argument 1: cannot convert from 'Google.Apis.Authentication.OAuth2.OAuth2Authenticator' to 'Google.Apis.Services.BaseClientService.Initializer'
Then I googled ServiceAccountCredential and ended up with this code (derived from this page: https://code.google.com/p/google-api-dotnet-client/wiki/OAuth2#Service_Accounts)
static DriveService BuildService() {
X509Certificate2 certificate = new X509Certificate2(SERVICE_ACCOUNT_PKCS12_FILE_PATH, "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(SERVICE_ACCOUNT_EMAIL)
{
User = "someone#mydomain.mygbiz.com",
Scopes = new[] { DriveService.Scope.DriveFile }
}.FromCertificate(certificate));
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Drive API Sample",
});
return service;
}
When I try to build this code it seems all fine, but when I run it I get the following error.
A first chance exception of type 'System.Security.Cryptography.CryptographicException' occurred in mscorlib.dll
Additional information: Det går inte att hitta det begärda objektet.
(Translated : The requested object cannot be found)
If there is a handler for this exception, the program may be safely continued.
The error occurs on this line:
X509Certificate2 certificate = new X509Certificate2(SERVICE_ACCOUNT_PKCS12_FILE_PATH, "notasecret", X509KeyStorageFlags.Exportable);
Anyone have any ideas?
Update 2013 Oct 31
I have tried this code:
{
Console.WriteLine("Drive API - Service Account");
Console.WriteLine("==========================");
String serviceAccountEmail = "<some email>#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"key.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = "<someuser>#<mydomain>.mygbiz.com",
Scopes = new[] { DriveService.Scope.Drive }
}.FromCertificate(certificate));
// Create the service.
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "DrvMgr",
});
Console.WriteLine("Executing listing");
FileList UserFiles = service.Files.List().Execute();
I get this error message:
An unhandled exception of type 'Google.Apis.Auth.OAuth2.Responses.TokenResponseException' occurred in Google.Apis.dll
Additional information: Error:"access_denied", Description:"", Uri:""

It looks like the path to your p12 file is incorrect. See the Plus.ServiceAccount sample for a working solution.
I think that in this sample, the key.p12 was added to the project as a content file which is always copied to the output directory. Then mentioning "key.p12" file path from the code will result in grabbing this file from the output folder.

Related

FileStream - Path found in console-application but not in mvc

Iv'e used this guide drive quickstart and successfully got it worked in a console-app.
Now I'm trying to do the same thing in an mvc-application, but I got error when creating a new FileStream.
The code a use is exactly the same in mvc instead this time i'ts triggered by a button-click. This is my code:
using (var stream = new FileStream("client_secret.json", FileMode.Open, FileAccess.Read))
{
string credPath = System.Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(credPath, ".credentials/drive-dotnet-quickstart.json");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
Console.WriteLine("Credential file saved to: " + credPath);
}
// Create Drive API service.
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
// Define parameters of request.
FilesResource.ListRequest listRequest = service.Files.List();
listRequest.PageSize = 10;
listRequest.Fields = "nextPageToken, files(id, name)";
// List files.
IList<Google.Apis.Drive.v3.Data.File> files = listRequest.Execute()
.Files;
I got error at 'using (var stream = new FileStream("client_secret.json", FileMode.Open, FileAccess.Read))'
My 'client_secret.json'-file is stored at the root of my mvc project.
And I've done the step: Select client_secret.json, and then go to the Properties window and set the Copy to Output Directory field to Copy always.
Why is this not working in my mvc-app?
The error I get is 'System.IO.FileNotFoundException' - '{"Cannot find file c:\windows\system32\inetsrv\client_secret.json.":"c:\windows\system32\inetsrv\client_secret.json"}'

gsutil on a Google Compute Engine VM can't use service account authentication with a key file

I'm launching an instance from the google .net API and despite my best efforts I can't get it to copy anything to or from storage. Currently I'm authenticating with a developer console service account like this:-
string ServiceAccountEmail = "blahblah#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"key.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(ServiceAccountEmail)
{
Scopes = new[] { ComputeService.Scope.Compute, ComputeService.Scope.DevstorageFullControl }
}.FromCertificate(certificate));
var cs = new ComputeService(new BaseClientService.Initializer
{
ApplicationName = "appname",
HttpClientInitializer = (Google.Apis.Http.IConfigurableHttpClientInitializer)credential,
});
Google.Apis.Compute.v1.Data.Instance newinst = new Google.Apis.Compute.v1.Data.Instance();
newinst.Name = "generatedinstance";
newinst.MachineType = "https://www.googleapis.com/compute/v1/projects/projectid/zones/zone/machineTypes/n1-standard-1";
Google.Apis.Compute.v1.Data.AttachedDisk ad = new Google.Apis.Compute.v1.Data.AttachedDisk();
ad.AutoDelete = true;
ad.Boot = true;
ad.Type = "PERSISTENT";
ad.InitializeParams = new Google.Apis.Compute.v1.Data.AttachedDiskInitializeParams();
ad.InitializeParams.DiskName = "newdisk";
ad.InitializeParams.SourceImage = "https://www.googleapis.com/compute/v1/projects/projectid/global/images/customimage";
ad.InitializeParams.DiskType = "https://www.googleapis.com/compute/v1/projects/projectid/zones/zone/diskTypes/pd-standard";
ad.Mode = "READ_WRITE";
newinst.Disks = new List<Google.Apis.Compute.v1.Data.AttachedDisk>();
newinst.Disks.Add(ad);
Google.Apis.Compute.v1.Data.NetworkInterface ni = new Google.Apis.Compute.v1.Data.NetworkInterface();
ni.Network = "https://www.googleapis.com/compute/v1/projects/projectid/global/networks/default";
ni.AccessConfigs = new List<Google.Apis.Compute.v1.Data.AccessConfig>();
ni.AccessConfigs.Add(new Google.Apis.Compute.v1.Data.AccessConfig
{
Type = "ONE_TO_ONE_NAT",
Name = "External NAT",
});
newinst.NetworkInterfaces = new List<Google.Apis.Compute.v1.Data.NetworkInterface>();
newinst.NetworkInterfaces.Add(ni);
var start = new Google.Apis.Compute.v1.Data.Metadata.ItemsData();
start.Key = "startup-script";
start.Value = "*startup script* includes gsutil cp which won't work without service account attached";
newinst.Metadata = new Google.Apis.Compute.v1.Data.Metadata();
newinst.Metadata.Kind = "compute#metadata";
newinst.Metadata.Items = new List<Google.Apis.Compute.v1.Data.Metadata.ItemsData>();
newinst.Metadata.Items.Add(start);
newinst.ServiceAccounts = new List<Google.Apis.Compute.v1.Data.ServiceAccount>();
//var sa = new Google.Apis.Compute.v1.Data.ServiceAccount();|with this section
//sa.Email = "blahblah#developer.gserviceaccount.com"; |the instance won't
//sa.Scopes = new[] { ComputeService.Scope.Compute, |start. (An equivalent
ComputeService.Scope.DevstorageFullControl }; |is found in instance
//newinst.ServiceAccounts.Add(sa); |start REST request)
var instinsert = new InstancesResource.InsertRequest(cs, newinst, "projectid", "zone");
var insertresponse = instinsert.Execute();
The message I get when I try to use gsutil cp is "You do not currently have an active account selected.". Can anyone tell me where I'm going wrong?
You need to run gcloud auth activate-service-account blahblah#developer.gserviceaccount.com --key-file path_to_key.p12 to tell the Cloud SDK (including gsutil) about your service account.
As per the code provided, I can see that the original example has
var certificate = new X509Certificate2(#"key.p12", "notasecret", X509KeyStorageFlags.Exportable);
I notice you are missing the '#'in your code. I'm not very familiar with .Net. I have tested these examples in python and this one. When creating my instance I added the service account for GCS and the file is uploaded correctly.
OKAY! Problem solved. The part I was getting wrong was the bit commented out in the question-
var sa = new Google.Apis.Compute.v1.Data.ServiceAccount();
sa.Email = "blahblah#developer.gserviceaccount.com";
sa.Scopes = new[] { ComputeService.Scope.Compute,
ComputeService.Scope.DevstorageFullControl };
newinst.ServiceAccounts.Add(sa);
I needed the email for the main service account for the developer console in this section rather than the same service account I used to create the credentials but don't ask me why. Point is the instance launches and gsutil is now happily copying away.
Thanks for your time and help everyone!
Ross

How to access contacts API with Service Account

I am in the process of upgrading my Marketplace applications to support the new marketplace api and OAUTH 2.
I have managed to migrate most APIs but am stuck on the contacts api. With the the previous marketplace version we used 2LO and client key/client secret to authenticate across the Google Apps domain. My understanding is that the only way to do this in current version is with Service Accounts and OAuth 2.
Based on the V3 calendar API I'm assuming something like this (although the contacts API does not support it from what I can see) -
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { "https://www.google.com/m8/feeds" },
User = "admin#domain.co"
}.FromCertificate(certificate));
var service = new ContactsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Contact API Sample",
});
If anybody has done this, your advice will be appreciated!
I know this question has been answered, but adding an alternative that doesn't require the AssertionFlowClient or a specific version of DotNetOpenAuth. This allows you to use the same ServiceAccountCredential code from your original question (ie, using the same method to query a user's gmail)
Thank you for following up on your own post, it helped me find this solution!
private static ServiceAccountCredential GenerateCred(IEnumerable<string> scopes, string delegationUser)
{
var certificate = new X509Certificate2(certLocation, certPassword, X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes,
User = delegationUser
}.FromCertificate(certificate));
return credential;
}
var credential = GenerateCred(new[] { "https://www.googleapis.com/auth/contacts.readonly" }, userToDelegate);
//Get the token for this scope and user
await credential.RequestAccessTokenAsync(new CancellationToken());
//use the token to initalize the request
var rs = new RequestSettings(projectName)
{
OAuth2Parameters = new OAuth2Parameters()
{
AccessToken = credential.Token.AccessToken
}
};
var request = new ContactsRequest(rs);
Feed<Contact> f = request.GetContacts();
foreach (var c in f.Entries)
{
//process each contact
}
Well I got no response anywhere on this so I can only assume that the standard client libraries don't have support for this.
I have come up with a workaround based on the following post for email access.
http://www.limilabs.com/blog/oauth2-gmail-imap-service-account
You will need to follow his post and note the following:
this must use the DotNetOpenAuth version that he specifies. The latest version will not work.
You will only need his AssertionFlowClient classes which can be added to the below code I have provided here:
X509Certificate2 certificate = new X509Certificate2(
serviceAccountCertPath,
serviceAccountCertPassword,
X509KeyStorageFlags.Exportable);
AuthorizationServerDescription server = new AuthorizationServerDescription
{
AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
TokenEndpoint = new Uri("https://accounts.google.com/o/oauth2/token"),
ProtocolVersion = ProtocolVersion.V20,
};
AssertionFlowClient provider = new AssertionFlowClient(server, certificate)
{
ServiceAccountId = serviceAccountEmail,
Scope = string.Join(" ", new[] { "https://mail.google.com/", "https://www.google.com/m8/feeds/" }),
ServiceAccountUser = userEmail,
};
IAuthorizationState grantedAccess = AssertionFlowClient.GetState(provider);
RequestSettings rs = new RequestSettings("iLink");
rs.OAuth2Parameters = new OAuth2Parameters();
rs.OAuth2Parameters.AccessToken = grantedAccess.AccessToken;
rs.OAuth2Parameters.RefreshToken = null;
rs.OAuth2Parameters.ClientId = null;
rs.OAuth2Parameters.ClientSecret = null;
rs.OAuth2Parameters.RedirectUri = null;
ContactsRequest cr = new ContactsRequest(rs);
Feed<Contact> f = cr.GetContacts();
foreach (var c in f.Entries)
{
Response.Write(c.Name.FullName);
}

where are my upload to google drive's files for service account

I have the code below that uploads a file to my Google service account. It is working, but I want to know where is the file? Which account can I use to login to google drive and get the file and how much space can I use? Is there anything like google drive UI for service accounts?
private static Google.Apis.Drive.v2.Data.File insertFile(
String title,
String mimeType,
MemoryStream FileStream)
{
String serviceAccountEmail = "XXXXXX#developer.gserviceaccount.com";
var certificate = new X509Certificate2(
#"XXXXXXprivatekey.p12",
"notasecret",
X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { DriveService.Scope.Drive }
}.FromCertificate(certificate));
// Create the service.
DriveService service = new DriveService(
new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Drive API Sample",
});
// File's metadata.
Google.Apis.Drive.v2.Data.File body =
new Google.Apis.Drive.v2.Data.File();
body.Title = title;
body.Description = title;
body.MimeType = mimeType;
FilesResource.InsertMediaUpload request =
service.Files.Insert(body, FileStream, mimeType);
request.Upload();
Google.Apis.Drive.v2.Data.File file = request.ResponseBody;
return file;
}
There is no UI for Service Accounts. The files are only accessible to your app.

Exchange web service returns 401 unauthorized when run from web page

I am using EWS for sending email from web page. When I run this code in console application It's successfuly executed:
var sender = "sample#mail.com";
var fromPassword = "pass";
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
service.Credentials = new WebCredentials(sender, fromPassword);
service.Url = new Uri("https://testserver.com/EWS/Exchange.asmx");
ServicePointManager.ServerCertificateValidationCallback = delegate(object s,
X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
return true;
};
EmailMessage message = new EmailMessage(service);
message.Subject = "test";
message.Body = "test";
message.ToRecipients.Add("sample#gmail.com");
message.SendAndSaveCopy();
When I run the same code from web page in my sharepoint application I get error "401 unauthorized" when SendAndSaveCopy method executing.
What I am doing wrong?
Thanks.