How to retrieve Google group membership with a service account? - c#

I'm trying to retrieve/update Google group membership in a background process with a service account but getting this error when executing query. How to add these permissions to the service account?
Google.GoogleApiException: 'Google.Apis.Requests.RequestError
Not Authorized to access this resource/api [403]
Errors [
Message[Not Authorized to access this resource/api] Location[ - ] Reason[forbidden] Domain[global]
]
'
C# code:
using FileStream fileStream = File.OpenRead(#"key.json");
GoogleCredential googleCredential = GoogleCredential.FromStream(fileStream)
.CreateScoped("https://www.googleapis.com/auth/admin.directory.group.member");
BaseClientService.Initializer initializer = new()
{
HttpClientInitializer = googleCredential,
ApplicationName = "My App",
};
DirectoryService service = new(initializer);
MembersResource.ListRequest listRequest = service.Members.List("my-group#example.com");
Members members = await listRequest.ExecuteAsync();

It seems that you have to use setServiceAccountUser, which is not supported in JSON based credentials. You could build it like this:
import static com.google.api.client.googleapis.util.Utils.getDefaultJsonFactory;
import static com.google.api.client.googleapis.util.Utils.getDefaultTransport;
private static final String APPLICATION_NAME = "AnyAppName";
private final List<String> SCOPES = ImmutableList.of(
DirectoryScopes.ADMIN_DIRECTORY_GROUP_MEMBER, DirectoryScopes.ADMIN_DIRECTORY_USER, DirectoryScopes.ADMIN_DIRECTORY_GROUP);
private Directory service;
#PostConstruct
void init() throws GeneralSecurityException, IOException {
GoogleCredential credential;
try (InputStream is = new FileInputStream("./config/client_secret.json")) {
credential = GoogleCredential.fromStream(is);
}
GoogleCredential credentialWithUser = new GoogleCredential.Builder()
.setTransport(getDefaultTransport())
.setJsonFactory(getDefaultJsonFactory())
.setServiceAccountUser("admin#yourdomain.ru") // <--- mail of domain's admin
.setServiceAccountId(credential.getServiceAccountId())
.setServiceAccountScopes(SCOPES)
.setServiceAccountPrivateKey(credential.getServiceAccountPrivateKey())
.setServiceAccountPrivateKeyId(credential.getServiceAccountPrivateKeyId())
.setTokenServerEncodedUrl(credential.getTokenServerEncodedUrl()).build();
service = new Directory.Builder(getDefaultTransport(), getDefaultJsonFactory(), credentialWithUser).setApplicationName(APPLICATION_NAME).build();
}
public void members() throws IOException {
Members members = service.members().list("groupName#yourdomain.ru").execute();
System.out.println(members);
}
Reference:
Google Directory API returns Not Authorized when call users().list().execute()

Thank you, I've changed these lines and now it works!
GoogleCredential googleCredential = GoogleCredential.FromStream(fileStream)
.CreateScoped("https://www.googleapis.com/auth/admin.directory.group.member")
.CreateWithUser("my-user#example.com");

Related

Google.Apis.GoogleAnalyticsAdmin.v1alpha raising insufficientPermissions when trying to get google admin analytics account detail in C#?

I wanted to get the account detail, after authenticating I added to the scope section to grant the permission, but when running the code it raised an error insufficient permission, I don't know what step I missed, how would I be able to fix this error?
private static GoogleAnalyticsAdminService _analyticsService;
public static void IntializeAnalytics() {
GoogleCredential credential = GoogleCredential.GetApplicationDefault();
if (CloudManager.Credential.IsCreateScopedRequired)
{
credential = CloudManager.Credential.CreateScoped(
GoogleAnalyticsAdminService.Scope.AnalyticsReadonly, GoogleAnalyticsAdminService.Scope.AnalyticsManageUsers,
GoogleAnalyticsAdminService.Scope.AnalyticsManageUsersReadonly, GoogleAnalyticsAdminService.Scope.AnalyticsEdit);
}
_analyticsService = new GoogleAnalyticsAdminService(
new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = CloudManager.ApplicationName
});
var response = _analyticsService.Accounts.Get("accounts/Default Account for Firebase").Execute();
}
Error message :
Unhandled exception. The service analyticsadmin has thrown an exception.
HttpStatusCode is Forbidden.
Google.Apis.Requests.RequestError
Request had insufficient authentication scopes. [403]
Errors [
Message[Insufficient Permission] Location[ - ] Reason[insufficientPermissions] Domain[global]
]
Google.GoogleApiException: The service analyticsadmin has thrown an exception. HttpStatusCode is Forbidden. Request had insufficient authentication scopes.
at Google.Apis.Requests.ClientServiceRequest`1.ParseResponse(HttpResponseMessage response)
at Google.Apis.Requests.ClientServiceRequest`1.Execute()
Request had insufficient authentication scopes.
Means that when you authorized your application you did not request the proper scopes. Im not sure where you got that code from Its nothing like the code I normally use.
using Google.Analytics.Admin.V1Beta;
Console.WriteLine("Hello, World!");
// Path to the service account credentials file.
const string credentialsJsonPath = #"C:\Development\FreeLance\GoogleSamples\Credentials\ServiceAccountCred.json";
// Property on Google analytics which the service account has been granted access to.
const string propertyId = "XXXXXX";
var client = new AnalyticsAdminServiceClientBuilder()
{
CredentialsPath = credentialsJsonPath
}.Build();
var response = client.ListAccounts(new ListAccountsRequest());
foreach (var account in response) {
Console.WriteLine(account.Name);
}
Console.ReadLine();

Service account delegation with Google workspace - Entity Not found

I am trying to access the Google Directory api as a test this method domains.list using a service account.
If I use the try me on that page logging in with my domain admin email. It works and I get a response back. So the method and the customer id i am passing should be working.
I followed the instructions here Perform Google Workspace Domain-Wide Delegation of Authority to create a service account and enable domain wide delegation.
If I check both my workspace account and Google cloud console. The delegation appears to be configured. with my domain admin email set as principle.
My code:
namespace Daimto.Sample.WorkspaceAdmin
{
class Program
{
private static readonly string[] Scopes = {DirectoryService.Scope.AdminDirectoryDomain};
private static readonly string PathToServiceAccountKeyFile = #"C:\YouTube\workspaceserviceaccount-e4823a933ae3.json";
private static readonly string CustomerId = "C01lp3chxa";
private static readonly string workspaceAdmin = "xxx#daimto.com";
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var credential = LoadGoogleCredentails();
var service = CreateDirectoryService(credential);
var request = service.Domains.List(CustomerId);
var result = request.Execute();
foreach (var domain in result.Domains)
{
Console.WriteLine(domain.DomainName);
}
}
private static DirectoryService CreateDirectoryService(GoogleCredential credential)
{
return new (new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Daimto Testing Workspace with service account"
}
);
}
private static GoogleCredential LoadGoogleCredentails()
{
return GoogleCredential.FromFile(PathToServiceAccountKeyFile)
.Impersonate(new ImpersonatedCredential.Initializer(workspaceAdmin))
.CreateScoped(Scopes);
}
}
}
error
Running code as it appears above.
"error": {
"code": 404,
"message": "Requested entity was not found.",
"errors": [
{
"message": "Requested entity was not found.",
"domain": "global",
"reason": "notFound"
}
Not found implies to me that its not even able to access the domain.
However if I remove the line for impersonation. Then I get this error
Not Authorized to access this resource/api [403]
Errors [
Message[Not Authorized to access this resource/api] Location[ - ] Reason[forbidden] Domain[global]
]
This implies to me that without the impersonation it doesn't have access.
So I am confused. With impersonation it cant find it, without impersonation it can find it but doesn't have access?
The only clue I can find in the documentation is this started note.
So what exactly do they mean by this Only users with access to the Admin APIs can access the Admin SDK Directory API I am admin shouldn't i have access? Should access be configured if so where?
Ok this required a lot of digging.
There are two methods in the Google .Net client library Impersonate() and CreateWithUser()
Impersonate Allows this credential to impersonate the ImpersonatedCredential.Initializer.TargetPrincipal. Only ServiceAccountCredentialand UserCredential support impersonation, so this method will throw <see InvalidOperationException if this credential's
UnderlyingCredential is not of one of those supported types.
while
CreateWithUser If the credential supports Domain Wide Delegation, this method creates a copy of the credential with the specified user. Otherwise, it throws <see InvalidOperationException. At the moment only ServiceAccountCredential supports Domain Wide Delegation.
So the key here is are you trying to delegate to a user or impersonate a user. There is a difference.
So by using CreateWithUser it now works.
private static GoogleCredential LoadGoogleCredentails()
{
return GoogleCredential.FromFile(PathToServiceAccountKeyFile)
.CreateScoped(Scopes)
.CreateWithUser(workspaceAdmin);
}

Gmail API Create and Send Email .NET C#

Problem
I am trying to implement the Gmail API into an API application. I created a service account and saved the p12 key and the json credentials. I am getting an exception talking about a failed precondition. I think it might have something to do with the message I'm trying to send.
Code
String serviceAccountEmail = "SERVICE-ACC-EMAIL";
X509Certificate2 certificate = new X509Certificate2("./key.p12", "notasecret", X509KeyStorageFlags.Exportable);
// FileStream stream = new FileStream("./credentials.json", FileMode.Open, FileAccess.Read); // ! Not Used
ServiceAccountCredential credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = serviceAccountEmail,
Scopes = new[] { GmailService.Scope.MailGoogleCom }
}.FromCertificate(certificate));
GmailService service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Testing Application",
});
var result = service.Users.Messages.Send(CreateEmail.CreateEmailMessage(), "me").Execute();
Exception
An unhandled exception of type 'Google.GoogleApiException' occurred in System.Private.CoreLib.dll: 'Google.Apis.Requests.RequestError
Precondition check failed. [400]
Errors [
Message[Precondition check failed.] Location[ - ] Reason[failedPrecondition] Domain[global]
]'
Building Mail Message (does not work)
In the CreateEmail.CreateEmailMessage method I build up a new instance of Google.Apis.Gmail.v1.Data.Message. Setting the payload and headers. Take this as reference. I am not sure if this is the way to do it but I can't seem to find a way to create a new message. All I can find is things written in Java or Python which i tried translating over to C#, failing spectacularly
var msg2 = new Message()
{
Payload = new MessagePart()
{
Body = new MessagePartBody()
{
Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("Hello world"))
},
Headers = new List<MessagePartHeader>() {
new MessagePartHeader() { Name = "To", Value = "My email"},
...
Precondition check failed. [400]
with the Gmail api and service accounts normally means that you have not properly setup domain wide delegation to the service account.
Implementing Server-Side Authorization
In your case it may be because you are delegating to a user that is not on your domain.
User = serviceAccountEmail,
Is not the service accounts email address it is the user on your Google Workspace which you want the service account to be impresontating.
string ApplicationName = "Gmail API .NET Quickstart";
const string serviceAccount = "clawskeyboard-smtp#clawskeyboard-api.iam.gserviceaccount.com";
var certificate = new X509Certificate2(#"D:\api-ed4859a67674.p12", "notasecret", X509KeyStorageFlags.Exportable);
var gsuiteUser = "xxx#YourWorkGroupDomain.com";
var serviceAccountCredentialInitializer = new ServiceAccountCredential.Initializer(serviceAccount)
{
User = gsuiteUser,
Scopes = new[] { GmailService.Scope.GmailSend, GmailService.Scope.GmailLabels }
}.FromCertificate(certificate);

Gmail API Set Forwarding with Service Account

Can anyone help as I'm getting bad request failed precondition errors when calling Gmail API to set forward address? Below is a C# .Net console app I'm trying do this with. I have delegated Domain Wide Authority to the Service Account.
Error:
Google.Apis.Requests.RequestError Bad Request [400] Errors [ Message[Bad Request] Location[ - ] Reason[failedPrecondition] Domain[global] ]
I think I was missing the User to impersonate. So, I added the user and now I get the following error.
Error:
Error:"unauthorized_client", Description:"Client is unauthorized to retrieve access tokens using this method.", Uri:""
namespace GmailForwarder
{
class Program
{
static string ApplicationName = "GmailForwarder";
static void Main(string[] args)
{
ServiceAccountCredential credential;
string serviceAccount = "gmailforwarder#gmailforwarder.iam.gserviceaccount.com";
var certificate = new X509Certificate2(#"key.p12", "notasecret", X509KeyStorageFlags.Exportable);
try
{
// Create credential
credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccount)
{
User = "wtestboonew#chicagobooth.edu",
Scopes = new[] { GmailService.Scope.GmailSettingsSharing }
}.FromCertificate(certificate));
// Create Gmail API service.
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
string user = "wtestboonew#chicagobooth.edu"; // test gmail account
string fwdAddr = "acw5274#gmail.com";
ForwardingAddress fwdAddress = new ForwardingAddress();
fwdAddress.ForwardingEmail = fwdAddr;
var createFwdAddressResult = service.Users.Settings.ForwardingAddresses.Create(fwdAddress,"me").Execute();
}
catch (Exception ex)
{
}
}
}
}
This worked for me when I used : https://mail.google.com/ and https://www.googleapis.com/auth/gmail.settings.sharing OAuth2 scopes
I had this problem too (400 code). I was having two problems:
1) As ACW says, I was missing "https://mail.google.com/" from the list of scopes required, appart from "https://www.googleapis.com/auth/gmail.settings.sharing".
2) I was missing to put the scopes wrapped in quotes ("") in the administration console (when specifying the scopes for the service account).
Hope it helps someone

Google Oauth error: At least one client secrets (Installed or Web) should be set

I'm using Google's Oauth 2.0 to upload videos to Youtube via our server.
My client ID is a "service account". I downloaded the json key and added it to my solution.
Here is the relevant code:
private async Task Run(string filePath)
{
UserCredential credential;
var keyUrl = System.Web.HttpContext.Current.Server.MapPath("~/content/oauth_key.json");
using (var stream = new FileStream(keyUrl, FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows an application to upload files to the
// authenticated user's YouTube channel, but doesn't allow other types of access.
new[] { YouTubeService.Scope.YoutubeUpload },
"user",
CancellationToken.None
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
});
When I run it, I get this error: At least one client secrets (Installed or Web) should be set.
However, in my json there is no "client secret":
{
"private_key_id": "9d98c06b3e730070806dcf8227578efd0ba9989b",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIICdQIBADANBgkqhk etc,
"client_email": "546239405652-8igo05a5m8cutggehk3rk3hspjfm3t04#developer.gserviceaccount.com",
"client_id": "546239405652-8igo05a5m8cutggehk3rk3hspjfm3t04.apps.googleusercontent.com",
"type": "service_account"
}
so I assume I overlooked something.
Maybe I can't use the "service account" ? don't know...
The solution that uses json file is quite similar.
Here is sample that create VisionService using GoogleCredential object created from json file with ServiceAccountCredential.
GoogleCredential credential;
using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream)
.CreateScoped(VisionService.Scope.CloudPlatform);
}
var service = new VisionService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "my-app-name",
});
this sample require two NuGet packages:
Google.Apis.Vision.v1
Google.Apis.Oauth2.v2
I managed to get a service account to work with a P12 file, But would like to know how to use with the JSON file, Or just value from the JSON file to create the certificate.
To get the token
private static String GetOAuthCredentialViaP12Key()
{
const string serviceAccountEmail = SERVICE_ACCOUNT_EMAIL;
var certificate = new X509Certificate2(SERVICE_ACCOUNT_PKCS12_FILE_PATH, "notasecret", X509KeyStorageFlags.Exportable);
var scope = DriveService.Scope.Drive + " https://spreadsheets.google.com/feeds";
var credential = new ServiceAccountCredential( new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { scope }
}.FromCertificate(certificate) );
if (credential.RequestAccessTokenAsync(CancellationToken.None).Result == false)
{
return null;
}
return credential.Token.AccessToken;
}
And this is how I used the token I got
// Initialize the variables needed to make the request
OAuth2Parameters parameters = new OAuth2Parameters {AccessToken = token};
GOAuth2RequestFactory requestFactory = new GOAuth2RequestFactory(null, "MySpreadsheetIntegration-v1", parameters);
SpreadsheetsService service = new SpreadsheetsService("MySpreadsheetIntegration-v1");
service.RequestFactory = requestFactory;
Not an expert on C# but it looks like you were trying to use the service account to do the OAuth2 web server flow, which shouldn't work.
You probably want to use ServiceAccountCredential instead.
For more information about different Google OAuth2 flows, please refer to the doc for web server, service account, etc.

Categories

Resources