I have created a Service bus queue. The URL for this is:
https://ns-eventqueue.servicebus.windows.net/eventqueue
I have also created a Shared Access Policy for this. The Policy name is EventPolicy.
When I try to use this policy and URL to connect to the service bus queue I get the following error:
40400: Endpoint Not Found
What am I doing wrong? Here is the code I am using:
// Uri to the Service Bus Queue
Uri uri = ServiceBusEnvironment.CreateServiceUri("sb", "ns-eventqueue", "EventQueue");
// Shared Access Signature (SAS) Authentication
string name = "EventPolicy";
string key = "TheKeyValue";
// Token Provider
TokenProvider tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(name, key);
// Create a Messaging Factory
MessagingFactory factory = MessagingFactory.Create(uri, tokenProvider);
string filePath = #"C:\Temp\VTData\Ring Buffer.xml";
byte[] data = File.ReadAllBytes(filePath);
BrokeredMessage bm = new BrokeredMessage(data);
bm.Label = "Ring Buffer File";
try
{
MessageSender sender = factory.CreateMessageSender("EventQueue");
sender.Send(bm);
}
catch (Exception ex)
{
throw;
}
The issue was with the 3rd parameter of ServiceBusEnvironment.CreateServiceUri method. The third parameter was supposed to be blank.
Related
TL;DR
why does AmazonApiGatewayManagementApiClient throwing an AggregateException when trying to use a cloudfront url as ServiceUrl? System.AggregateException: One or more errors occurred. (Credential should be scoped to a valid region, not 'us-east-1'. )
Details
We have a running system were devices connect via websocket to AWS API Gateway.
Device (web socket client) <-> AWS_API_Gateway <-> .net service
this worked good until today. We used the urls provided in the AWS console
We thought it might be a good idea to use cloudfront in order to provide a "real" url (something like https://urlviaCloudfront.mydomain.com/DeviceCenterApi). The device can connect to the url from outside. The connect route is called. The communication from the device to the service does work.
BUT when we try to send data to the connection with the AmazonApiGatewayManagementApiClient we get a exception:
System.AggregateException: One or more errors occurred. (Credential should be scoped to a valid region, not 'us-east-1'. )
This does not happen if we use the "direct" link instead of the custom domain name.
I tried to simplify the code to provide a [mcve]:
AmazonApiGatewayManagementApiConfig configuration = new AmazonApiGatewayManagementApiConfig()
{
RegionEndpoint = Amazon.RegionEndpoint.GetBySystemName("eu-west-1"),
ServiceURL = "https://GENERATEDID.execute-api.eu-west-1.amazonaws.com/DeviceCenterApi",
};
//configuration with cloud front url
AmazonApiGatewayManagementApiConfig notWorkingConfiguration = new AmazonApiGatewayManagementApiConfig()
{
RegionEndpoint = Amazon.RegionEndpoint.GetBySystemName("eu-west-1"), // also tried "us-east-1" -> same exception
ServiceURL = "https://urlviaCloudfront.mydomain.com/DeviceCenterApi",
};
AmazonApiGatewayManagementApiClient client = new AmazonApiGatewayManagementApiClient(configuration);
string connectionId = "M23IVdFmxxxxxxxx=";
PostToConnectionRequest awsRequest = new PostToConnectionRequest
{
ConnectionId = connectionId,
Data = "Hello world",
};
PostToConnectionResponse response = await client.PostToConnectionAsync(awsRequest);
Any idea how we can avoid this exception?
I found the reason why this is failing...
https://github.com/aws/aws-lambda-dotnet/issues/605
Basically AuthenticationRegion needs to be added to the config.
AmazonApiGatewayManagementApiConfig configuration = new AmazonApiGatewayManagementApiConfig()
{
AuthenticationRegion = "eu-west-1",
RegionEndpoint = Amazon.RegionEndpoint.GetBySystemName("eu-west-1"),
ServiceURL = "https://GENERATEDID.execute-api.eu-west-1.amazonaws.com/DeviceCenterApi",
};
I am getting while setting meta data in azure data lake file using azure fileclient, it's throwing the exception like "Specified value has invalid HTTP Header characters. Parameter name: name in azure data lake"
I am using the Dictionary to set metadata.
PathHttpHeaders path = new PathHttpHeaders();
path.ContentType = "application/octet-stream";
fileClient.SetHttpHeaders(path);
var metaDataProperties = await GetMetaDataProperties(entityData);
await fileClient.SetMetadataAsync(metaDataProperties);
The above issue has been resolved, the issue was coming due to the FileClient instance, I was using the same FileClient instance which I have used to store the file into the Azure data lake. For resolving the issue I have created a new FileClient Instance and that worked for me. Below is my code.
private async Task SetMetaDataProps(MessageWrapper wrapper, Uri uri, string filename)
{
try
{
var entityData = JObject.Parse(wrapper.Payload);
entityData["FileName"] = filename;
var storageCredentials = new StorageSharedKeyCredential("accountName", "accountKey");
var fileclient = new DataLakeFileClient(uri, storageCredentials);
var metaDataProps = await GetMetaDataProperties(entityData);
var ss = fileclient.SetMetadataAsync(metaDataProps);
}
catch(Exception ex)
{
throw ex;
}
}
[HttpGet]
[Route("api/sender")]
public string Sender(string message, string hostName)
{
try
{
Send("myQ1", message, hostName);
return "successfully message sent to Queue";
}
catch (Exception ex)
{
return ex.Message;
}
}
public static void Send(string queue, string data,string hostName)
{
var factory = new ConnectionFactory() { HostName = hostName };
using (IConnection connection = factory.CreateConnection())
{
using (IModel channel = connection.CreateModel())
{
channel.QueueDeclare(queue, false, false, false, null);
channel.BasicPublish(string.Empty, queue, null, Encoding.UTF8.GetBytes(data));
}
}
}
I have configured RabbitMQ cluster on Azure AKS(Hybrid cluster Win+Linux) using helm and then deployed the above code by creating docker image using LoadBalancer Service to access the application. So, my API URL is like :
http://x.x.x.x/api/sender?message=Hi&hostname=rabbit#my-release-rabbitmq-0.my-release-rabbitmq-headless.default.svc.cluster.local
Here I am passing hostname dynamically to this API as : rabbit#my-release-rabbitmq-0.my-release-rabbitmq-headless.default.svc.cluster.local
I have port forwarded from 15672 to 3000 to access RabbitMQ Dashboard from my native computer like below image.
But not able to send the message to Queue.
So, my question is what should I pass as host name in the API I have created(above code) to send message to RabbitMQ queue
Below is my AKS cluster deployment details:
I have modified the code like:
var factory = new ConnectionFactory() { HostName = hostName, UserName="RabbitMQ Username",Password="RabitMQ Password" };
Then I passed the hostname="my-release-rabbitmq-headless";
This is the service name.
Now its working as expected.
I am writing a windows desktop application with External Authentication(Google, Facebook) in C#.
I'm using HttpListener to allow a user to get Barer token by External Authentication Service with ASP.NET Web API, but administrator privileges are required for that and I want run without admin mode.
My reference was Sample Desktop Application for Windows.
Is this the best practice for external authentication provider from C#? Or is there another way to do that?
This is my code to get Barer token by external provider:
public static async Task<string> RequestExternalAccessToken(string provider)
{
// Creates a redirect URI using an available port on the loopback address.
string redirectURI = string.Format("http://{0}:{1}/", IPAddress.Loopback, GetRandomUnusedPort());
// Creates an HttpListener to listen for requests on that redirect URI.
var http = new HttpListener();
http.Prefixes.Add(redirectURI);
http.Start();
// Creates the OAuth 2.0 authorization request.
string authorizationRequest = Properties.Settings.Default.Server
+ "/api/Account/ExternalLogin?provider="
+ provider
+ "&response_type=token&client_id=desktop"
+ "&redirect_uri="
+ redirectURI + "?";
// Opens request in the browser.
System.Diagnostics.Process.Start(authorizationRequest);
// Waits for the OAuth authorization response.
var context = await http.GetContextAsync();
// Sends an HTTP response to the browser.
var response = context.Response;
string responseString = string.Format("<html><head></head><body></body></html>");
var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
var responseOutput = response.OutputStream;
Task responseTask = responseOutput.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) =>
{
responseOutput.Close();
http.Stop();
Console.WriteLine("HTTP server stopped.");
});
// Checks for errors.
if (context.Request.QueryString.Get("access_token") == null)
{
throw new ApplicationException("Error connecting to server");
}
var externalToken = context.Request.QueryString.Get("access_token");
var path = "/api/Account/GetAccessToken";
var client = new RestClient(Properties.Settings.Default.Server + path);
RestRequest request = new RestRequest() { Method = Method.GET };
request.AddParameter("provider", provider);
request.AddParameter("AccessToken", externalToken);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
var clientResponse = client.Execute(request);
if (clientResponse.StatusCode == HttpStatusCode.OK)
{
var responseObject = JsonConvert.DeserializeObject<dynamic>(clientResponse.Content);
return responseObject.access_token;
}
else
{
throw new ApplicationException("Error connecting to server", clientResponse.ErrorException);
}
}
I don't know about Facebook, but usually (I am experienced with Google OAuth2 and Azure AD as well as Azure AD B2C), the authentication provider allows you to use a custom URI scheme for the authentication callback, something like badcompany://auth
To acquire an authentication token I ended up implementing the following scheme (All code is presented without warranty and not to be copied thoughtlessly.)
1. Register an URI-handler when the app is started
You can register an URI-Handler by creating a key in the HKEY_CURRENT_USER/Software/Classes (hence no admin privileges needed) key in the Windows registry
The name of the key equals the URI prefix, badcompany in our case
The key contains an empty string value named URL Protocol
The key contains a subkey DefaultIcon for the icon (actually I do not know whether this is necessary), I used the path of the current executable
There is a subkey shell/open/command, whose default value determines the path of the command to execute when the URI is tried to be opened, **please note*, that the "%1" is necessary to pass the URI to the executable
this.EnsureKeyExists(Registry.CurrentUser, "Software/Classes/badcompany", "URL:BadCo Applications");
this.SetValue(Registry.CurrentUser, "Software/Classes/badcompany", "URL Protocol", string.Empty);
this.EnsureKeyExists(Registry.CurrentUser, "Software/Classes/badcompany/DefaultIcon", $"{location},1");
this.EnsureKeyExists(Registry.CurrentUser, "Software/Classes/badcompany/shell/open/command", $"\"{location}\" \"%1\"");
// ...
private void SetValue(RegistryKey rootKey, string keys, string valueName, string value)
{
var key = this.EnsureKeyExists(rootKey, keys);
key.SetValue(valueName, value);
}
private RegistryKey EnsureKeyExists(RegistryKey rootKey, string keys, string defaultValue = null)
{
if (rootKey == null)
{
throw new Exception("Root key is (null)");
}
var currentKey = rootKey;
foreach (var key in keys.Split('/'))
{
currentKey = currentKey.OpenSubKey(key, RegistryKeyPermissionCheck.ReadWriteSubTree)
?? currentKey.CreateSubKey(key, RegistryKeyPermissionCheck.ReadWriteSubTree);
if (currentKey == null)
{
throw new Exception("Could not get or create key");
}
}
if (defaultValue != null)
{
currentKey.SetValue(string.Empty, defaultValue);
}
return currentKey;
}
2. Open a pipe for IPC
Since you'll have to pass messages from one instance of your program to another, you'll have to open a named pipe that can be used for that purpose.
I called this code in a loop in a background Task
private async Task<string> ReceiveTextFromPipe(CancellationToken cancellationToken)
{
string receivedText;
PipeSecurity ps = new PipeSecurity();
System.Security.Principal.SecurityIdentifier sid = new System.Security.Principal.SecurityIdentifier(System.Security.Principal.WellKnownSidType.WorldSid, null);
PipeAccessRule par = new PipeAccessRule(sid, PipeAccessRights.ReadWrite, System.Security.AccessControl.AccessControlType.Allow);
ps.AddAccessRule(par);
using (var pipeStream = new NamedPipeServerStream(this._pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous, 4096, 4096, ps))
{
await pipeStream.WaitForConnectionAsync(cancellationToken);
using (var streamReader = new StreamReader(pipeStream))
{
receivedText = await streamReader.ReadToEndAsync();
}
}
return receivedText;
}
3. Make sure that the application is started only once
This can be acquired using a Mutex.
internal class SingleInstanceChecker
{
private static Mutex Mutex { get; set; }
public static async Task EnsureIsSingleInstance(string id, Action onIsSingleInstance, Func<Task> onIsSecondaryInstance)
{
SingleInstanceChecker.Mutex = new Mutex(true, id, out var isOnlyInstance);
if (!isOnlyInstance)
{
await onIsSecondaryInstance();
Application.Current.Shutdown(0);
}
else
{
onIsSingleInstance();
}
}
}
When the mutex has been acquired by another instance, the application is not fully started, but
4. Handle being called with the authentication redirect URI
If it's the only (first) instance, it may handle the authentication redirect URI itself
Extract the token from the URI
Store the token (if necessary and/or wanted)
Use the token for requests
If it's a further instance
Pass the redirect URI to the first instance by using pipes
The first instance now performs the steps under 1.
Close the second instance
The URI is sent to the first instance with
using (var client = new NamedPipeClientStream(this._pipeName))
{
try
{
var millisecondsTimeout = 2000;
await client.ConnectAsync(millisecondsTimeout);
}
catch (Exception)
{
onSendFailed();
return;
}
if (!client.IsConnected)
{
onSendFailed();
}
using (StreamWriter writer = new StreamWriter(client))
{
writer.Write(stringToSend);
writer.Flush();
}
}
To add to Paul's excellent answer:
Identity Model Libraries are worth looking at - one of the things they'll do for you is Authorization Code Flow (PKCE) which is recommended for native apps
My preference is the same as Paul's - to use custom URI schemes - usability is better I think
Having said that, a loopback solution should work without admin rights for ports greater than 1024
If it helps there is some stuff on my blog about this - including a Nodejs / Electron sample you can run from here to see what a finished solution looks like.
How can i get client certificate when he connect to the signalr Hub? My code look like this i read certificate from file and then trying to connect to the hub. I want to create certifate object on hub and read some information from it.
Hub code:
public class ServerHub:Hub
{
public override Task OnConnectedAsync()
{
string connectionId = Context.ConnectionId;
//get certificate ? `X509Certificate2 cert = new X509Certificate2(Certificate);`
return base.OnConnectedAsync();
}
Client code
string Certificate = #"C:\Users\StażCRM\Downloads\sample.cer";
X509Certificate2 cert = new X509Certificate2(Certificate);
string resultsTrue = cert.ToString(true);
// Display the value to the console.
Console.WriteLine(resultsTrue);
// Get the value.
string resultsFalse = cert.ToString(false);
// Display the value to the console.
Console.WriteLine(resultsFalse);
this.id = id;
HubConnection con = new HubConnectionBuilder().WithUrl("https://localhost:44375/ClinicServer",opt=>opt.ClientCertificates.Add(cert)).Build();
con.StartAsync().Wait();
According to this answer, you can get the certificate directly from the HttpContext.
SignalR with Client Certificate Authentication
However, the code is for .NET framework.
When using asp.net core I think you can get the certificate like this
this.Context.GetHttpContext().Connection.ClientCertificate