ServiceBus throws 401 Unauthorized Error - c#

I'm using a simple implementation of the Windows Service Bus 1.0 Brokered messaging to keep track of the user interactions with a particular web application.
Every time something is saved to a "sensitive" table in the database, I have setup the repository layer send a message like so:
ServiceBus.MessageQueue<T>.PushAsync(entity);
which will then serialize the entity and create a message out of it.
My MessageQueue class is something like this.
public static class MessageQueue<T>
{
static string ServerFQDN;
static int HttpPort = 9355;
static int TcpPort = 9354;
static string ServiceNamespace = "ServiceBusDefaultNamespace";
public static void PushAsync(T msg)
{
ServerFQDN = System.Net.Dns.GetHostEntry(string.Empty).HostName;
//Service Bus connection string
var connBuilder = new ServiceBusConnectionStringBuilder { ManagementPort = HttpPort, RuntimePort = TcpPort };
connBuilder.Endpoints.Add(new UriBuilder() { Scheme = "sb", Host = ServerFQDN, Path = ServiceNamespace }.Uri);
connBuilder.StsEndpoints.Add(new UriBuilder() { Scheme = "https", Host = ServerFQDN, Port = HttpPort, Path = ServiceNamespace}.Uri);
//Create a NamespaceManager instance (for management operations) and a MessagingFactory instance (for sending and receiving messages)
MessagingFactory messageFactory = MessagingFactory.CreateFromConnectionString(connBuilder.ToString());
NamespaceManager namespaceManager = NamespaceManager.CreateFromConnectionString(connBuilder.ToString());
if (namespaceManager == null)
{
Console.WriteLine("\nUnexpected Error");
return;
}
//create a new queue
string QueueName = "ServiceBusQueueSample";
if (!namespaceManager.QueueExists(QueueName))
{
namespaceManager.CreateQueue(QueueName);
}
try
{
QueueClient myQueueClient = messageFactory.CreateQueueClient(QueueName);
string aaa = JsonConvert.SerializeObject(msg, Formatting.Indented,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ContractResolver = new NHibernateContractResolver()
});
BrokeredMessage sendMessage1 = new BrokeredMessage(aaa);
sendMessage1.Properties.Add("UserName",Thread.CurrentPrincipal.Identity.Name);
sendMessage1.Properties.Add("TimeStamp", ApplicationDateTime.Now);
sendMessage1.Properties.Add("Type", msg.GetType().Name);
myQueueClient.Send(sendMessage1);
}
catch (Exception e)
{
var l = new Logger();
l.Log(LogEventEnum.WebrequestFailure, e.Message);
Console.WriteLine("Unexpected exception {0}", e.ToString());
throw;
}
}
}
This works flawlessly when I debug this locally. But when I publish the site in IIS and run, the namespaceManager.QueueExists(QueueName) call fails with an exception which says "401 Unauthorized error".
When I change the Application pool identity (in IIS) to an admin account this error does not occur. However, there is absolutely no way that I can make this happen when we go production.
Am I missing something? If so, what is it? Any help is greatly appreciated.

Did you read the security section in the docs, Chameera? http://msdn.microsoft.com/en-us/library/windowsazure/jj193003(v=azure.10).aspx
You seem to be running with the default security settings, meaning you only have admin accounts authorized. Review the documentation section and grant the requisite rights to the accounts you want to use in prod.

Related

Ws-Trust authentication which has been deprecated and no longer supported in your environment. Please use oAuth2.0 authentication

In Current Project i am using this syntax to retrieve data from Dynamic CRM:
using (OrganizationServiceProxy serviceProxy = new OrganizationServiceProxy(organizationUri, null, _crmCredentials, null)
{
// Creating IOrganizationService object to access organization services
IOrganizationService service = serviceProxy;
EntityCollection retrieveAccountGuid = service.RetrieveMultiple(QueryOptions);
return retrieveAccountGuid;
}
Any one please help me what changes i need to do?
I have Read this article but how to replace IOrganizationService in code?
https://learn.microsoft.com/en-us/power-apps/developer/data-platform/authenticate-office365-deprecation
The CrmServiceClient is the class we can use to connect to Dynamics 365 in .NET. It supports multiple authentication scenarios. For server-to-server communications on Azure and the Power Platform working with app registrations is the recommended approach.
App registrations accessing Dynamics/Dataverse need to be added as application users and must be assigned an appropriate security role. See also Register an app with Azure Active Directory - MS Docs.
CrmServiceClient implements the IOrganizationService interface, so the majority of your code should work just the same way. Just replace the instantation of the OrganizationServiceProxy by something like this:
private void Test(Uri dataverseUrl, Guid clientId, string clientSecret, string tokenCachePath)
{
using (var client = new Microsoft.Xrm.Tooling.Connector.CrmServiceClient(dataverseUrl, clientId.ToString("D"), clientSecret, false, tokenCachePath))
{
Perform(client);
}
}
private void Perform(IOrganizationService organizationService)
{
}
CrmServiceClient can be found in NuGet package Microsoft.CrmSdk.XrmTooling.CoreAssembly.
public CrmServiceClient devuelve_Servicio()
{
CrmServiceClient svc = null;
string ConnectionString = "AuthType = OAuth; " +
"Username = ;" +
"Password = ; " +
"Url = https://.crm.dynamics.com;" +
"AppId=;" +
"RedirectUri=app://58145B91-0C36-4500-8554-080854F2AC97;" +
"LoginPrompt=Auto";
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
try
{
CrmServiceClient svc = new CrmServiceClient(ConnectionString);
if (svc != null)
{
this.strErrMsg = "Error en la conexión!!!" + "LastError: " + svc.LastCrmError.ToString();
}
}
catch (Exception ex)
{
this.strErrMsg = ex.Message;
}
return svc;
}
}

Can't configure credentials WCF with basic realm?

I have added a Connected Reference in Visual Studio 2019. It consumed a https endpoint, and created all binding information needed into a reference.cs file.
It didn't generate any App.config file, so I suspected what I needed was bundled into the reference.cs file. Indeed, looking into it, it mostly was.
So I tried creating a client, specify client credentials in two ways, as you can see, but still, doesn't matter how I specify it, I get an exception when calling this code below.
public async Task SendFile(Stream fileStream, string fileName, Guid machineKey)
{
_logger.LogInformation("Starting file sending to Manager 1.");
_logger.LogInformation($"Sending file {fileName} from Machine {machineKey}");
try
{
var client = new FileTransferClient(FileTransferClient.EndpointConfiguration.BasicHttpBinding_IFileTransfer, _options.FileTransferEndPoint)
{
ClientCredentials =
{
UserName =
{
UserName = _options.FileTransferUsername,
Password = _options.FileTransferPassword
}
}
};
client.ClientCredentials.UserName.UserName = _options.FileTransferUsername;
client.ClientCredentials.UserName.Password = _options.FileTransferPassword;
using (new OperationContextScope(client.InnerChannel))
{
}
await client.UploadAsync(new FileUploadMessage
{
// Assume that this is enough. Can't really supply file length...
FileInfo = new FileTransferInfo
{
TransferId = new Guid(),
MachineUUID = machineKey.ToString(),
Name = fileName
},
TransferStream = fileStream
});
}
catch (Exception e)
{
_logger.LogError("An unexpected exception occurred while sending file to Manager 1G.", e);
}
_logger.LogInformation("File sending finished.");
}
The exception is "The HTTP request is unauthorized with client authentication scheme 'Basic'. The authentication header received from the server was 'Basic Realm'."
I have compared to similar APIs that use the beforementioned App.config, and have edited the reference.cs to match the security I think it should have.
Specifically, I've added the security related lines here:
private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.BasicHttpBinding_IFileTransfer))
{
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true;
result.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.Transport;
result.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
result.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
return result;
}
if ((endpointConfiguration == EndpointConfiguration.MetadataExchangeHttpsBinding_IFileTransfer))
{
System.ServiceModel.Channels.CustomBinding result = new System.ServiceModel.Channels.CustomBinding();
System.ServiceModel.Channels.TextMessageEncodingBindingElement textBindingElement = new System.ServiceModel.Channels.TextMessageEncodingBindingElement();
result.Elements.Add(textBindingElement);
System.ServiceModel.Channels.HttpsTransportBindingElement httpsBindingElement = new System.ServiceModel.Channels.HttpsTransportBindingElement();
httpsBindingElement.AllowCookies = true;
httpsBindingElement.MaxBufferSize = int.MaxValue;
httpsBindingElement.MaxReceivedMessageSize = int.MaxValue;
result.Elements.Add(httpsBindingElement);
return result;
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
What I found dumbfounding, was that with embedding in the constructor calling setting the ClientCredentials, they were not in any way populated when I inspected the client with a debug session attached. Hence I tried to set it afterwards specifically.
But either way, the end result is the same, get the same error.
How can I resolve that error in Code?
I can in theory try to add an App.config and do it there, but I don't know the Contract. And I am not sure what to look for in the generated reference.cs to identify it. So I'd prefer to learn to do this by Code, as the Contract is already in place there, and I can supply the endpoint via the _options, so it should be able to configure for different environments by that.
Turned out I had indeed password and username exchanged, so fixing that helped me get past of this issue.

Conversation always restarts in Directline BOT channel websocket, how to keep it flowing?

I have built an app that needs to connect to a Bot DirectLine - websockets channel to interact in conversations via LUIS and sms with Twilio.
To make the bot talk to the app I wrote a mvc controller that relays messages.
I am not sure this approach is correct, I made it up from some samples.
It works, but the main problem is that my code seems to always start a new conversation when a message is received from the client, so the context is not maintained.
How can I keep the conversation flowing and not restarting at every message?
I mean, the steps should be, for example:
Bot: Hello, what's your name?
User: Carl
Bot: Pleased to meet you Carl!
instead I get:
Bot: Hello, what's your name?
User: Carl
Bot: Sorry, I can't help you with that.
like the conversation is restarted from scratch.
Here is my controller code (the Twilio webhook is set to https://mySmsMVCapp.azurewebsites.net/smsapp/):
public class smsappController : TwilioController
{
private static string directLineSecret = ConfigurationManager.AppSettings["DirectLineSecret"];
private static string botId = ConfigurationManager.AppSettings["BotId"];
const string accountSid = "obfuscated";
const string authToken = "obfuscated";
private static string fromUser = "DirectLineSampleClientUser";
private string SMSreply = "";
public async Task<TwiMLResult> Index(SmsRequest incomingMessage)
{
// Obtain a token using the Direct Line secret
var tokenResponse = await new DirectLineClient(directLineSecret).Tokens.GenerateTokenForNewConversationAsync();
// Use token to create conversation
var directLineClient = new DirectLineClient(tokenResponse.Token);
var conversation = await directLineClient.Conversations.StartConversationAsync();
using (var webSocketClient = new WebSocket(conversation.StreamUrl))
{
webSocketClient.OnMessage += WebSocketClient_OnMessage;
// You have to specify TLS version to 1.2 or connection will be failed in handshake.
webSocketClient.SslConfiguration.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12;
webSocketClient.Connect();
while (true)
{
string input = incomingMessage.Body;
if (!string.IsNullOrEmpty(input))
{
if (input.ToLower() == "exit")
{
break;
}
else
{
if (input.Length > 0)
{
Activity userMessage = new Activity
{
From = new ChannelAccount(fromUser),
Text = input,
Type = ActivityTypes.Message
};
await directLineClient.Conversations.PostActivityAsync(conversation.ConversationId, userMessage);
//break;
if (!string.IsNullOrEmpty(SMSreply))
{
var messagingResponse = new MessagingResponse();
var message = messagingResponse.AddChild("Message");
message.AddText(SMSreply); //send text
SMSreply = string.Empty;
return TwiML(messagingResponse);
}
}
}
}
}
}
return null;
}
private void WebSocketClient_OnMessage(object sender, MessageEventArgs e)
{
// Occasionally, the Direct Line service sends an empty message as a liveness ping. Ignore these messages.
if (!string.IsNullOrWhiteSpace(e.Data))
{
var activitySet = JsonConvert.DeserializeObject<ActivitySet>(e.Data);
var activities = from x in activitySet.Activities
where x.From.Id == botId
select x;
foreach (Activity activity in activities)
{
if (!string.IsNullOrEmpty(activity.Text))
{
SMSreply = activity.Text;
}
}
}
}
}
The issue was actually I wasn't saving and retrieving conversationID.
For the moment I am testing using a static variable to store the value.
Then I reconnect to the conversation with it and the conversation with the bot keeps in context.

RabbitMQ: Sending messages from one pc to another

I am trying to get a rabbitMQ queue set up that sits on one pc, and recieves messagess from other computers giving it tasks. I have followed all the tutorials on the rabbit website but these only apply to local host. Can someone explain how I get this same code to communicate across 2 computers, not just from the same computer.
I have the following code:
Sender.cs
class Send
{
static void Main(string[] args)
{
Console.WriteLine("------------------");
Console.WriteLine("RabbitMQ Test");
Console.WriteLine("------------------");
var factory = new ConnectionFactory() { HostName = "localHost" };
try
{
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare("abc", false, false, false, null);
Console.WriteLine("Enter the messages you want to send (Type 'exit' to close program)...");
string message = null;
while (message != "exit")
{
message = Console.ReadLine();
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish("", "abc", null, body);
}
}
}
}
catch (Exception e)
{
string message = e.ToString();
}
}
Reciever.cs
class Recieve
{
static void Main(string[] args)
{
ConnectionFactory factory = new ConnectionFactory()
{
HostName = "localhost"
};
using (IConnection connection = factory.CreateConnection())
{
using (IModel channel = connection.CreateModel())
{
channel.QueueDeclare("abc", false, false, false, null);
QueueingBasicConsumer consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume("abc", true, consumer);
Console.WriteLine(" [*] Waiting for messages." +
"To exit press CTRL+C");
while (true)
{
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine("[Recieved]: {0}", message);
}
}
}
}
}
Is the idea to get these communicating across 2 computers to change the ConnectionFactory's hostname to the IP of the other computer or something to that extent? I have installed rabbit correctly on both computers, and this code runs correctly on each computer individually. I just need the communication to happen across the computers.
Any help would be greatly appreciated. I can't find any examples of this anywhere on the internet.
RabbitMQ is a centralized message queue. You would only install it on your server machine (or a cluster of server machines), and not on each publishing or subscribing client. Clients would then subscribe or publish to queues on the centralized machine.
In your development environment you should decide which of your two machines you want to act as the server. Then pass that hostname and password each client. Both clients should be working towards the same hostname.
var server = "http://127.0.0.1"; // An install of RabbitMQ reachable from both clients
var password = "guest";
var username = "guest";
var connectionFactory = new ConnectionFactory { HostName = server, Password = password , Username = username};
If you want to do message passing without installing something on a server you should take a look at ZeroMQ
You can check out shovel plugin - it is built to take messages messages from queue on one node of rabbit and publishes these messages on other one, while taking care of poor networking (WAN).
Check the official description

Has anyone got Google DirectoryService.Users.List().Execute(); to work in C#/.NET?

I've been trying to list users in my google apps domain for a while now.
No problem in Python, but in C# i get an error message:
An error has occured:
Google.Apis.Requests.RequestError
Bad Request [400]
Errors [
Message[Bad Request] Location[ - ] Reason[badRequest] Domain[global]
]
I'm no C# guru of any sorts but when I looked through the Google.Apis.Admin.directory_v1.cs - file it looked to me as if the UserResource ListRequest is wrong???
It's found on line 7349-7352 in the file. Anyone know's if it's not yet implemented in the API?
Edit:
I start with why I THINK the code in Google.Apis.Admin.directory_v1.cs, lines 7349-7352 is wrong(as I mentioned - I'm not a C#-guru):
The Code:
/// <summary>Retrieve either deleted users or all users in a domain (paginated)</summary>
public virtual ListRequest List() {
return new ListRequest(service);
}
Why I find it odd:
I can' see where to pass the customerid or domain as an argumant to this request, but in the APIs Explorer it's needed (otherwise I get the same error message as above, in my original post).
Edit : I looked a bit further down in the file and I guess that line 8904 and onwards is doing what I looked for earlier. My Bad!
But still I can't get my code to work?!?!?
And my code that won't work:
static void Main(string[] args)
{
// Display the header and initialize the sample.
CommandLine.EnableExceptionHandling();
Console.WriteLine("List users in a google apps domain!");
Console.WriteLine("by Jonas Bergstedt 2013");
// Get the domainname
Console.Write("Domain: ");
string domain = Console.ReadLine();
// Register the authenticator.
var provider = new NativeApplicationClient(GoogleAuthenticationServer.Description)
{
ClientIdentifier = <myClientId>,
ClientSecret = <myClientSecret>",
};
var auth = new OAuth2Authenticator<NativeApplicationClient>(provider, GetAuthorization);
// Create the service.
var service = new DirectoryService(new BaseClientService.Initializer()
{
Authenticator = auth,
ApplicationName = "List Users",
ApiKey = <myApiKey>
});
// Trying to add the domain
service.Users.List().Domain = domain;
Users results = service.Users.List().Execute();
foreach (User list in results.UsersValue)
{
Console.WriteLine("- " + list.Name);
}
}
private static IAuthorizationState GetAuthorization(NativeApplicationClient arg)
{
// Get the auth URL:
IAuthorizationState state = new AuthorizationState(new[] { DirectoryService.Scopes.AdminDirectoryUser.GetStringValue() });
state.Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl);
Uri authUri = arg.RequestUserAuthorization(state);
// Request authorization from the user (by opening a browser window):
Process.Start(authUri.ToString());
Console.WriteLine();
Console.Write("Authorization Code: ");
string authCode = Console.ReadLine();
// Retrieve the access token by using the authorization code:
return arg.ProcessUserAuthorization(authCode, state);
}
}
ListRequest had those properties. It looks like those properties aren't mandatory, so they aren't part of the constructor.
You can do the following:
var listReq = service.Users.List();
listReq.Customer = "CUSTOMER_HERE";
listReq.Domain = "DOMAIN_HERE";
Users results = listReq.Execute();

Categories

Resources