Send email using Graph API from service account - c#

I am working on task in ASP.NET Core 5 (C#) which requires to send an email using Graph API, I have referred to following article and did the configuration on the Azure trial account and was able to send the emails.
Sending e-mails with Microsoft Graph using .NET
This is the send email code:
//send email
var client = await GetAuthenticatedGraphClient();
await client.Users[senderObjectId]
.SendMail(graphMailMessage, true)
.Request()
.PostAsync();
senderObjectId - Object Id coming from config
We deployed the same code on the client's Azure account we needed the User Object Id for the service account that we are going to use as a sender's email id. However, the client came back saying that the account is not part of the Azure AD and its a service account. Is there a way of sending emails without using the user object id.

Here's the method which takes parameters for mail sending. Also, it separates (comma) the mail and sends it to multiple users
public string SendEmail(string fromAddress, string toAddress, string CcAddress, string subject, string message, string tenanatID , string clientID , string clientSecret)
{
try
{
var credentials = new ClientSecretCredential(
tenanatID, clientID, clientSecret,
new TokenCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud });
GraphServiceClient graphServiceClient = new GraphServiceClient(credentials);
string[] toMail = toAddress.Split(',');
List<Recipient> toRecipients = new List<Recipient>();
int i = 0;
for (i = 0; i < toMail.Count(); i++)
{
Recipient toRecipient = new Recipient();
EmailAddress toEmailAddress = new EmailAddress();
toEmailAddress.Address = toMail[i];
toRecipient.EmailAddress = toEmailAddress;
toRecipients.Add(toRecipient);
}
List<Recipient> ccRecipients = new List<Recipient>();
if (!string.IsNullOrEmpty(CcAddress))
{
string[] ccMail = CcAddress.Split(',');
int j = 0;
for (j = 0; j < ccMail.Count(); j++)
{
Recipient ccRecipient = new Recipient();
EmailAddress ccEmailAddress = new EmailAddress();
ccEmailAddress.Address = ccMail[j];
ccRecipient.EmailAddress = ccEmailAddress;
ccRecipients.Add(ccRecipient);
}
}
var mailMessage = new Message
{
Subject = subject,
Body = new ItemBody
{
ContentType = BodyType.Html,
Content = message
},
ToRecipients = toRecipients,
CcRecipients = ccRecipients
};
// Send mail as the given user.
graphServiceClient
.Users[fromAddress]
.SendMail(mailMessage, true)
.Request()
.PostAsync().Wait();
return "Email successfully sent.";
}
catch (Exception ex)
{
return "Send Email Failed.\r\n" + ex.Message;
}
}

Related

EWS Connection Giving Unauthorized (401) Error

I have been working on a program that scans an exchange inbox for specific emails from a specified address. Currently the program reads the inbox, downloads the attachment, and moves the email to another folder. However, after about 15 pulls from the EWS server, the connection starts giving a 401 Unauthorized error until I restart the program. The program is setup to login via OAuth as basic auth is disabled by the system administrator. Below is the code that I am using to obtain the exchange connection and read the emails from the inbox.
Exchange Connection Code:
public static async Task<ExchangeService> GetExchangeConnection()
{
var pcaOptions = new PublicClientApplicationOptions
{
ClientId = AppID,
TenantId = TenantID,
};
var pca = PublicClientApplicationBuilder.CreateWithApplicationOptions(pcaOptions).Build();
var ewsScopes = new string[] { "https://outlook.office365.com/EWS.AccessAsUser.All" };
var securePassword = new SecureString();
foreach (char c in Pasword)
securePassword.AppendChar(c);
try
{
var authResult = await pca.AcquireTokenByUsernamePassword(ewsScopes, Username, securePassword).ExecuteAsync();
ExchangeService exchangeService = new ExchangeService()
{
Credentials = new OAuthCredentials(authResult.AccessToken),
Url = new Uri("https://outlook.office365.com/ews/exchange.asmx"),
};
return exchangeService;
}
catch
{
return null;
}
}
Email Retriever
public static List<Email> RetreiveEmails()
{
ExchangeService exchangeConnection = GetExchangeConnection().Result;
try
{
List<Email> Emails = new List<Email>();
TimeSpan ts = new TimeSpan(0, -5, 0, 0);
DateTime date = DateTime.Now.Add(ts);
SearchFilter.IsGreaterThanOrEqualTo EmailTimeFilter = new SearchFilter.IsGreaterThanOrEqualTo(ItemSchema.DateTimeReceived, date);
if (exchangeConnection != null)
{
FindItemsResults<Item> findResults = exchangeConnection.FindItems(WellKnownFolderName.Inbox, EmailTimeFilter, new ItemView(10));
foreach (Item item in findResults)
{
if (item.Subject != null)
{
EmailMessage message = EmailMessage.Bind(exchangeConnection, item.Id);
message.Load(new PropertySet(BasePropertySet.FirstClassProperties, ItemSchema.TextBody));
Emails.Add(new Email(message.DateTimeReceived, message.From.Name.ToString(), message.Subject, message.TextBody.ToString(), (message.HasAttachments) ? "Yes" : "No", message.Id.ToString()));
}
}
}
exchangeConnection = null;
return Emails;
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
return null;
}
}
The error occurs when the email retriever tries to either create the exchange connection or when requesting the emails from the folder. In either case the code will error out and give me 401 unauthorized while using credentials that work for the first dozen times and then fails after so many attempts. I have tried it with multiple different accounts and the issue persists with all of them and I have made sure that the application is authorized to access the exchange inbox. Any suggestions or help is much appreciated.
After doing further tracing regarding the 401 error it resulted in an issue with the token reaching the end of it's 1 hour lifespan. This is due to the original OAuth token having an initial life of 1 hour. This however was able to be fixed by setting up code to automatically refresh the token when needed. Here is the code to address this issue for anyone else who comes across this problem.
Authentication Manager:
class AuthenticationManager
{
protected IPublicClientApplication App { get; set; }
public AuthenticationManager(IPublicClientApplication app)
{
App = app;
}
public async Task<AuthenticationResult> AcquireATokenFromCacheOrUsernamePasswordAsync(IEnumerable<String> scopes, string username, SecureString password)
{
AuthenticationResult result = null;
var accounts = await App.GetAccountsAsync();
if (accounts.Any())
{
try
{
result = await (App as PublicClientApplication).AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync();
}
catch (MsalUiRequiredException)
{ }
}
if (result == null)
{
result = await (App as PublicClientApplication).AcquireTokenByUsernamePassword(scopes, username, password).ExecuteAsync();
}
return result;
}
}
I am using direct username and password authentication but the line of code can be switched to getting the user authentication via interactive methods as well. The code essentially creates a new instance of the authentication manager with a PublicClientApplication used to initialize it which houses the appID and tenantID. After initializing, you can call the AquireATokenFromCacheOrUsernamePasswordAsync which will attempt to see if there is an account present to get a token against. Next it will attempt to retrieve the previously cached token or refresh the token if it expires in less than 5 minutes. If there is a token available it will return that to the main application. If there isn't a token available, it will acquire a new token using the username and password supplied. Implementation of this code looks something like this,
class ExchangeServices
{
AuthenticationManager Manager = null;
public ExchangeServices(String AppId, String TenantID)
{
var pcaOptions = new PublicClientApplicationOptions
{
ClientId = AppID,
TenantId = TenantID,
};
var pca = PublicClientApplicationBuilder.CreateWithApplicationOptions(pcaOptions).Build();
Manager = new AuthenticationManager(pca);
}
public static async Task<ExchangeService> GetExchangeService()
{
var ewsScopes = new string[] { "https://outlook.office365.com/EWS.AccessAsUser.All" }
var securePassword = new SecureString();
foreach(char c in Password)
securePassword.AppendChar(c);
var authResult = await Manager.AquireATokenFromCacheOrUsernamePasswordAsync(ewsScopes, Username, securePassword);
ExchangeService exchangeService = new ExchangeService()
{
Credentials = new OAuthCredentials(authResult.AccessToken),
Url = new Uri("https://outlook.office365.com/ews/exchange.asmx");
};
return exchangeService;
}
}
The code above is everything laid out that is needed to create a new authentication manager and use it to get and update new tokens while using EWS services through OAuth. This is the solution that I found to fix the issue described above.

ArgumentNullException for address when sending email using NetCore.MailKit

I am using NetCore.MailKit NuGet package to help me send an email which contains a link to confirm their email when the user registers. I have followed the steps on their Github page but, I am getting an ArgumentNullException for the address, even though I have set the sending address.
The error:
ArgumentNullException: Value cannot be null. (Parameter 'address')
MimeKit.MailboxAddress..ctor(Encoding encoding, string name, string address)
The above error occurs when is send the email in my controller using IEmailService:
_EmailService.Send("usersemail#gmail.com", "subject", "message body");
This is my appsettings.json configuration:
"EmailConfiguration": {
"Server": "smtp.gmail.com",
"Port": 587,
"SenderName": "name",
"SenderEmail": "mygmail#gmail.com",
"SenderPassword": "My gmail password"
}
This is my setup in startup.cs
services.AddMailKit(optionBuilder =>
{
optionBuilder.UseMailKit(new MailKitOptions()
{
//get options from sercets.json
Server = Configuration["Server"],
Port = Convert.ToInt32(Configuration["Port"]),
SenderName = Configuration["SenderName"],
SenderEmail = Configuration["SenderEmail"],
Account = Configuration["SenderEmail"],
Password = Configuration["SenderPassword"],
Security = true
});
});
Below is the code for EmailService:
public void Send(string mailTo, string subject, string message, bool isHtml = false)
{
SendEmail(mailTo, null, null, subject, message, Encoding.UTF8, isHtml);
}
private void SendEmail(string mailTo, string mailCc, string mailBcc, string subject, string message, Encoding encoding, bool isHtml)
{
var _to = new string[0];
var _cc = new string[0];
var _bcc = new string[0];
if (!string.IsNullOrEmpty(mailTo))
_to = mailTo.Split(',').Select(x => x.Trim()).ToArray();
if (!string.IsNullOrEmpty(mailCc))
_cc = mailCc.Split(',').Select(x => x.Trim()).ToArray();
if (!string.IsNullOrEmpty(mailBcc))
_bcc = mailBcc.Split(',').Select(x => x.Trim()).ToArray();
Check.Argument.IsNotEmpty(_to, nameof(mailTo));
Check.Argument.IsNotEmpty(message, nameof(message));
var mimeMessage = new MimeMessage();
//add mail from
mimeMessage.From.Add(new MailboxAddress(_MailKitProvider.Options.SenderName, _MailKitProvider.Options.SenderEmail));
//add mail to
foreach (var to in _to)
{
mimeMessage.To.Add(MailboxAddress.Parse(to));
}
//add mail cc
foreach (var cc in _cc)
{
mimeMessage.Cc.Add(MailboxAddress.Parse(cc));
}
//add mail bcc
foreach (var bcc in _bcc)
{
mimeMessage.Bcc.Add(MailboxAddress.Parse(bcc));
}
//add subject
mimeMessage.Subject = subject;
//add email body
TextPart body = null;
if (isHtml)
{
body = new TextPart(TextFormat.Html);
}
else
{
body = new TextPart(TextFormat.Text);
}
//set email encoding
body.SetText(encoding, message);
//set email body
mimeMessage.Body = body;
using (var client = _MailKitProvider.SmtpClient)
{
client.Send(mimeMessage);
}
}
As you can see I have set everything, am I missing something here? why is address null at MimeKit.MailboxAddress()?
You seem to be not loading your configuration settings correctly. I suspect the failing line in your code is
//add mail from
mimeMessage.From.Add(new MailboxAddress(_MailKitProvider.Options.SenderName, _MailKitProvider.Options.SenderEmail));
The Options are all null, which causes the exception.
When you load the settings from a configuration file you need to prepend the section name to the variables. E.G.
services.AddMailKit(optionBuilder =>
{
optionBuilder.UseMailKit(new MailKitOptions()
{
//get options from sercets.json
Server = Configuration["EmailConfiguration:Server"],
Port = Convert.ToInt32(Configuration["EmailConfiguration:Port"]),
SenderName = Configuration["EmailConfiguration:SenderName"],
SenderEmail = Configuration["EmailConfiguration:SenderEmail"],
Account = Configuration["EmailConfiguration:SenderEmail"],
Password = Configuration["EmailConfiguration:SenderPassword"],
Security = true
});
});
My guess is that the exception is being thrown on the following line:
mimeMessage.From.Add(new MailboxAddress(_MailKitProvider.Options.SenderName, _MailKitProvider.Options.SenderEmail));
This means that _MailKitProvider.Options.SenderEmail is null.
I know you expect these values to be loaded correctly from your appsettings.json file, but seemingly, they are not being loaded for some reason.

Create AWS Cognito user with account status "CONFIRMED" and without email address

How can I create a Cognito user with the account status confirmed using c#? After a user is created the account status displays FORCE_CHANGE_PASSWORD. Another thing is I need to create user without email address.
AmazonCognitoIdentityProviderClient cognitoProvider =
new AmazonCognitoIdentityProviderClient(region);
string userName = "user";
string tempPassword = "Temp#3434";
string newPassword = "RealPass#2019";
AdminCreateUserRequest adminUserCreateRequest = new AdminCreateUserRequest()
{
UserPoolId = poolId,
Username = userName,
TemporaryPassword = tempPassword
};
AdminCreateUserResponse signUpResponse = await cognitoProvider.AdminCreateUserAsync(adminUserCreateRequest);
Admin InitiateRequest
Dictionary<string, string> initialParams = new Dictionary<string, string>();
initialParams.Add("USERNAME", userName);
initialParams.Add("PASSWORD", tempPassword);
AdminInitiateAuthRequest initialRequest = new AdminInitiateAuthRequest()
{
AuthFlow = AuthFlowType.ADMIN_NO_SRP_AUTH,
AuthParameters = initialParams,
ClientId = appClientId_tenantApi,
UserPoolId = poolId
};
AdminInitiateAuthResponse resInitAuth = await cognitoProvider.AdminInitiateAuthAsync(initialRequest);
InitiateAuthRresponse has email as a required attribute.
{[requiredAttributes, ["userAttributes.email"]]}
But the documentation doesn't say so.
For ADMIN_NO_SRP_AUTH: USERNAME (required), SECRET_HASH (if app client is configured with client secret), PASSWORD (required), DEVICE_KEY
Admin Respond to challenge
var authParameters = new Dictionary<string, string>();
authParameters.Add("USERNAME", userName);
authParameters.Add("NEW_PASSWORD", newPassword);
AdminRespondToAuthChallengeRequest adminAuthRequest = new AdminRespondToAuthChallengeRequest()
{
UserPoolId = poolId,
ClientId = appClientId_tenantApi,
ChallengeName = ChallengeNameType.NEW_PASSWORD_REQUIRED,
ChallengeResponses = authParameters,
Session = session
};
cognitoProvider.AdminRespondToAuthChallengeAsync(adminAuthRequest);
I am thinking I may missed some user settings in Cognito to avoid email. Any one have similar experience ? or is this not possible to create user without email ?
During the creation of the user pool, under general settings;attributes as in the photocognito creation on aws one is required to choose the attributes that must be present, i believe in your case the email was selected by default hence the challenge request response you got.
The admin create user request requires the client to confirm the email for purposes of verification that the user owns the email.
A hack for the same would be to allow users to sign themselves up on your cognito configuration, then sign someone up then follow with a username and password, then proceed to confirm them as an admin
var signup = await cognitoClient.SignUpAsync(new SignUpRequest
{
Username = person.Username,
ClientId = cognitoOptions.ClientId,
Password = person.IdNumber,
});
var confirm = await cognitoClient.AdminConfirmSignUpAsync(new AdminConfirmSignUpRequest
{
Username = person.Username,
UserPoolId = cognitoOptions.UserPoolId
});
In case if anyone still looking for answer
Initalize Provider.
AmazonCognitoIdentityProviderClient provider = new AmazonCognitoIdentityProviderClient("*************", "************", Amazon.RegionEndpoint.USWest);
Create user
AdminCreateUserResponse adminCreateUserResponse = await provider.AdminCreateUserAsync(new AdminCreateUserRequest
{
Username = "TestUser",
TemporaryPassword = "TempPassword#1",
UserPoolId = "us-west-**********"
});
Authenticate user
CognitoUserPool userPool = new CognitoUserPool("us-west-***", "***", provider);
CognitoUser user = new CognitoUser("TestUser", "******", userPool, provider, "**********");
InitiateSrpAuthRequest authRequest = new InitiateSrpAuthRequest()
{
Password = "TempPassword#1"
};
AuthFlowResponse authResponse = await user.StartWithSrpAuthAsync(authRequest).ConfigureAwait(false);
Vaidate user authentication result and get the user AccessToken
if (authResponse.AuthenticationResult == null)
{
if (authResponse.ChallengeName == ChallengeNameType.NEW_PASSWORD_REQUIRED)
{
//Console.WriteLine("Enter your desired new password:");
string newPassword = "NewPWD#1";// Console.ReadLine();
Dictionary<string, string> att = new Dictionary<string, string>();
att.Add("userAttributes.email", "testemail#xyz.com");
user.Attributes.Add("preferred_username", "TestUser1");
And update the new password using Accesstoken ( post update the User status will be confirmed)
authResponse = await user.RespondToNewPasswordRequiredAsync(new RespondToNewPasswordRequiredRequest()
{
SessionID = authResponse.SessionID,
NewPassword = newPassword,
},att);
accessToken = authResponse.AuthenticationResult.AccessToken;
}

how to get the user's Email ID after AAD login using bot framework

I'm working on a bot using bot framework. With active directory authentication I managed to get the username . Now I want to get the phone number and logged in user Email ID after authenticated using active directory ?
Below is the code I'm working with.
Authentication
AuthenticationOptions options = new AuthenticationOptions()
{
UseMagicNumber = false,
Authority = Convert.ToString(ConfigurationManager.AppSettings["aad:Authority"]),
ClientId = Convert.ToString(ConfigurationManager.AppSettings["aad:ClientId"]),
ClientSecret = Convert.ToString(ConfigurationManager.AppSettings["aad:ClientSecret"]),
ResourceId = Convert.ToString(ConfigurationManager.AppSettings["aad:ResourceId"]),
RedirectUrl = Convert.ToString(ConfigurationManager.AppSettings["aad:Callback"])
};
await context.Forward(new AuthDialog(new ADALAuthProvider(), options), ResumeAfterLogin, message, context.CancellationToken);
Extracting the data
private async Task ResumeAfterLogin(IDialogContext authContext, IAwaitable<AuthResult> authResult)
{
string tokenstring = string.Empty;
string userName = string.Empty;
var resultToken = await authResult;
string email = string.Empty;
try
{
tokenstring = resultToken.AccessToken;
userName = resultToken.UserName;
MyGlobalVariables.EmailID = "";
MyGlobalVariables.username = userName;
if (null != tokenstring && string.Empty != tokenstring)
{
authContext.UserData.SetValue<string>("AccessToken", tokenstring);
authContext.UserData.SetValue<string>("userName", userName);
await authContext.PostAsync($"*info: you are logged in as {userName}*");
authContext.Call(new RootDialog(), this.ResumeAfterOptionDialog);
}
}
catch (Exception ex)
{
authContext.Wait(MessageReceivedAsync);
throw ex;
}
finally
{
}
}
You can get phone numbers and emails of logged in users by accessing the Microsoft AAD Graph API. For example:
public async Task<User> GetMe()
{
var graphClient = GetAuthenticatedClient();
var me = await graphClient.Me.Request().GetAsync();
return me;
}
A full sample can be found here.

specify an email address for Google Calendar notifications via api

How do I specify an email address for GoogleCalendar API notifications?
I need to programatically manipulate my Google Calendar via the Google API using C# dot-net. I've been able to create, delete and modify events successfully, but I'd like some control over notifications. Currently only email and UI popup options are available, and email defaults to the Google email account.
What I'm thinking is that if I can manipulate the email address used, I can send email to my Verizon email-to-SMS address, and send a text message instead of email (SMS is an option only for Government accounts). Or possibly to a dummy email that interfaces to an SMS service like Twilio. Bottom line is I would like to send email notifications to one or more email addresses of my choosing.
The only option for email is in a constructor for an EventReminder override:
new EventReminder{Method="email",Minutes=60};
Here's my working code:
namespace stuff
{
class Program
{
static string[] Scopes = { CalendarService.Scope.Calendar };
static string ApplicationName = "Google Calendar API .NET Quickstart";
static string _Chelle = "fake_id#gmail.com";
static string _EventCalendar = "jwejq36jjgwijg54iw7yjs7j7e#group.calendar.google.com";
static void Main(string[] args)
{
UserCredential credential;
using (var stream =
new FileStream("client_secret.json", FileMode.Open, FileAccess.Read))
{
string credPath = System.Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(credPath, ".credentials/calendar-dotnet-quickstart.json");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
Console.WriteLine("Credential file saved to: " + credPath);
}
// Create Google Calendar API service.
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
EventsResource.ListRequest request = service.Events.List(_EventCalendar);
request.TimeMin = DateTime.Now;
request.TimeMax = DateTime.Now.AddDays(30);
request.ShowDeleted = false;
Events events = request.Execute();
if (events.Items != null && events.Items.Count > 0)
{
foreach (var eventItem in events.Items)
{
// add new reminders
eventItem.Reminders = new Event.RemindersData
{
UseDefault = false,
Overrides = new[]
{
new EventReminder {Method="popup",Minutes=((60*24*7) - (60*9)) }, // one week before # 0900
new EventReminder {Method="email",Minutes=((60*24*7) - (60*9)) }
}
};
try
{
service.Events.Update(eventItem, _EventCalendar, eventItem.Id).Execute();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Thread.Sleep(1000);
}
}
else
{
Console.WriteLine("No events found.");
}
Console.Read();
}
}
}

Categories