How to apply basic authentication in WCF? - c#

I'm implementing a WcfClientFactory
public class WcfClientFactory : IDisposable
{
internal const string WildcardConfigurationName = "*";
//We track all channels created by this instance so they can be destroyed
private readonly List<WeakReference<IDisposable>> _disposableItems = new List<WeakReference<IDisposable>>();
public T CreateClient<T>(string configurationName = WildcardConfigurationName, string address=null)
{
var factory = new ChannelFactory<T>(configurationName);
if (!string.IsNullOrWhiteSpace(address))
{
factory.Endpoint.Address = new EndpointAddress(address);
}
var channel = factory.CreateChannel();
var clientChannel = (IClientChannel)channel;
clientChannel.Open();
_disposableItems.Add(new WeakReference<IDisposable>(clientChannel,false));
return channel;
}
void IDisposable.Dispose()
{
//No finalizer is implemented as there are no directly held scarce resources.
//Presumably the finalizers of items in disposableItems will handle their own teardown
//if it comes down to it.
foreach (var reference in _disposableItems)
{
IDisposable disposable;
if (reference.TryGetTarget(out disposable))
{
disposable.Dispose();
}
}
}
}
So I can create a WCF clientChannel
var client = _wcfClientFactory.CreateClient<ICrmService>(address);
It works fine if the WCF does not have any authentication. Now, we want to add authentication to this factory. How can I do it? I tried below code
public T CreateClientWithBasicAuthentication<T>(string address)
{
WSHttpBinding myBinding = new WSHttpBinding();
myBinding.Security.Mode = SecurityMode.Transport;
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
var factory = new ChannelFactory<T>(myBinding, new EndpointAddress(address));
var channel = factory.CreateChannel();
var clientChannel = (IClientChannel)channel;
////CrmServiceClient csc = (CrmServiceClient)channel;
////csc.ClientCredentials.UserName.UserName = _UserName;
////csc.ClientCredentials.UserName.Password = _Password;
clientChannel.Open();
_disposableItems.Add(new WeakReference<IDisposable>(clientChannel, false));
return channel;
}
But it generates exception and asks for UserName and Password. How can I set the password and username?
The variable factory has a member of Credential, but it is get only. That's why I think it has to be a way to set credential before call CreateChannel
Thanks

There may be a problem with the description here, your idea is right, here we can set the client authentication credentials.
var factory = new ChannelFactory<T>(myBinding, new EndpointAddress(address));
//for HttpClientCredentialType.Basic
factory.Credentials.UserName.UserName = "administrator";
factory.Credentials.UserName.Password = "abcd1234!";
//for window credentials
//factory.Credentials.Windows.ClientCredential.UserName = "adminsitrator";
//factory.Credentials.Windows.ClientCredential.Password = "abcd1234!";
var channel = factory.CreateChannel();
Besides, please note that the binding type that the channel factory uses should be consistent with the binding type on the server-side.
Feel free to let me know if the problem still exists.

Related

How to pass a class to a method where the methods parameter is a generic

This method below has been written 12 different times in my project using a different interface
ChannelFactory<interface>
and a different "User".
var user = new SomeWebServiceReference.User();
Examples below.
So I'd like to make a single generic like class or a generic method in a class (not sure what I need) that passes in the "WebServiceAWI" interface and any of the other interfaces (ex. WebServiceBW, WebServiceAWA) and a "User" but I'm not sure how to do it.
// file 1
public void TestSomething(string url)
{
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
EndpointAddress endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<WebServiceAWI>(binding, endpoint);
var webservice = channelFactory.CreateChannel();
var user = new User(); // from ServiceReferenceARM
user.UserName = webservice.EncryptValue("someone");
user.Password = webservice.EncryptValue("password");
WebServiceResult result = webservice.AttorneysGet(user);
}
// file 2
public void TestSomething(string url)
{
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
EndpointAddress endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<WebServiceBW>(binding, endpoint);
var webservice = channelFactory.CreateChannel();
var user = new User(); // from ServiceReferenceBW
user.UserName = webservice.EncryptValue("someone");
user.Password = webservice.EncryptValue("password");
WebServiceResult result = webservice.AttorneysGet(user);
}
I think I'm on the right track with the interface, but I'm not sure how to pass in a class that then acts like a generic.
Here is what I have so far.
Calling it like this
TestTOSomething<WebServiceAWI, WebServicesTO.ServiceReferenceARM.User>(new ServiceReferenceARM.User(), url);
and then implementing it like this but I know this is wrong.
public void TestTOSomething<T, K>(K newUser, string url)
{
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
EndpointAddress endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<T>(binding, endpoint);
var webservice = channelFactory.CreateChannel();
var user = newUser;
user.UserName = webservice.EncryptValue("someone");
user.Password = webservice.EncryptValue("password");
WebServiceResult result = webservice.AttorneysGet(user);
}
You could make interfaces for these, and then reference them in your method. There is no need to include the generic type for newUser if it isn't being used, just directly use that in the parameters.
As for the WebServiceAWI, you would need to define that type using the where keyword.
public void TestTOSomething<T>(IUser newUser, string url) where T : IWebServiceAWI

How to use RabbitMQ list of hosts connection parameter

I'm trying to use the IList<string> parameter when creating a connection in the C# rabbitMQ library:
IConnection CreateConnection(IList hostnames)
My code is as follows:
private IConnection CreateConnection()
{
var connectionFactory = new ConnectionFactory
{
UserName = _userName,
Password = _password,
VirtualHost = _vhost,
AutomaticRecoveryEnabled = DEFAULT_AUTO_RECOVER,
RequestedHeartbeat = HEARTBEAT_TIMEOUT_SECONDS,
Port = AmqpTcpEndpoint.UseDefaultPort,
};
// _hosts contains valid IPs "###.###.###.###"
return connectionFactory.CreateConnection(_hosts);
}
But regardless of what I suppose for the hosts parameter it doesn't seem to connect (I get "None of the specified endpoints were reachable")
Even if my list contains only one element.
Now, if I use the single host implementation like this, it works correctly:
private IConnection CreateConnection()
{
var connectionFactory = new ConnectionFactory
{
UserName = _userName,
Password = _password,
VirtualHost = _vhost,
AutomaticRecoveryEnabled = DEFAULT_AUTO_RECOVER,
RequestedHeartbeat = HEARTBEAT_TIMEOUT_SECONDS,
Port = AmqpTcpEndpoint.UseDefaultPort,
HostName = _hosts.First() // or just one string
};
return connectionFactory.CreateConnection();
}
I recognize that RabbitMQ suggests not storing the set of hosts on the client but I'm just trying to get their provided method to work.
I think you might need to set a value for the HostnameSelector property of the connection factory
private IConnection CreateConnection()
{
var connectionFactory = new ConnectionFactory
{
UserName = _userName,
Password = _password,
VirtualHost = _vhost,
AutomaticRecoveryEnabled = DEFAULT_AUTO_RECOVER,
RequestedHeartbeat = HEARTBEAT_TIMEOUT_SECONDS,
Port = AmqpTcpEndpoint.UseDefaultPort,
HostnameSelector = new RandomHostnameSelector()
};
// _hosts contains valid IPs "###.###.###.###"
return connectionFactory.CreateConnection(_hosts);
}
RabbitMQ provides a RandomHostnameSelector
class RandomHostnameSelector : IHostnameSelector
{
string IHostnameSelector.NextFrom(IList<string> options)
{
return options.RandomItem();
}
}
Or you could create your own implementation of IHostnameSelector to have your own host selection strategy.

using IRequiresSessionState, IReadOnlySessionState breaks oauth2 but is required to set cookie

I have a generic handler written in C# - this is called as part of an OAuth2 process to validate users via Google.
Once the correct user is identified I need to set a cookie so the rest of the site can then identify the user.
The trouble is in order to set session variables the handler needs to implement IRequiresSessionState - when I do that - then the OAuth 2 process fails with the following error.
Unexpected OAuth authorization response received with callback and client state that does not match an expected value
So I can implement OAuth or write a session variable but cannot do both. I could get OAuth to call the first page but then the code can be seen in the URL (which I would rather not do - as it gives clues for any nefarious person to break security ). I could get a page to call the handler, return JSON to identify the user and have the page itself set the session variable, then go to the first page - but this page would have no content, as well as requiring two hops - so how can you have an HTML page which is essentially empty, or set the session without IRequiresSessionState breaking the session.
The code for the OAuth handler is shown below.
public void ProcessRequest(HttpContext context)
{
NetworkParameters networkParameters = null;
NetworkParameter networkParameter = null;
WebServerClient consumer = null;
AuthorizationServerDescription server = null;
IAuthorizationState grantedAccess = null;
LoginResult loginResult = null;
String code = String.Empty;
String result = String.Empty;
String consumerSecret = String.Empty;
String consumerKey = String.Empty;
String securityCookieVal = String.Empty;
Uri tokenEndpoint = null;
Uri authorizationEndpoint = null;
Profile profile = null;
Profile profile2 = null;
Profiles profiles = null;
NetworkAuthorizations authorizations = null;
NetworkAuthorization na = null;
try
{
loginResult = new LoginResult();
tokenEndpoint = new Uri("https://accounts.google.com/o/oauth2/token");
authorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=auto");
// retrieve network parameters
networkParameters = new NetworkParameters(Properties.Settings.Default.DatabaseConnection);
authorizations = new NetworkAuthorizations(Properties.Settings.Default.DatabaseConnection);
networkParameter = networkParameters.Select("GOOGLE");
code = context.Request["code"];
consumerKey = networkParameter.ClientId;
consumerSecret = networkParameter.ClientSecret;
// set up request
server = new AuthorizationServerDescription();
server.AuthorizationEndpoint = authorizationEndpoint;
server.TokenEndpoint = tokenEndpoint;
server.ProtocolVersion = ProtocolVersion.V20;
// initialise webserver client
consumer = new WebServerClient(server, consumerKey, consumerSecret);
consumer.ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(consumerSecret);
// retrieve access
grantedAccess = consumer.ProcessUserAuthorization();
profile = GoogleServices.GetProfile(grantedAccess.AccessToken);
profiles = new Profiles(Properties.Settings.Default.DatabaseConnection);
profile2 = profiles.SelectByNetworkId(profile.Network, profile.NetworkId);
if (profile2 == null)
{
na = new NetworkAuthorization()
{
Id = Guid.NewGuid().ToString(),
AccessToken = grantedAccess.AccessToken,
ExpirationDate = (DateTime)grantedAccess.AccessTokenExpirationUtc,
IssueDate = (DateTime)grantedAccess.AccessTokenIssueDateUtc,
RefreshToken = grantedAccess.RefreshToken,
Network = profile.Network,
NetworkId = profile.NetworkId
};
authorizations.Insert(na);
profiles.Insert(profile);
}
loginResult.UserId = profile.NetworkId;
}
catch (System.Exception e)
{
loginResult.Status.Status = "ERROR";
loginResult.Status.Message = e.Message;
}
finally
{
context.Response.ContentType = "application/json";
context.Response.Write(JsonConvert.SerializeObject(loginResult));
}
}
Not really an answer - but I got around this by passing a randomly generated id to the first page of the website, and storing it against the user - cookie was set when the first page was called.
Not really secure sending such information in the query string but will suffice in what is a prototype - eventual plan is to use Node.js where this will not be an issue.

Self-hosted WCF Service with MEF and NetTcpBinding for push and pull mechanism

Below is all the code that I am currently using for a self-hosted WCF service in a Windows Service using NetTcpBinding and MEF. So far this works well for pulling data. However when I try to add a service for pushing data, this code doesn't work. The problem is caused by the assembly that contains the WCF service. For the push mechanism I need the ServiceContract and OperationContract attributes on an interface instead of the class itself.
Unfortnately the service does not accept the interface and requires the attributes to be set on the class. Something else that is not working is the inheritance of the default interface on the interface of the specific interface.
I am currently a bit puzzled about how to solve this. My programming skills are not that advanced. Can someone help me out or point me to an article that can give me some guidance?
While doing some searching about this, I came across some articles about creating custom ServiceHost and ServiceHostFactory. How does this work? Most articles seem to be more complex, then what I need.
I hope that I have provided all information required. If not, I will be glad to try to answer the requests for more information.
BootStrapper used to load the WCF Services:
internal class Bootstrapper
{
[ImportMany(typeof(IWcfSvc))]
internal IEnumerable<IWcfSvc> Services { get; set; }
}
This is the default interface that every WCF service should have:
[InheritedExport]
public interface IWcfSvc
{
string Name { get; }
void Init();
}
The MEF class to load the WCF Services:
private void Load()
{
Bootstrapper bootStrapper = new Bootstrapper();
AggregateCatalog catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(this.PluginPath));
CompositionContainer container = new CompositionContainer(catalog, CompositionOptions.DisableSilentRejection);
try
{
container.ComposeParts(bootStrapper);
}
catch (CompositionException ex)
{
this.log.Error("CompositionException", ex);
}
List<string> svcNames = new List<string>();
foreach (var service in bootStrapper.Services)
{
if (svcNames.Count(c => c == service.Name) == 0)
{
service.Init();
Type serviceType = service.GetType();
Uri address = new Uri(string.Format(URI, this.Host, this.Port, service.Name));
this.wcfLoader.CreateWcfService(serviceType, address);
svcNames.Add(service.Name);
}
else
{
this.log.Warn(string.Format("Tried to load {0} multiple times, which was refused.", service.Name));
}
}
}
}
Class to start the WCF Services:
private NetTcpBinding netTcpBinding
{
get
{
return new NetTcpBinding()
{
MaxBufferSize = int.MaxValue,
MaxReceivedMessageSize = int.MaxValue,
MaxBufferPoolSize = long.MaxValue,
CloseTimeout = new TimeSpan(0, 2, 0),
OpenTimeout = new TimeSpan(0, 2, 0),
ReceiveTimeout = new TimeSpan(0, 2, 0),
SendTimeout = new TimeSpan(0, 2, 0),
Security = new NetTcpSecurity()
{
Mode = SecurityMode.TransportWithMessageCredential,
Transport = new TcpTransportSecurity() { ClientCredentialType = TcpClientCredentialType.Certificate },
Message = new MessageSecurityOverTcp() { ClientCredentialType = MessageCredentialType.UserName }
}
};
}
}
internal void CreateWcfService(Type serviceType, Uri address)
{
Action<string> StoreAddress = delegate(string addr)
{
string addrLine = string.Format(ADDRLINE, addr.EndsWith(URLSEPARATOR + MEX, StringComparison.CurrentCultureIgnoreCase) ? MEXBINDING : BINDING, addr);
this.log.Info(addrLine);
};
try
{
CustomBinding binding = new CustomBinding(this.netTcpBinding);
BindingElementCollection bec = binding.Elements;
foreach (var be in bec)
if (be is TcpTransportBindingElement)
(be as TcpTransportBindingElement).ConnectionPoolSettings.LeaseTimeout = TimeSpan.FromSeconds(this.LeaseTimeOut);
ServiceHost serviceHost = new ServiceHost(serviceType, address);
serviceHost.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByIssuerName, this.CertIssuerName);
ServiceEndpoint endpoint = serviceHost.AddServiceEndpoint(serviceType, binding, string.Empty);
endpoint.Address = new EndpointAddress(address, EndpointIdentity.CreateDnsIdentity(this.Host));
ServiceMetadataBehavior serviceMetadataBehavior = new ServiceMetadataBehavior();
serviceHost.Description.Behaviors.Add(serviceMetadataBehavior);
serviceHost.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexTcpBinding(), MEX);
ServiceDebugBehavior svcDebugBehavior = serviceHost.Description.Behaviors.Find<ServiceDebugBehavior>();
if (svcDebugBehavior == null)
serviceHost.Description.Behaviors.Add(new ServiceDebugBehavior());
svcDebugBehavior.IncludeExceptionDetailInFaults = this.Debug;
serviceHost.Opening += (sender, e) => this.serviceHost_State(sender, e, serviceHost, ActionState.Opening);
serviceHost.Opened += (sender, e) => this.serviceHost_State(sender, e, serviceHost, ActionState.Open);
serviceHost.Closing += (sender, e) => this.serviceHost_State(sender, e, serviceHost, ActionState.Closing);
serviceHost.Closed += (sender, e) => this.serviceHost_State(sender, e, serviceHost, ActionState.Closed);
serviceHost.Open();
serviceHost.Description.Endpoints.ToList().ForEach(f => StoreAddress(f.Address.Uri.AbsoluteUri));
this.serviceHosts.Add(serviceHost);
}
catch (Exception ex)
{
this.log.Error("Could not load WCF service...", ex);
}
}
Example of a WCF Service that uses a pulling mechanism:
[OperationContract]
public List<BillOfMaterials> GetBillOfMaterials(int startProductID, DateTime checkDate)
{
using (DbAccess db = new DbAccess())
{
List<SqlParameter> parms = new List<SqlParameter>();
parms.Add(new SqlParameter()
{
DbType = DbType.Int32,
Direction = ParameterDirection.Input,
ParameterName = "StartProductID",
Value = startProductID
});
parms.Add(new SqlParameter()
{
DbType = DbType.DateTime,
Direction = ParameterDirection.Input,
ParameterName = "CheckDate",
Value = checkDate
});
return db.GetData<BillOfMaterials>("uspGetBillOfMaterials", parms);
}
}
The problem was that I was referring to the Type of the class within the ServiceEndpoint.
This should be the interface of the ServiceContract.
What I originally had:
ServiceEndpoint endpoint = serviceHost.AddServiceEndpoint(serviceType, binding, string.Empty);
Should be:
ServiceEndpoint endpoint = serviceHost.AddServiceEndpoint(serviceInterfaceType, binding, string.Empty);
Where serviceInterfaceType is the Type of the interface containing the ServiceContract.

Can't connect to NetTcpBinding WCF service due to "There was no endpoint listening" error

Could you guys please point what I'm missing here ?
I have two sample projects. Both console applications (.Net 3.5). Here is "server" side:
class Program
{
static void Main()
{
var baseAddresses = new Uri("net.tcp://localhost:9000/WcfServiceLibrary/svc");
var host = new ServiceHost(typeof(UiWcfSession), baseAddresses);
var reliableSession = new ReliableSessionBindingElement { Ordered = true, InactivityTimeout = TimeSpan.MaxValue };
var binding = new CustomBinding(reliableSession, new TcpTransportBindingElement()) { ReceiveTimeout = TimeSpan.MaxValue };
host.AddServiceEndpoint(typeof(IClientFulfillmentPipeService), binding, k.WinSvcEndpointName);
host.Open();
Thread.CurrentThread.Join();
}
}
Not sure if it's important but here is small snippet of IClientFulfillmentPipeService
[ServiceContract(CallbackContract = typeof (IClientFulfillmentPipeServiceCallbacks), SessionMode = SessionMode.Required)]
public interface IClientFulfillmentPipeService
{
[OperationContract(IsOneWay = true)]
void Initialize(int uiProcessId, string key);
}
[ServiceContract]
public interface IClientFulfillmentPipeServiceCallbacks
{
[OperationContract(IsOneWay = true)]
void ShowBalloonTip(int timeout, string tipTitle, string tipText, BalloonTipIcon tipIcon);
}
and finally client
private void OpenConnection()
{
try
{
var ep = new EndpointAddress("net.tcp://localhost:9000/WcfServiceLibrary/svc");
var reliableSession = new ReliableSessionBindingElement {Ordered = true};
Binding binding = new CustomBinding(reliableSession, new TcpTransportBindingElement());
reliableSession.InactivityTimeout = TimeSpan.MaxValue;
var pipeFactory = new DuplexChannelFactory<IClientFulfillmentPipeService>(this, binding, ep);
commChannel = pipeFactory.CreateChannel();
((IChannel) commChannel).Open(OpenConnectionTimeout);
}
catch (Exception ex)
{
Log.ErrorFormat("The communication channel to the windows service could not be established. {0}", ex.Message);
}
}
The client fails with System.ServiceModel.EndpointNotFoundException exception which says: "There was no endpoint listening at net.tcp://localhost:9000/WcfServiceLibrary/svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details."
This is modification of production code which is using named pipes and I want to convert it to Tcp transport.
Thanks!
I accidentally left k.WinSvcEndpointName in the endpoint definition in the server. That was the problem.

Categories

Resources