Thank you for sharing the information privately. As requested in private message, can you please also share the screen shots of application permissions as well.
I have received application permissions in a private message via your colleague @pdhert. I have associated information in our issue tracking system and will get back to you with feedback as soon as the issue will be fixed.
I regret to share that at present there are no updates available. We will share updates with you as soon as the issue will be addressed.
How this can be fixed by Aspose:
class Microsoft.Exchange.WebServices.Data.ExchangeService has a Property with name “ImpersonatedUserId”.
Setting this Property adds the following SOAP-Header to Server-Requests
soap:Header
<t:ExchangeImpersonation>
<t:ConnectingSID>
<t:PrimarySmtpAddress>alisa@contoso.com</t: PrimarySmtpAddress>
</t:ConnectingSID>
</t:ExchangeImpersonation>
</soap:Header>
Please add an overload to
EWSClient.GetEWSClient(
where the ImpersonatedUserId can also be provided.
Please consider following implementation on your end.
Required permissions in Azure portal:
EWS.AccessAsUser.All // for EWS
POP.AccessAsUser.All // for POP
IMAP.AccessAsUser.All // for IMAP
SMTP.Send // for SMTP
Please use the following sample code to initialize clients.
EWS client initialization
string[] scopeAr = new string[]
{
"https://outlook.office.com/EWS.AccessAsUser.All",
};
ITokenProvider tokenProvider = new AzureROPCTokenProvider(
"Tenant",
"ClientId",
"ClientSecret",
"EMail",
"Password",
scopeAr);
OAuthNetworkCredential credentials = new OAuthNetworkCredential(tokenProvider);
EWSClient client = EWSClient.GetEWSClient("EWSUrl", credentials);
}
Pop3 client initialization
string[] scopeAr = new string[]
{
"https://outlook.office.com/POP.AccessAsUser.All",
};
ITokenProvider tokenProvider = new AzureROPCTokenProvider(
"Tenant",
"ClientId",
"ClientSecret",
"EMail",
"Password",
scopeAr);
client = new Pop3Client(
"Pop3Url",
"Pop3Port",
"EMail",
tokenProvider,
SecurityOptions.Auto);
SMTP client initialization
string[] scopeAr = new string[]
{
"https://outlook.office.com/SMTP.Send",
};
ITokenProvider tokenProvider = new AzureROPCTokenProvider(
"Tenant",
"ClientId",
"ClientSecret",
"EMail",
"Password",
scopeAr);
SmtpClient client = new SmtpClient(
"SmtpUrl",
"SmtpPort",
"EMail",
tokenProvider,
SecurityOptions.Auto);
Imap client initialization
string[] scopeAr = new string[]
{
"https://outlook.office.com/IMAP.AccessAsUser.All",
};
ITokenProvider tokenProvider = new AzureROPCTokenProvider(
"Tenant",
"ClientId",
"ClientSecret",
"EMail",
"Password",
scopeAr);
ImapClient client = new ImapClient(
"ImapUrl",
"ImapPort,
"EMail",
tokenProvider,
SecurityOptions.Auto);
AzureROPCTokenProvider
extern alias GlobalNewtonsoftJson;
using JsonConvert = GlobalNewtonsoftJson::Newtonsoft.Json.JsonConvert;
using Aspose.Email.Clients;
using Aspose.Email.Common.Utils;
using Aspose.Email.Tests.TestUtils;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Net;
using System.Text;
namespace Tests
{
/// <summary>
/// Azure resource owner password credential (ROPC) token provider
/// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc
/// https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-authenticate-an-ews-application-by-using-oauth
/// https://portal.azure.com
/// https://developer.microsoft.com/en-us/graph/graph-explorer/#
/// token parser https://jwt.io
/// </summary>
internal class AzureROPCTokenProvider : ITokenProvider
{
private const string uriFormat = "https://login.microsoftonline.com/{0}/oauth2/v2.0/token";
private const string bodyFormat =
"client_id={0}" +
"&scope={1}" +
"&username={2}" +
"&password={3}" +
"&grant_type={4}";
private readonly string scope;
private const string grant_type = "password";
private readonly object tokenSyncObj = new object();
private OAuthToken token;
private readonly string tenant;
private readonly string clientId;
private readonly string clientSecret;
private readonly string userName;
private readonly string password;
/// <summary>
/// Initializes a new instance of the <see cref="AzureROPCTokenProvider"/> class
/// </summary>
/// <param name="tenant"></param>
/// <param name="clientId"></param>
/// <param name="clientSecret"></param>
/// <param name="scope"></param>
/// <param name="userName"></param>
/// <param name="password"></param>
/// <param name="scopeAr"></param>
public AzureROPCTokenProvider(
string tenant,
string clientId,
string clientSecret,
string userName,
string password,
string[] scopeAr)
{
this.tenant = tenant;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.userName = userName;
this.password = password;
this.scope = string.Join(" ", scopeAr);
}
/// <summary>
/// Gets oAuth access token.
/// </summary>
/// <param name="ignoreExistingToken">
/// If ignoreExistingToken is true, requests new token from a server. Otherwise behaviour is depended on whether token exists or not.
/// If token exists and its expiration date is not expired returns current token, otherwise requests new token from a server.
/// </param>
/// <returns>Returns oAuth access token</returns>
public virtual OAuthToken GetAccessToken(bool ignoreExistingToken)
{
lock (tokenSyncObj)
{
if (this.token != null && !this.token.Expired && !ignoreExistingToken)
return this.token;
token = null;
string uri = string.Format(uriFormat, string.IsNullOrWhiteSpace(tenant) ? "common" : tenant);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
string body = string.Format(bodyFormat,
HttpUtility.UrlEncode(clientId),
HttpUtility.UrlEncode(scope),
HttpUtility.UrlEncode(userName),
HttpUtility.UrlEncode(password),
HttpUtility.UrlEncode(grant_type));
byte[] bytes = Encoding.ASCII.GetBytes(body);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = bytes.Length;
MemoryStream ms = new MemoryStream(bytes);
using (Stream requestStream = request.GetRequestStream())
requestStream.Write(bytes, 0, bytes.Length);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StringBuilder responseText = new StringBuilder();
bytes = new byte[1024];
int read = 0;
using (Stream stream = response.GetResponseStream())
{
while ((read = stream.Read(bytes, 0, bytes.Length)) > 0)
responseText.Append(Encoding.ASCII.GetString(bytes, 0, read));
}
string jsonString = responseText.ToString();
AzureTokenResponse t = JsonConvert.DeserializeObject<AzureTokenResponse>(jsonString);
token = new OAuthToken(
t.access_token,
TokenType.AccessToken,
DateTime.Now.AddSeconds(t.expires_in));
return token;
}
}
/// <summary>
/// Gets oAuth access token.
/// If token exists and its expiration date is not expired returns current token, otherwise requests new token from a server.
/// </summary>
/// <returns>Returns oAuth access token</returns>
public OAuthToken GetAccessToken()
{
return GetAccessToken(false);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public virtual void Dispose()
{
}
}
}
your provided mehtod
public virtual OAuthToken GetAccessToken(bool ignoreExistingToken)
fails on the following line
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
HTTP 401 Unauthorized
{“error”:“invalid_client”,“error_description”:"AADSTS7000218: The request body must contain the following parameter: ‘client_assertion’ or ‘client_secret’
I have included the information in concerned ticket and will share the feedback with you as soon as it will be resolved.
Please also forward my last post to your developers, because this would solve the problem quickly.
I have included the information in our issue tracking system and will share the feedback with you as soon as it will be fixed.
In order to perform impersonation you have to invoke method IEWSClient.ImpersonateUser.
Your answer does not help.
In order to perform IEWSClient.ImpersonateUser I need an instance of EWSClient.
To create a new Instance of EWSClient I need to call EWSClient.GetEWSClient(…)
BUT when using OAuthNetworkCredential I am not able to call GetEWSClient
because I receive Exception “ExchangeImpersonation SOAP header must be present”.
So please add an overload to EWSClient.GetEWSClient() where the missing ExchangeImpersonation SOAP header can be provided.
I have associated the information in our issue tracking system for our team’s review and will share the feedback with you as soon as it will be shared.
+1
I have finally made it to the point where I am getting an access token and thought I was past the worst of it, but now I find myself in the same predicament as you. With the retirement of basic auth for Office 365 eminent this week, I am thinking I might have to switch over to the Exchange Service. I’ll let you know if I have any luck.
Aspose.Email version 20.9.1.0 seems to have resolved this issue.
Set the oAuthNetworkCredential.UserName to the e-mail account you wish to impersonate before calling getEWSClient and it will sucessfully return a client. It is worth noting that this had no effect in prior versions of Aspose.EMail.
Yes, thank you. The new version works very well.
I have the exact use case as the topic starter but within Java instead of .NET. The topic says that it only works from version 20.9.1.0 from the Aspose.Email library. I’m using 20.9 in Java and I still have the same problems as in .NET prior to version 20.9.1.0. There isn’t a Java version for this.
Can the solution used in 20.9.1.0 for .NET also be implemented in Java?
We will get back to you with feedback regarding Java based implementation for the same after discussion with team.