I am trying to get google users from my domain using google service account.
But it throws error
Error:"access_denied", Description:"Requested client not authorized.", Uri:""
My code
X509Certificate2 certificate = new X509Certificate2(key_path,
"notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer("publickey.gserviceaccount.com")
{ Scopes = scopes,
User = "admin#domain.com"
}.FromCertificate(certificate));
var service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "appname",
});
service.Users.List().Domain = "domain.com";
Users results = service.Users.List().Execute();
Thanks in advance
The service account email address needs to have access the domain. Take the email and add it as a user just enough access that it can read should be good.
Also did you change this for posting?
"publickey.gserviceaccount.com"
A service account email looks more like this:
539621478854-imkdv94bgujcom228h3ea33kmkoefhil#developer.gserviceaccount.com
You need to give your service-account/API project access to your domain first.
Steps detailed in the docs here:
https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account
You need to specify the correct scopes you need in step 6 of those instructions, which would be https://www.googleapis.com/auth/admin.directory.user.readonly to access the list of users.
In addition for the Directory API to work you need to enable API access in the domain settings: https://developers.google.com/admin-sdk/directory/v1/guides/prerequisites#set_up_api
I was finally able to get this working. Here is the code I have
var grpReq = service.Groups.List();
grpReq.Domain = "mydomain.com";
Groups groups = grpReq.Execute();
IList<Group> gps = groups.GroupsValue;
var memReq=service.Members.List(groups.GroupsValue[0].Id);
Members members = memReq.Execute();
I am still not sure why creating a var object and then Execute() got this to work but the earlier code didn't work.
I still have the problem of the consent screen showing up for all users. I have the following code. I think the way I get the logged in user's email is incorrect. Any ideas?
string mymail = googleauth.GetUsersEmail(ExchangeCodeWithAccessAndRefreshToken().Access_Token);
string path = "d:\\c6b82065f26fbb0-privatekey.p12";
X509Certificate2 certificate = new X509Certificate2(
path,
"notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer("876131792-v824u6drpss#developer.gserviceaccount.com")
{
User = mymail,
Scopes = new[] { PlusService.Scope.UserinfoEmail, PlusService.Scope.UserinfoProfile, PlusService.Scope.PlusMe }
}.FromCertificate(certificate));
PlusService plus = new PlusService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "myapp"
});
Person profile = plus.People.Get("me").Execute();
string email = profile.Emails[0].Value;
Related
I'm currently trying to integrate with the Google Admin SDK via C# so we can manage users via our own system. However, when running the project I get the error: Unauthorized Client.
Things I have already done via a super admin account:
Setup Service Account
Enabled GSuite domain-wide delegation on service Account
Enabled API Access
Added the Service Accounts client ID to API Client Access with the scope (https://www.googleapis.com/auth/admin.directory.user)
Here's the code that i'm using.
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(_googleServiceSettings.Client_Email)
{
ProjectId = _googleServiceSettings.Project_Id,
User = "superadmin#google.com",
Scopes = new[] { DirectoryService.Scope.AdminDirectoryUser }
}.FromPrivateKey(_googleServiceSettings.Private_Key));
var service = new DirectoryService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "Test API"
});
var request = service.Users.Get("user#google.com");
var result = await request.ExecuteAsync();
The full error i'm getting is
An unhandled exception has occurred while executing the request.
Google.Apis.Auth.OAuth2.Responses.TokenResponseException: Error:"unauthorized_client", Description:"Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.", Uri:""
Example code that will print some information about a user.
The important item is the class Google.Apis.Admin.Directory.directory_v1.Data.User
Documentation link.
Your error is caused by not creating the credentials correctly. Usually, an issue with scopes when creating the credentials. I am assuming that you have Domain-Wide Delegation setup correctly for the service account.
I am also assuming that the user that you are impersonating is a G Suite Super Admin. If not, you will see a 403 error for service.Users.Get().
The file service_account.json is a normal JSON file that you downloaded from the Google Console (or created with gcloud).
The user user1#example.com is the email address for the G Suite user for which information will be displayed.
The user admin#example.com is the G Suite Super Admin.
using Google.Apis.Auth.OAuth2;
using Google.Apis.Admin.Directory.directory_v1;
using Google.Apis.Admin.Directory.directory_v1.Data;
using Google.Apis.Services;
using System;
using System.IO;
// dotnet add package Google.Apis.Admin.Directory.directory_v1
// Tested with version 1.39.0.1505
// Google.Apis.Admin.Directory.directory_v1.Data.User
// https://developers.google.com/resources/api-libraries/documentation/admin/directory_v1/csharp/latest/classGoogle_1_1Apis_1_1Admin_1_1Directory_1_1directory__v1_1_1Data_1_1User.html
namespace Example
{
class Program
{
static void Main(string[] args)
{
// Service Account with Domain-Wide delegation
var sa_file = "service_account.json";
// G Suite User to impersonate
var user_email = "admin#example.com";
// G Suite User to get information about
var gs_email = "user1#example.com";
// Scopes
var scopes = "https://www.googleapis.com/auth/admin.directory.user";
var credential = GoogleCredential.FromFile(sa_file)
.CreateScoped(scopes)
.CreateWithUser(user_email);
// Create Directory API service.
var service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential
});
try {
var request = service.Users.Get(gs_email);
var result = request.Execute();
Console.WriteLine("Full Name: {0}", result.Name.FullName);
Console.WriteLine("Email: {0}", result.PrimaryEmail);
Console.WriteLine("ID: {0}", result.Id);
Console.WriteLine("Is Admin: {0}", result.IsAdmin);
} catch {
Console.WriteLine("User not found.");
}
}
}
}
If you want to use the service account you can authenticate with below code.
String serviceAccountEmail = "yourserviceaccountmail";
public GmailService GetService(string user_email_address)
{
var certificate = new X509Certificate2(#"yourkeyfile.p12",
"notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = user_email_address,
Scopes = new[] { GmailService.Scope.MailGoogleCom }
}.FromCertificate(certificate));
GmailService service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = AppName,
});
return service;
}
You can list users using this service. Its work for me.
And you can list userlist with below code. ( with DirectoryService)
public Users GetDirService()//UserList with DirectoryService
{
string Admin_Email = "yoursuperadminemail";
string domain = "yourdomain.com";
try
{
var certificate = new X509Certificate2(#"yourkeyfile.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credentialUsers = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { DirectoryService.Scope.AdminDirectoryUser },
User = Admin_Email,
}.FromCertificate(certificate));
var serviceUsers = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentialUsers,
ApplicationName = AppName,
});
var listReq = serviceUsers.Users.List();
listReq.Domain = domain;
Users users = listReq.Execute();
return users;
}
catch (Exception ex)
{
MessageBox.Show("your mail address must be super admin authorized.", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
return null;
}
}
Ok I have solved the issue.
Adding the following scope via the security settings within the Google Portal has solved the issue. This is strange as their own example doesn't require this scope to be added ad their documentation doesn't say it's required for this method.
https://www.googleapis.com/auth/admin.directory.group
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.
As per different threads here on stackoverflow, it seems that I can create a service account for any regular gmail account. But cannot assign permissions from the domain admin panel.
Can anyone confirm if ServiceAccount can be used to download Emails from any regular gmail account?
If I use the following code:
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = userEmail,
Scopes = new string[] { Gmail.v1.GmailService.Scope.GmailReadonly }
}.FromCertificate(certificate)
);
then I get the following error:
{"Error:\"unauthorized_client\", Description:\"Unauthorized client or scope in request.\", Uri:\"\""}
And If I remove the User object from the ServiceAccountCredential constructor then the error is gone. But have the following error when I use any google service:
{"Google.Apis.Requests.RequestError\r\nBad Request [400]\r\nErrors [\r\n\tMessage[Bad Request] Location[ - ] Reason[failedPrecondition] Domain[global]\r\n]\r\n"}
I did set the 'Viewer' role for the service account from the developer console and enabled the google APIs.
Is this anyway related to domain account or permissions for the domain account?
Full Code is as following:
String serviceAccountEmail = "gmailscraperserviceaccount2#gmailscraper-1221.iam.gserviceaccount.com";
var certificate = new X509Certificate2("key2.p12", "notasecret",
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
string userEmail = "abc#gmail.com";
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new string[] { Gmail.v1.GmailService.Scope.GmailReadonly }
}.FromCertificate(certificate)
);
if (credential.RequestAccessTokenAsync(CancellationToken.None).Result)
{
Google.Apis.Auth.OAuth2.Responses.TokenResponse toke = credential.Token;
GmailService gs = new GmailService(
new Google.Apis.Services.BaseClientService.Initializer()
{
ApplicationName = "TestApp",
HttpClientInitializer = credential
}
);
UsersResource.MessagesResource.ListRequest allMessageRequest = gs.Users.Messages.List(userEmail);
Google.Apis.Gmail.v1.Data.ListMessagesResponse response = allMessageRequest.Execute();
this this code snniped:
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,//read from client secret.json file
Scopes,
"user",
CancellationToken.None).Result;
// Create Gmail API service.
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
UsersResource.LabelsResource.ListRequest request = service.Users.Labels.List("me");
WatchRequest body = new WatchRequest()
{
TopicName = "projects/push-notifications-ver3/topics/mytopic",
LabelIds = new[] {"INBOX"}
string userId = "me";
UsersResource.WatchRequest watchRequest = service.Users.Watch(body, userId);
WatchResponse test = watchRequest.Execute();
Getting Error:
Error sending test message to Cloud PubSub projects/push-notifications-ver3/topics/mytopic : User not authorized to perform this action. [403]
Topic was created with subscription, permission was given to current user as owner of topic
Any suggestion why user not authorized ?
Have you completed the OAuth process for the given user? Also, are you replacing the word "user" in the method AuthorizeAsync() with your authenticated user? If yes, then try to do it with new client secrets file and also check if PubSub Scope is present in the variable scope.
I face a similar issue and it turned out to be one of these issues. Might work for you as well.
I'm successfully using service accounts for domain-wide authority to access users' drive files and calendars as follows:
ServiceAccountCredential.Initializer initializer =
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = serviceAccountScope,
User = "user1#myDomain.com"
}.FromCertificate(certificate);
ServiceAccountCredential credential = new ServiceAccountCredential(initializer);
service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = appName,
});
However, I'm not entirely sure the best way to change users, especially if I'm dealing with many users at once. Is it best to just start over and create a new initializer and service for each user? Or is it possible to keep the same service and just update the imitated user?
The way ServiceAccountCredential was designed is that it's thread safe, so you can't change the User data. However, we may change the current getter property User to be similar Token (in the way that we look when getting and setting it). Please feel free to open a new issue in our issue tracker and elaborate more.
For now, you should use a new service and for each "user", as following:
ServiceAccountCredential.Initializer initializer =
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = serviceAccountScope,
User = "user1#myDomain.com"
}.FromCertificate(certificate);
var service1 = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = new ServiceAccountCredential(initializer),
ApplicationName = appName,
});
initializer.User = "user2#myDomain.com"
var service2 = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = new ServiceAccountCredential(initializer),`
ApplicationName = appName,
});