Gmail API Create and Send Email .NET C# - 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);

Related

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.

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

Send message as user alias

Is there a way to use the Gmail API to send as one of the provided users aliases instead of the users direct email?
I have a general user in my Google Org and it has a few aliases such as help#example.com, support#example.com which all belong to generaluser#example.com
At the moment it sends fine but different sections of my app need to send email as the specified alias.
Below is my code which sends email as the specified user without error.
private static async Task<GmailService> GetAuthorizedGmailService()
{
var serviceAccountEmail = "serviceaccount#gserviceaccount.com";
string AuthFile = HttpContext.Current.Server.MapPath("");
X509Certificate2 certificate = new X509Certificate2(AuthFile,"", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential;
credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = "user#email.com",
Scopes = Scopes
}.FromCertificate(certificate));
GmailService service = null;
if (await credential.RequestAccessTokenAsync(CancellationToken.None))
{
service = new GmailService(new BaseClientService.Initializer()
{
ApplicationName = ApplicationName,
HttpClientInitializer = credential,
});
}
return service;
}
The above is my code for creating my GmailService and below is my execution of the SendRequest:
var mimeMessage = MimeKit.MimeMessage.CreateFromMailMessage(mail);
var gmailMessage = new Google.Apis.Gmail.v1.Data.Message
{
Raw = Encode(mimeMessage.ToString())
};
var service = await GetAuthorizedGmailService();
UsersResource.MessagesResource.SendRequest request = service.Users.Messages.Send(gmailMessage, "user#email.co.za");
await request.ExecuteAsync();
Anyone know how I can specify which alias should be used in the from address?
I have tried setting it in the from section of the HTTP header and I still get the message from the users direct email address. I would very much like to not have to create a user account for each of these alias just so I can send as the appropriate email address.
The comment made by Tholle was correct. Adding the alias in the From header makes it use the alias.
The problem I was having is while the user had the alias assigned to them you have to also add it to the Send mail as in the users Gmail Settings.
All I had to do was go add the alias there and then it didn't override the From header with the users primary address.
Setting is located in: Settings > Acounts > Send mail as:

ASP.net Web Application (Not MVC): Accessing Google GoogleWebAuthorizationBroker.AuthorizeAsync returns access denied error

Environment: ASP.NET simple web application on .net 4.5.1 integrated pipeline running on iis8 on server 2012 which is not following MVC.
I'm attempting to get credentials from google's GoogleWebAuthorizationBroker but i keep getting "access is denied"...Even i allowed my urls in "Authorized JavaScript origins" and "Authorized redirect URIs"
Following below URL implementation for Installed App
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#installed-applications
Here are mine code snippet
var folder = "F:\\MyApp\\WebApp\\MyGoogleStorage";
string[] scopes = new string[] {
Google.Apis.Proximitybeacon.v1beta1.ProximitybeaconService.Scope.UserlocationBeaconRegistry
};
ClientSecrets secrets = new ClientSecrets()
{
ClientId = CLIENT_ID,
ClientSecret = CLIENT_SECRET
};
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
secrets,
scopes,
"user",
CancellationToken.None,
new FileDataStore(folder)).Result;
And use another way to create credentials
using (var stream = new FileStream(Utility.AppPath + "/client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
scopes,
"user", CancellationToken.None, new FileDataStore(folder));
}
But in both cases i am getting access denied.
My assumption is that, it's happening because i am trying to use sample
"Installed Applications"
Please advice me what is the best way to do that in .net simple web application.
Also share some code if any one done it successfully.
Thanks in advance..!!!
For this solution work around I found was
string serviceAccountEmail = "proximitybeacon#proximitytest-1234.iam.gserviceaccount.com"; // Service Account Email
string userEmail = "abc#gmail.com"; //Email of yours
//.p12 file is having all the details about the Service Account we create a Cryptography certificate by it. This you need to download fron your project which you make in Google Developer Console. Foolow stesps from this link https://webapps.stackexchange.com/questions/58411/how-where-to-obtain-a-p12-key-file-from-the-google-developers-console
X509Certificate2 certificate = new X509Certificate2("F://yorrappPath/ProximityTest-2d889bd4fa49.p12", "notasecret", X509KeyStorageFlags.Exportable); // F://yorrappPath -- Give proper location of .p12 file
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = userEmail,
Scopes = new string[] { "https://www.googleapis.com/auth/userlocation.beacon.registry" }
}.FromCertificate(certificate));
if (!credential.RequestAccessTokenAsync(CancellationToken.None).Result)
{
return "Can't get the access token";
}
//Beacon Object with its values
Google.Apis.Proximitybeacon.v1beta1.Data.Beacon beacon = new Google.Apis.Proximitybeacon.v1beta1.Data.Beacon();
Google.Apis.Proximitybeacon.v1beta1.Data.AdvertisedId advertisedId = new Google.Apis.Proximitybeacon.v1beta1.Data.AdvertisedId();
beacon.AdvertisedId = new Google.Apis.Proximitybeacon.v1beta1.Data.AdvertisedId() { Id = "ZgCC7BLy8FXla3VmnnibWQ==", Type = "EDDYSTONE" };
beacon.BeaconName = "99911";
beacon.Status = "ACTIVE";
beacon.LatLng = new Google.Apis.Proximitybeacon.v1beta1.Data.LatLng() { Latitude = 28.38, Longitude = 77.12 };
beacon.ExpectedStability = "STABLE";
//Beacon Service for register which having HttpClientInitializer(credential with token detail)
Google.Apis.Proximitybeacon.v1beta1.ProximitybeaconService service = new Google.Apis.Proximitybeacon.v1beta1.ProximitybeaconService(new BaseClientService.Initializer()
{
ApplicationName = "proximityTest", // Replace that with you App name which you given in Google
HttpClientInitializer = credential
});
var registerRequest = new Google.Apis.Proximitybeacon.v1beta1.BeaconsResource.RegisterRequest(service, beacon);
//uncomment this for update beacons. 3 parameter is important for update BeaconName
//var updateRequest = new Google.Apis.Proximitybeacon.v1beta1.BeaconsResource.UpdateRequest(service, beacon, "beacons/3!660082ec12f2f055e56b75669e789b59");
//updateRequest.Execute();
//uncomment this for getting list of beacons.
// var listRequest = new Google.Apis.Proximitybeacon.v1beta1.BeaconsResource.ListRequest(service);
// return listRequest.Execute();
try
{
//This will register a Beacon
registerRequest.Execute();
}
catch (Exception ex)
{
return ex.Message;
}
return beacon;

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