Google Drive API (.NET) - Transfer ownership from Service Account to Domain user - c#

I am creating file on Google drive with .NET client API with Service account.
string[] scopes = new string[] { DriveService.Scope.Drive };
GoogleCredential credential;
using (var stream = new FileStream(Directory.GetCurrentDirectory() + "/Resources/GoogleCredentials.json", FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream).CreateScoped(scopes);
}
DriveService drive = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
});
I succesfully create file,
var f = drive.Files;
var request = f.Create(new Google.Apis.Drive.v3.Data.File()
{
Name = "Test from ASP.NET Core",
AppProperties = prop,
MimeType = "application/vnd.google-apps.document"
});
var file = await request.ExecuteAsync();
share it with all domain, but I can not transfer ownership to a domain user.
Permission permission = new Permission()
{
EmailAddress = "user#example.com",
Type = "user",
Domain = "example.com",
Role = "owner"
};
var requestpermission = drive.Permissions.Create(permission, file.Id);
requestpermission.TransferOwnership = true;
var perm = await requestpermission.ExecuteAsync();
I get error:
The specified domain is invalid or not applicable for the given
permission type.
I found this link, but using p12 cert file is not recommended. So I want to use JSON.

Ownership transfers can only be done between users in the same domain, and service accounts don't belong to any domain. You're best option may be to create a Team Drive that the service account has access to, and perform a two stage process:
Use the service account to move the file into the team drive. Files.update with the addParents parameter set to the Team Drive ID.
Use the domain user to move the file out of the team drive. Files.update with the addParents parameter set to root (or some target folder's ID) and the removeParents parameter set to the Team Drive ID.

Related

Google APIs Adding a contact using PeopleService

I have a question about how correctly add a contact to Google Contacts using Google API.
For authorization I use external Json file Generated.
When I execute it , it doesn't give any mistakes but No Contact is Added to Google Contacts.
What can be wrong with the code?
Please find code below
Thanks
private async Task Run()
{
GoogleCredential credential;
using (Stream stream = new FileStream(#"D:\project1.json", FileMode.Open, FileAccess.Read, FileShare.Read))
{
credential = GoogleCredential.FromStream(stream);
}
string[] scopes = new string[] {
PeopleServiceService.Scope.Contacts,
PeopleServiceService.Scope.ContactsReadonly,
PeopleServiceService.Scope.ContactsOtherReadonly,
};
credential = credential.CreateScoped(scopes);
BaseClientService.Initializer initializer = new BaseClientService.Initializer()
{
HttpClientInitializer = (IConfigurableHttpClientInitializer)credential,
ApplicationName = "Project1",
GZipEnabled = true,
};
PeopleServiceService service = new PeopleServiceService(initializer);
Person contactToCreate = new Person();
List<Name> names = new List<Name>();
names.Add(new Name() { GivenName = "Alex", FamilyName = "Breen", DisplayName = "Alex Breen" });
contactToCreate.Names = names;
List<PhoneNumber> phoneNumbers = new List<PhoneNumber>();
phoneNumbers.Add(new PhoneNumber() { Value = "11-22-33" });
contactToCreate.PhoneNumbers = phoneNumbers;
List<EmailAddress> emailAddresses = new List<EmailAddress>();
emailAddresses.Add(new EmailAddress() { Value = "AlexBreen#mail.com" });
contactToCreate.EmailAddresses = emailAddresses;
PeopleResource.CreateContactRequest request = new PeopleResource.CreateContactRequest(service, contactToCreate);
Person createdContact = request.Execute();
Console.WriteLine(request);
}
Results
Metrics
You need to go though your service object.
var request = service.People.CreateContact(new Person()
{
Names = new List<Name>() { new Name() { DisplayName = "test"}}
// Fill in the rest of the person object here.
});
var response = request.Execute
Make sure you are checking google contacts from the same user you are authenticating your application from.
The response should be returning the new user.
all contacts for a user
You can also test it by doing. This will give you a list of the users inserted for the user you have authorized.
var results = service.People.Connections.List("people/me").Execute();
who is the current user
var results = service.People.Get("people/me").Execute();
var results = service.People.Connections.List("people/me").Execute();
service accounts
A service account is not you. Think of a service account more as a dummy user it has its own Google contacts account. When you insert into it you are inserting into the account owned by the service account.
If you have google workspace you can set up domain wide deligation to the service account and then delegate to users on the domain and add contacts to their google contacts within the domain.
You can not use a service account to write to a standard google gmail user's google contacts. For that you would need to use Oauth2 and authorize the user to access their google contacts.
Based on the comments on the previous answer:
You are inserting contacts to a service account
To use your own account you have to create an OAuth client ID and then use the credentials.json to authorise on the code.
There is no C# sample on the People API samples but you can check on how to use this credentials based on the .NET quickstart for Drive API but without using Drive API scopes and code.
Basically using this part of the code:
UserCredential credential;
using(var stream =
new FileStream("credentials.json", FileMode.Open, FileAccess.Read))
{
// The file token.json stores the user's access and refresh tokens, and is created
// automatically when the authorization flow completes for the first time.
string credPath = "token.json";
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
Console.WriteLine("Credential file saved to: " + credPath);
}

Listing HTML files on my Google Site using Google.Apis and c#

I'm wanting to list all the pages on my google site using the Google.Apis. Using the Google.Apis.Drive.v3 methods, I'm able to list the contents of my Drive account, but I'm only getting one entry for my site - the title with a MimeType of "application/vnd.google-apps.site". I'm using a service account to connect which seems to be working. Is there another way to interact with the files on a Google Site? Here is my code:
string[] scopes = { DriveService.Scope.Drive, DriveService.Scope.DriveFile, DriveService.Scope.DriveAppdata, DriveService.Scope.DriveMetadata, DriveService.Scope.DriveScripts };
//open the service account private key file
using (var stream = new FileStream("appName-bc2d6ad1c3bb.json", FileMode.Open, FileAccess.Read))
{
//create a new service credential
var credential = GoogleCredential.FromStream(stream);
credential = credential.CreateScoped(scopes);
//create a new Drive service
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "appName",
});
var files = service.Files.List().Execute().Files;
foreach(var tmpFile in files)
{
//Only lists the site name, not the individual HTML files
Console.WriteLine(tmpFile.Name + " : " + tmpFile.Id + " : " + tmpFile.MimeType);
}
}
First off you need to remember that a service account is not you. The service account isn't going to have access to the data unless you grant it access. Try going to your site and sharing the site with the service account email address.
Second are you sure these files are on Google drive?

Failure of delegation of Google Drive access to a service account

I've been involved with building an internal-use application through which users may upload files, to be stored within Google Drive. As it is recommended not to use service accounts as file owners, I wanted to have the application upload on behalf of a designated user account, to which the company sysadmin has access.
I have created the application, along with a service account. There are two keys created for the service account, as I have tried both the JSON and PKCS12 formats trying to achieve this:
I have downloaded the OAuth 2.0 client ID details, and also have the .json and .p12 files for the service account keys (in that order as displayed above):
I had my sysadmin go through the steps detailed here to delegate authority for Drive API access to the service account: https://developers.google.com/drive/v2/web/delegation#delegate_domain-wide_authority_to_your_service_account
We found that the only thing that worked for the "Client name" in step 4 was the "Client ID" listed for the Web application (ending .apps.googleusercontent.com). The long hexadecimal IDs listed for the Service account keys were not what it required (see below):
Previously to the above, I had code which would create a DriveService instance that could upload directly to the service account, referencing the .json file for the service account keys:
private DriveService GetServiceA()
{
var settings = SettingsProvider.GetInstance();
string keyFilePath = HostingEnvironment.MapPath("~/App_Data/keyfile.json");
var scopes = new string[] { DriveService.Scope.Drive };
var stream = new IO.FileStream(keyFilePath, IO.FileMode.Open, IO.FileAccess.Read);
var credential = GoogleCredential.FromStream(stream);
credential = credential.CreateScoped(scopes);
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "MyAppName"
});
return service;
}
That works for listing and uploading, though of course there's no web UI for access to the files, and it seems as though it doesn't handle things like permissions metadata or generation of thumbnails for e.g. PDFs. This is why I'm trying to use a standard account for the uploads.
Once the delegation was apparently sorted, I then attempted to adapt the code shown in the delegation reference linked above, combining with code from elsewhere for extracting the necessary details from the .json key file. With this code, as soon as I try to execute any API command, even as simple as:
FileList fileList = service.FileList().Execute();
I receive an error:
Exception Details: Google.Apis.Auth.OAuth2.Responses.TokenResponseException: Error:"unauthorized_client", Description:"Unauthorized client or scope in request.", Uri:""
The code for that effort is:
private DriveService GetServiceB()
{
var settings = SettingsProvider.GetInstance();
string keyFilePath = HostingEnvironment.MapPath("~/App_Data/keyfile.json");
string serviceAccountEmail = "<account-email>#<project-id>.iam.gserviceaccount.com";
var scopes = new string[] { DriveService.Scope.Drive };
var stream = new IO.FileStream(keyFilePath, IO.FileMode.Open, IO.FileAccess.Read);
var reader = new IO.StreamReader(stream);
string jsonCreds = reader.ReadToEnd();
var o = JObject.Parse(jsonCreds);
string privateKey = o["private_key"].ToString();
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes,
User = "designated.user#sameappsdomain.com"
}
.FromPrivateKey(privateKey)
);
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "MyAppName"
});
return service;
}
Finally, I created the second service account key to save a .p12 file in order to more closely match the code in the authority delegation documentation, but which results in the same exception:
private DriveService GetServiceC()
{
var settings = SettingsProvider.GetInstance();
string p12KeyFilePath = HostingEnvironment.MapPath("~/App_Data/keyfile.p12");
string serviceAccountEmail = "<account-email>#<project-id>.iam.gserviceaccount.com";
var scopes = new string[] { DriveService.Scope.Drive }; // Full access
X509Certificate2 certificate = new X509Certificate2(
p12KeyFilePath,
"notasecret",
X509KeyStorageFlags.Exportable
);
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes,
User = "designated.user#sameappsdomain.com"
}
.FromCertificate(certificate)
);
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "MyAppName"
});
return service;
}
The minimial relevant class where this method lives is:
public class GoogleDrive
{
public DriveService Service { get; private set; }
public GoogleDrive()
{
this.Service = this.GetService();
}
private DriveService GetService()
{
// Code from either A, B or C
}
public FilesResource.ListRequest FileList()
{
return this.Service.Files.List();
}
}
And that's used in this fashion:
var service = new GoogleDrive();
FilesResource.ListRequest listRequest = service.FileList();
FileList fileList = listRequest.Execute();
The exception occurs on that last line.
I do not understand why my service account cannot act on behalf of the designated user, which is part of the domain for which the application's service account should have delegated authority. What is it that I've misunderstood here?
I have found the answer myself, and it was configuration, not code. The link I shared with the steps for delegation of authority does not mention an option available when creating the service account: a checkbox saying that the account will be eligible for domain-wide delegation (DwD).
This link describes the service account creation and delegation more accurately: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
I did not know about DwD when I created the service account, and so I had not selected that option. It is possible to go back and edit a service account to select it. Once I did this, I was able to retrieve a correct client ID for use in the "Manage API Client Access" part of the admin console. Using the GetServiceC() method then works as intended, and I am able to retrieve files for users in the same Apps domain.
This is the checkbox that needs to be ticked for a service account to be eligible for domain-wide delegation of authority:
This is the extra information available once you've done that (with a throwaway service account alongside that did not have the box ticked, for comparison):
You may tick the checkbox Enable G Suite Domain-wide Delegation, when you create the service account on the admin panel.
Regards
Most everything looks ok but:
A. Use ServiceC code, not sure if the object typing matters but your line:
var credential = new ServiceAccountCredential...
should be
ServiceAccountCredential credential = new ServiceAccountCredential...
B. Check that the P12 file in ServiceC is the real P12 file you actually uploaded to your environment where you're running this.
C. update your question with the exact runable code you're using to create and invoke your service:filelist:execute code. This way there's more clarity and less assumptions.

Unable To upload file to google drive - using c#

All code runs without errors, but when I check my Google Drive account I can't find the file I am uploading ("document.txt").
Also it has asked me for Authentication again.
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = "Here my clientid",
ClientSecret = "client secret",
},
new[] { DriveService.Scope.Drive },
"user",
CancellationToken.None).Result;
// Create the service.
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Drive API Sample",
});
File body = new File();
body.Title = "My document";
body.Description = "A test document";
body.MimeType = "text/plain";
byte[] byteArray = System.IO.File.ReadAllBytes("document.txt");
System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray);
FilesResource.InsertMediaUpload request = service.Files.Insert(body, stream, "text/plain");
request.Upload();
File file = request.ResponseBody;
Questions:
Why cant I find my uploaded file, and how can I get it to remember my authentication.
I think you are forgetting body.Parent so it doesn't know what directory to place the file into.
parents[] list Collection of parent folders which contain this file.
Setting this field will put the file in all of the provided folders.
On insert, if no folders are provided, the file will be placed in the
default root folder.
example:
body.Parents = new List<ParentReference>() { new ParentReference() { Id = 'root' } };
You are getting asked for authentication again because you aren't saving authentication.
//Scopes for use with the Google Drive API
string[] scopes = new string[] { DriveService.Scope.Drive,
DriveService.Scope.DriveFile};
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
UserCredential credential =
GoogleWebAuthorizationBroker
.AuthorizeAsync(new ClientSecrets { ClientId = CLIENT_ID
, ClientSecret = CLIENT_SECRET }
,scopes
,Environment.UserName
,CancellationToken.None
,new FileDataStore("Daimto.GoogleDrive.Auth.Store")
).Result;
FileDataStore stores the authentication data in the %appdata% directory.
More detailed information can be found in the tutorial Google Drive API with C# .net – Upload
Update For the following error:
"The API is not enabled for your project, or there is a per-IP or
per-Referer restriction configured on your API key and the request
does not match these restrictions. Please use the Google Developers
Console to update your configuration. [403]"
Go to Developer console for your project here Under APIs & auth -> APIs enable Google drive API and sdk. Also go to credentials and make sure you added a product name and email.

Can we access GMAIL API using Service Account?

I have a desktop application to read mail using GMAIL API over REST Interface. I want to use service account so that we can download the mails using domain setting and user interaction is null. I am successfully able to create Gmail Service instance but when I try to access any Gmail API method like fetching mail list or any other I get an exception saying
Google.Apis.Auth.OAuth2.Responses.TokenResponseException:
Error:"access_denied", Description:"Requested client not
authorized."
I am done with all the setting at developer console and added scopes to my gapps domain.
Does Gmail API support service account? Using the same setting and service account I am able to get list of all files in Google drive using Drive service and API.
I use the following C# code for accessing Gmail from Service Account
String serviceAccountEmail =
"999999999-9nqenknknknpmdvif7onn2kvusnqct2c#developer.gserviceaccount.com";
var certificate = new X509Certificate2(
AppDomain.CurrentDomain.BaseDirectory +
"certs//fe433c710f4980a8cc3dda83e54cf7c3bb242a46-privatekey.p12",
"notasecret",
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
string userEmail = "user#domainhere.com.au";
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = userEmail,
Scopes = new[] { "https://mail.google.com/" }
}.FromCertificate(certificate)
);
if (credential.RequestAccessTokenAsync(CancellationToken.None).Result)
{
GmailService gs = new GmailService(
new Google.Apis.Services.BaseClientService.Initializer()
{
ApplicationName = "iLink",
HttpClientInitializer = credential
}
);
UsersResource.MessagesResource.GetRequest gr =
gs.Users.Messages.Get(userEmail, msgId);
gr.Format = UsersResource.MessagesResource.GetRequest.FormatEnum.Raw;
Message m = gr.Execute();
if (gr.Format == UsersResource.MessagesResource.GetRequest.FormatEnum.Raw)
{
byte[] decodedByte = FromBase64ForUrlString(m.Raw);
string base64Encoded = Convert.ToString(decodedByte);
MailMessage msg = new MailMessage();
msg.LoadMessage(decodedByte);
}
}
Here is a little bit of python 3.7:
from google.oauth2 import service_account
from googleapiclient.discovery import build
def setup_credentials():
key_path = 'gmailsignatureproject-zzz.json'
API_scopes =['https://www.googleapis.com/auth/gmail.settings.basic',
'https://www.googleapis.com/auth/gmail.settings.sharing']
credentials = service_account.Credentials.from_service_account_file(key_path,scopes=API_scopes)
return credentials
def test_setup_credentials():
credentials = setup_credentials()
assert credentials
def test_fetch_user_info():
credentials = setup_credentials()
credentials_delegated = credentials.with_subject("tim#vci.com.au")
gmail_service = build("gmail","v1",credentials=credentials_delegated)
addresses = gmail_service.users().settings().sendAs().list(userId='me').execute()
assert gmail_service
If you want to "read mail" you'll need the newer Gmail API (not the older admin settings API that 'lost in binary' pointed out). Yes you can do this with oauth2 and the newer Gmail API, you need to whitelist the developer in Cpanel and create a key you can sign your requests with--it take a little bit to setup:
https://developers.google.com/accounts/docs/OAuth2ServiceAccount#formingclaimset
For C# Gmail API v1, you can use the following code to get the gmail service. Use gmail service to read emails. Once you create the service account in Google Console site, download the key file in json format. Assuming the file name is
"service.json".
public static GoogleCredential GetCredenetial(string serviceAccountCredentialJsonFilePath)
{
GoogleCredential credential;
using (var stream = new FileStream(serviceAccountCredentialJsonFilePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream)
.CreateScoped(new[] {GmailService.Scope.GmailReadonly})
.CreateWithUser(**impersonateEmail#email.com**);
}
return credential;
}
public static GmailService GetGmailService(GoogleCredential credential)
{
return new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Automation App",
});
}
// how to use
public static void main()
{
var credential = GetCredenetial("service.json");
var gmailService = GetGmailService(credential);
// you can use gmail service to retrieve emails.
var mMailListRequest = gmailService.Users.Messages.List("me");
mMailListRequest.LabelIds = "INBOX";
var mailListResponse = mMailListRequest.Execute();
}
Yes you can... check the delegation settings...
https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account
Edit: Use the link Eric DeFriez shared.
You can access any user#YOUR_DOMAIN.COM mails/labels/threads etc. with the new Gmail API:
https://developers.google.com/gmail/api/
via service account with impersonation (service account is accessing api as if it was specific user from your domain).
See details here: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
Here is relevant code in Dartlang:
import 'package:googleapis_auth/auth_io.dart' as auth;
import 'package:googleapis/gmail/v1.dart' as gmail;
import 'package:http/http.dart' as http;
///credentials created with service_account here https://console.developers.google.com/apis/credentials/?project=YOUR_PROJECT_ID
final String creds = r'''
{
"private_key_id": "FILL_private_key_id",
"private_key": "FILL_private_key",
"client_email": "FILL_service_account_email",
"client_id": "FILL_client_id",
"type": "service_account"
}''';
Future<http.Client> createImpersonatedClient(String impersonatedUserEmail, List scopes) async {
var impersonatedCredentials = new auth.ServiceAccountCredentials.fromJson(creds,impersonatedUser: impersonatedUserEmail);
return auth.clientViaServiceAccount(impersonatedCredentials , scopes);
}
getUserEmails(String userEmail) async { //userEmail from YOUR_DOMAIN.COM
var client = await createImpersonatedClient(userEmail, [gmail.GmailApi.MailGoogleComScope]);
var gmailApi = new gmail.GmailApi(client);
return gmailApi.users.messages.list(userEmail, maxResults: 5);
}

Categories