Gmail API Set Forwarding with Service Account - c#

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

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();

C# Google Drive Get File return Invalid Algorithm

I am stuck with this problem. I am working with Google Drive in C# with P12 files. It works fine on my Local machine and on Server. I need to deploy it on Client new server where i face this issue when i try to get Files it return Invalid Algorithm. Same settings work fine on my local machine and on other server . It seems to be machine specific issue. Application is running on Windows 7 Professional 64 bit OS.
Service is created successfully certificate is read but following code return error Here is my code which return error
FilesResource.ListRequest list = service.Files.List();
list.PageSize = 100;
files = list.Execute().Files; /// This line has error
Can it be a Firewall Issue. What should be opned on Firewall?
Here is my Authorization code:
public override DriveService AuthenticateServiceAccount(DriveRequest request)
{
if (request == null)
return null;
string errorMessage = "Exception: invalid Drive Request";
if (string.IsNullOrEmpty(request.EmailAccount) || string.IsNullOrEmpty(request.KeyFilePath))
return null;
errorMessage = "Exception: Keyfile not found on the path-" + request.KeyFilePath;
// check the file exists
if (!System.IO.File.Exists(request.KeyFilePath))
{
Commons.Utilities.WriteLog(errorMessage);
return null;
}
//---- This scope is used For G-Suite Account
string[] scopes = new string[] { DriveService.Scope.Drive };
errorMessage = "Exception: issue with certificate";
var certificate = new X509Certificate2(request.KeyFilePath, "notasecret", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
try
{
ServiceAccountCredential credential = null;
if (request.UserAccount != "" && request.UserAccount != null)
{
credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(request.EmailAccount)
{
Scopes = scopes,
User = request.UserAccount
}.FromCertificate(certificate));
}
else {
credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(request.EmailAccount)
{
Scopes = scopes
}.FromCertificate(certificate));
}
errorMessage = "Exception: while creating google service";
if (service == null) {
// Create the service.
service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Simplicity Cloud Project" // User defined any name can be given
}) ;
}
return service;
}
catch (Exception ex)
{
//Console.WriteLine(ex.InnerException);
Commons.Utilities.WriteLog(errorMessage);
return null;
}
}
Here is the generated log:
Read Certificate
2021-11-22 15:25:58,416 |SimplicityOnlineWebApi.Commons.Utilities|INFO| - Certificate is read:[Subject]
CN=102737515943555401961
[Issuer]
CN=102737515943555401961
[Serial Number]
1E269224F694B808
[Not Before]
02/09/2016 16:24:59
[Not After]
31/08/2026 16:24:59
[Thumbprint]
C26D88E95B402EFCC1AC81230F89C0B7887A6C6A
2021-11-22 15:25:58,445 |SimplicityOnlineWebApi.Commons.Utilities|INFO| - Creating Service
2021-11-22 15:25:58,455 |SimplicityOnlineWebApi.Commons.Utilities|INFO| - Service has been created successfully Google.Apis.Drive.v3.DriveService
2021-11-22 15:25:58,528 |SimplicityOnlineWebApi.Commons.Utilities|INFO| - Error occured in GetFiles:Invalid algorithm specified.
Here is the code of GetFile method
Here is code:public override AttachmentFilesFolder GetFiles(DriveService service, string search)
{
AttachmentFilesFolder attachments = null;
IList<File> files = new List<File>();
try
{
//List all of the files and directories for the current user.
FilesResource.ListRequest list = service.Files.List();
list.PageSize = 100;
Utilities.WriteLog("Getting files");
files = list.Execute().Files;
}
catch (Exception ex)
{
Utilities.WriteLog("Error occured in GetFiles:" + ex.message);
}
}
Issues with P12 files.
Issues authorizing a service account with the p12 key file normally relate back to the line X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable if I recall it has to do with where and how the key is stored on the server. That being said it was also when the app was hosted in Azure and the error message was diffrent then what you state you are seeing. I wrote a blog post on it a few years ago Azure with service accounts in C#
This is the code i use for p12 files
var certificate = new X509Certificate2(serviceAccountCredentialFilePath, "notasecret", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes
}.FromCertificate(certificate));
// Create the Drive service.
return new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Drive Authentication Sample",
});
Json key file
Honestly you should not bother using the p12 file you should use the json key file for service accounts this is much easer to work with and you wont have the issues with the server giving you greif.
GoogleCredential credential;
using (var stream = new FileStream(serviceAccountCredentialFilePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream)
.CreateScoped(scopes);
}
// Create the Analytics service.
return new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Drive Service account Authentication Sample",
});
I'm fairly certain that there's nothing in your code, your service account or Google client libraries that's provoking this error. What I think is happening:
ServiceAccountCredential uses SHA256 to sign payloads when making auth requests.
The error you are getting suggests that your machine currently does not support SHA256.
Older versions of Windows 7 do not support SHA256. You can read more about it here: 2019 SHA-2 Code Signing Support requirement for Windows and WSUS where there's also a list of updates you need to install for your Windows 7 machine to support SHA256.
If, after checking that your system is up to date, you continue to receive the same error, that still would indicate some SHA256 incompatibility in your environment. There's a long thread here Why am I getting "Invalid algorithm specified" exception with fixes/workarounds for different causes, and some of those may apply to you.

How to retrieve Google group membership with a service account?

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");

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);

How do I use a refresh token to get a new access token for Google SpreadsheetsService

I'm trying to build an application which reads and writes to a private Google Spreadsheet. For this I need to use Google OAuth2.0 .
My problem is that I lose access after 1 hour. I assume this means that the refresh token is not being used correctly. Here is my code for handling authentication:
public static SpreadsheetsService AuthenticateOauth(string clientId, string clientSecret, string userName)
{
string[] scopes = new string[] { DriveService.Scope.Drive, // view and manage your files and documents
DriveService.Scope.DriveAppdata,
DriveService.Scope.DriveAppsReadonly,
DriveService.Scope.DriveFile,
DriveService.Scope.DriveMetadataReadonly,
DriveService.Scope.DriveReadonly,
"https://spreadsheets.google.com/feeds",
"https://docs.google.com/feeds"
};
try
{
// 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 = clientId, ClientSecret = clientSecret }
, scopes
, userName
, CancellationToken.None
, new FileDataStore("MY.APP.Auth.Store")).Result;
SpreadsheetsService service = new SpreadsheetsService(My App");
var requestFactory = new GDataRequestFactory("My App");
requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
service.RequestFactory = requestFactory;
return service;
}
catch (Exception ex)
{
Console.WriteLine(DateTime.Now.ToString("HH:mm") + ": An authentication error occurred: " + ex.InnerException);
return null;
}
}
How do I go about making the refresh token being used correctly?
You are using the current Google .NET client library to authenticate.
When you create a service, it normally will automatically refresh your access token when needed. However, you are sending an access token to an old Gdata library, which doesn't automatically refresh it.
If you create a drive service and run a dummy request against it once an hour it will refresh your access token for you when needed.
var service = new DriveService(new BaseClientService.Initializer() {HttpClientInitializer = credential,
ApplicationName = "Drive API Sample",});
// Dummy request example:
FilesResource.ListRequest list = service.Files.List();
list.MaxResults = 1;
list.Q = "title=dummysearch";
FileList dummyFeed = list.Execute();
// End of Dummy request

Categories

Resources