I have implemented an Azure Service Bus REST API client. At the moment I am building xUnit tests for my REST project and need to create a Queue with a name provided by the test, send messages with the REST client and then delete the Queue with that specific name.
One of my requirements are to specify a Shared Access Policy for the newly created Queue with only Send permissions programmatically but I can't find anything online that suggests that this is possible.
So far I have this
TokenProvider credentials = TokenProvider.CreateSharedAccessSignatureTokenProvider("MyBusAccessPolicy", "XXXXXXXXXXXXXXXX");
NamespaceManager namespaceManager = new NamespaceManager(ServiceBusEnvironment.CreateServiceUri("sb", _serviceNamespace, string.Empty), credentials);
QueueDescription queueDescription = await namespaceManager.CreateQueueAsync(queueName);
How would I proceed to create the Shared Access Policy specifically for that queue if even possible?
Neil,
Something like this should work:
string queuePolicyName = "SendPolicy";
string queuePrimaryKey = SharedAccessAuthorizationRule.GenerateRandomKey();
QueueDescription queueDescription = new QueueDescription(queueName);
SharedAccessAuthorizationRule queueSharedAccessPolicy = new SharedAccessAuthorizationRule(queuePolicyName, queuePrimaryKey, new[] { AccessRights.Send });
queueDescription.Authorization.Add(queueSharedAccessPolicy);
await _namespaceManager.CreateQueueAsync(queueDescription);
Related
I need to read emails using C# (concrete task - count of received/sent emails by range).
Ideally, user enters credentials of his Microsoft Office/Exchange email on webpage and receive this info.
I see the following ways to implement it.
ExchangeService
using nuget package Microsoft.Exchange.WebServices we can get access to email profile, e.g.
ExchangeService _service;
_service = new ExchangeService(ExchangeVersion.Exchange2013_SP1)
{
Credentials = new WebCredentials("myemail#hotmail.com", "mypassword"),
};
_service.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
_service.TraceEnabled = true;
_service.TraceFlags = TraceFlags.All;
var items = _service.FindItems(WellKnownFolderName.Inbox, searchFilter: new SearchFilter.IsGreaterThan(TaskSchema.DateTimeCreated, DateTime.Now.AddDays(-10)), new ItemView(int.MaxValue));
sometimes this approach works, but for some cases does not work (for example, if 2-way authentication is enabled)
using IAuthenticationProvider and Active Directory Tenant Id
there is a way to impement own IAuthenticationProvider with implementation AuthenticateRequestAsync like this
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
var token = await GetTokenAsync();
request.Headers.Authorization = new AuthenticationHeaderValue(token.TokenType, token.AccessToken);
}
after this we can create GraphServiceClient like this:
GraphServiceClient graphServiceClient =
new GraphServiceClient(_authenticationProvider, _graphHttpProvider);
and can send email, for example:
await graphServiceClient.Users[fromAddress]
.SendMail(message, false)
.Request()
.PostAsync();
I suppose, similar way I can get access to folders also
For this approach, I assume, I need to get tenantId etc (I'm a little confused with it)
How to implement stable solution to read Microsoft Office/Exchange emails ?
I would avoid the first approach EWS is now legacy and in that example your using Basic Authentication which is depreciated and will be disabled in October this year (if it hasn't already been).
For 2 you need to create a Azure Application registration see https://learn.microsoft.com/en-us/graph/use-the-api which has some detailed documentation and walkthroughs on how to do that. There's also https://learn.microsoft.com/en-us/graph/tutorials/aspnet-core which is a pretty good example of building a webapp that takes you through all the steps.
The graphQLClient package: https://github.com/graphql-dotnet/graphql-client
Integrating queries and mutations through the GraphQL client package was pretty easy. Since I was able to add authentication through a custom HttpMessageHandler and I was then able to use the AWSV4Signer to sign the request before it is sent. The problem which I am currently facing with subscriptions is that this message handler is not being invoked for the requests sent through the subscriptions. Which makes sense since it uses WebSockets and not Http. My question is whether I can use something like the HttpMessageHandler but for web sockets in order to sign the requests(more generally, edit them before they are sent) or I will just have to build and send the web socket requests from scratch like in this post:
AWS Appsync implementation using GraphQL-client library in .Net
The code for sending a mutation or query:
var graphQlRequest = new GraphQLRequest
{
Query = mutation
};
var graphQlClient = new GraphQLHttpClient(new GraphQLHttpClientOptions
{
EndPoint = new Uri(endpoint),
HttpMessageHandler = new AWSMessageHandler(accessKey, secretKey)
},
new NewtonsoftJsonSerializer());
graphQlClient.HttpClient.DefaultRequestHeaders.Add("x-amz-security-token", sessionToken);
var respoonse = await graphQlClient.SendMutationAsync<T>(graphQlRequest);
The AWSMessageHandler is a custom class I built, It simply signs the HttpRequestMessage before it is sent.
My web service is currently doing basic username/password authentication in order to subscribe the exchange user for receiving the events (like new mail event etc) like below:
var service = new ExchangeService(exchangeVersion)
{
KeepAlive = true,
Url = new Uri("some autodiscovery url"),
Credentials = new NetworkCredential(username, password)
};
var subscription = service.SubscribeToPushNotifications(
new[] { inboxFolderFoldeID },
new Uri("some post back url"),
15,
null,
EventType.NewMail,
EventType.Created,
EventType.Deleted,
EventType.Modified,
EventType.Moved,
EventType.Copied);
Now, I am supposed to replace the authentication mechanism to use OAuth protocol. I saw some examples but all of them seem to be talking about authenticating the client (https://msdn.microsoft.com/en-us/library/office/dn903761%28v=exchg.150%29.aspx?f=255&MSPPError=-2147217396) but nowhere I was able to find an example of how to authenticate an exchange user with OAuth protocol. Any code sample will help a lot. Thanks.
It's not clear what you mean with 'web service' and how you currently get the username and password. If that is some kind of website where the user needs to login or pass credentials, then you'll have to start an OAuth2 grant from the browser as in redirecting the clients browser to the authorize endpoint to start implicit grant or code grant. The user will be presented a login screen on the OAuth2 server (and not in your application), once the user logs in a code or access token (depending on the grant) will be returned to your application which you can use in the ExchangeService constructor.
If that 'web' service is some service that runs on the users computer you can use one of the methods described below.
Get AccessToken using AuthenticationContext
The example seems to be based on an older version of the AuthenticationContext class.
The other version seems to be newer, also the AcquireToken is now renamed to AcquireTokenAsync / AcquireTokenSilentAsync.
No matter which version you're using, you will not be able to pass username and password like you're doing in your current code. However, you can let the AcquireToken[Async] method prompt for credentials to the user. Which, let's be honest, is more secure then letting your application deal with those user secrets directly. Before you know, you'll be storing plain text passwords in a database (hope you aren't already).
In both versions, those methods have a lot of overloads all with different parameters and slightly different functionality. For your use-case I think these are interesting:
New: AcquireTokenAsync(string, string, Uri, IPlatformParameters) where IPlatformParameters could be new PlatformParameters(PromptBehavior.Auto)
Old: AcquireToken(string, string, Uri, PromptBehavior where prompt behavior could be PromptBehavior.Auto
Prompt behavior auto, in both vesions, means: the user will be asked for credentials when they're not already cached. Both AuthenticationContext constructors allow you to pass a token-cache which is something you can implement yourself f.e. to cache tokens in memory, file or database (see this article for an example file cache implementation).
Get AccessToken manually
If you really want to pass in the user credentials from code without prompting the user, there is always a way around. In this case you'll have to implement the Resource Owner Password Credentials grant as outlined in OAuth2 specificatioin / RFC6749.
Coincidence or not, I have an open-source library called oauth2-client-handler that implements this for use with HttpClient, but anyway, if you want to go this route you can dig into that code, especially starting from this method.
Use Access Token
Once you have an access token, you can proceed with the samples on this MSDN page, f.e.:
var service = new ExchangeService(exchangeVersion)
{
KeepAlive = true,
Url = new Uri("some autodiscovery url"),
Credentials = new OAuthCredentials(authenticationResult.AccessToken))
};
In case someone is still struggling to get it to work. We need to upload a certificate manifest on azure portal for the application and then use the same certificate to authenticate the client for getting the access token. For more details please see: https://blogs.msdn.microsoft.com/exchangedev/2015/01/21/building-daemon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow/
Using the example code in this Microsoft Document as the starting point and these libraries:
Microsoft Identity Client 4.27
EWS Managed API v2.2
I am able to successfully authenticate and connect with Exchange on Office 365.
public void Connect_OAuth()
{
var cca = ConfidentialClientApplicationBuilder
.Create ( ConfigurationManager.AppSettings[ "appId" ] )
.WithClientSecret( ConfigurationManager.AppSettings[ "clientSecret" ] )
.WithTenantId ( ConfigurationManager.AppSettings[ "tenantId" ] )
.Build();
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
AuthenticationResult authResult = null;
try
{
authResult = cca.AcquireTokenForClient( ewsScopes ).ExecuteAsync().Result;
}
catch( Exception ex )
{
Console.WriteLine( "Error: " + ex );
}
try
{
var ewsClient = new ExchangeService();
ewsClient.Url = new Uri( "https://outlook.office365.com/EWS/Exchange.asmx" );
ewsClient.Credentials = new OAuthCredentials( authResult.AccessToken );
ewsClient.ImpersonatedUserId = new ImpersonatedUserId( ConnectingIdType.SmtpAddress, "ccc#pppsystems.co.uk" );
ewsClient.HttpHeaders.Add( "X-AnchorMailbox", "ccc#pppsystems.co.uk" );
var folders = ewsClient.FindFolders( WellKnownFolderName.MsgFolderRoot, new FolderView( 10 ) );
foreach( var folder in folders )
{
Console.WriteLine( "" + folder.DisplayName );
}
}
catch( Exception ex )
{
Console.WriteLine( "Error: " + ex );
}
}
The Microsoft example code did not work - the async call to AcquireTokenForClient never returned.
By calling AcquireTokenForClient in a separate try catch block catching a general Exception, removing the await and using .Result, this now works - nothing else was changed.
I realise that this is not best practice but, both with and without the debugger, the async call in the original code never returned.
In the Azure set-up:
A client secret text string was used - a x509 certificate was not necessary
The configuration was 'app-only authentication'
Hope this helps someone avoid hours of frustration.
I'm using Microsoft.WindowsAzure.Management and Microsoft.IdentityModel.Clients.ActiveDirectory packages trying to work with Azure Management API from C# code, but when I try to retrieve some data from it, I'm always getting the error:
ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription.
There's the code sample I'm using:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.WindowsAzure.Management;
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.windows.net/mytenant");
var cc = new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential("azure-app-id", "azure-app-secret");
var token = await authContext.AcquireTokenAsync("https://management.core.windows.net/", cc);
var tokenCred = new Microsoft.Azure.TokenCloudCredentials(token.AccessToken);
var client = new ManagementClient(tokenCred);
// this is where I get the error:
var subscriptions = await client.Subscriptions.GetAsync(CancellationToken.None);
I believe you're getting this error is because the Service Principal (or in other words the Azure AD application) does not have permission on your Azure Subscription. You would need to assign a role to this Service Principal.
Please see this link regarding how you can assign a role in an Azure Subscription to a Service Principal: https://learn.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal#assign-application-to-role.
Once you do that, the error should go away.
I can reproduce this issue too. And to list the subscription, we need to use the SubscriptionClient instead of ManagementClient. Here is the code which works well for me:
var token = "";
var tokenCred = new Microsoft.Azure.TokenCloudCredentials(token);
var subscriptionClient = new SubscriptionClient(tokenCred);
foreach (var subscription in subscriptionClient.Subscriptions.List())
{
Console.WriteLine(subscription.SubscriptionName);
}
Note:To make the code work, we need to acquire token using the owner of the subscription instead of the certificate.
I have UI where I have the user enter the Url where I can find the Exchange Web Service(EWS). My application uses EWS to get Free/Busy information but my configuration tool just needs to set it for the user.
For now I am just asking for the host name and building up the Url from there. For example they enter example.org as the host and I build https://example.org/EWS/Exchange.asmx from that.
I would like to add a test button to ensure the host they entered is reachable by the machine they are configuring. But I'm not sure how simple or complex I need to be to test the service.
Is there any noop or bind I can do to make sure I can establish communication with EWS?
Something like:
var serviceUri = new Uri(_textBoxEwsUrl.Text));
var exchangeService = new ExchangeService();
exchangeService.Url = serviceUri;
// what can I call here to test that I can talk to the exchangeService?
exchangeService.????
Bind to the inbox folder of the users mail address. Of course, you would need his credentials to this.
Any kind of operation that results in communication with server can be used as test. Simple binding to any folder or item will throw ServiceRequestException or some different type of exception if your url or credentials are incorrect.
Sample code:
try
{
var inbox = Folder.Bind(service, WellKnownFolderName.Inbox);
}
catch(ServiceRequestException e)
{
//handle exception in some way
}