CRM Context Object persists connection even when creating new instance - c#

I have a system in place for a WCF Service, in which I take in some credentials from the client. I then try to authenticate with CRM using these credentials. If the authentication fails, I use a pre-defined service account, with the credentials stored in web.config.
What I have found is, no matter what, the first set of credentials used persists for any further requests, no matter how much I tear down the first object. I even instantiate new objects, wrap each context in a using statement, etc.
I have watered the code down into a simple 'connect, retry' block, and this suffers the same issue. The code is as follows:
try
{
var connection = new CrmConnection();
connection.ServiceUri = new Uri("https://my.crm.dynamics.com/");
connection.ClientCredentials = new ClientCredentials();
connection.ClientCredentials.UserName.UserName = "removed1";
connection.ClientCredentials.UserName.Password = "removed1";
using (var crm = new CrmOrganizationServiceContext(connection))
{
var req = new Microsoft.Crm.Sdk.Messages.WhoAmIRequest();
var resp = (Microsoft.Crm.Sdk.Messages.WhoAmIResponse)crm.Execute(req);
}
}
catch (Exception ex) { }
try
{
var connection = new CrmConnection();
connection.ServiceUri = new Uri("https://my.crm.dynamics.com/");
connection.ClientCredentials = new ClientCredentials();
connection.ClientCredentials.UserName.UserName = "removed2";
connection.ClientCredentials.UserName.Password = "removed2";
using (var crm = new CrmOrganizationServiceContext(connection))
{
var req = new Microsoft.Crm.Sdk.Messages.WhoAmIRequest();
var resp = (Microsoft.Crm.Sdk.Messages.WhoAmIResponse)crm.Execute(req);
}
}
catch (Exception ex) { }
Assume that removed1 is incorrect and removed2 is correct. The second call will fail instantly with a token exception, saying invalid credentials. If removed1 is correct, and removed2 is not, the first will authenticate and get the WhoAmIRequest fine. Then, removed2 should fail, but it does not, as it seems to still hold the connection using the old credentials. The invalid credentials still allows the service to make requests. Not good!
The bizarre thing is, the code for the authentication is in a separate project. I have included this project in a simple console application, and everything works fine. I can only assume this is something to do with the WCF service and the way it holds connections. I've tried manually disposing, calling garbage collection, setting to null, etc. I've also tried using web config connection strings called by name (hard coded 2 test ones), tried manually creating the connection string settings with unique names, using CrmConnection.Parse(), etc.
I have even copy pasted the code i'm using directly into a console application, and it works fine. Due to this, I am convinced it is to do with the behavior of a WCF service, and not the code itself. I set the class to have the behavior of
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
But no luck. If it is of any importance, this code is running in a message inspector class which implements IDispatchMessageInspector.
How can I ensure that I can get a fresh session? Thanks.

You are using the default constructor of the CrmConnection class. When doing that, your connection is cached by name. This name is supposed to be the name of the ConnectionStringSettings, but using this constructor that property is never being set and keeps its default value, thus always returning the first connection object created.
Just use another overload of the constructors, e.g. that using a connection string or accepting the service url, credentials etc.
The CrmConnection class was designed to offer an easy way to create connectionstrings in config files, similar to database connection strings. It had its issues and has been removed from the Dynamics CRM 2016 SDK.

Related

Generated WCF SOAP client uses current user for windows authentication instead of given credentials

I'm kind of new to the whole WCF and SOAP topic so please be kind.
I'm using a generated SOAP Client with .net6. In another project we successfully worked with the same Web Service using the old .net Framework 2.0 Web References and the same credentials.
Strange enough everything seemed to work fine at first. Until I realized, that it does not use the given credentials to authenticate. Instead it authenticates with my own domain user.
I also tried to get it to work with explicitly setting the binding with a BasicHttpBinding but I only could get the same broken logic to work or I got various authentication/protocol/security errors.
So it seems the authentication is basically working. It just doesn't use the provided credentials. So my question is: How can I configure it to work with the provided identity?
I also found out that it might have anything to do with a cached Windows token. But how can I get rid of it. How to prevent caching in the first place?
EDIT:
Specified the variable types explicitly.
string url = "http://someServer/AdministrationService.asmx";
AdministrationServiceSoapClient client = new AdministrationServiceSoapClient(
AdministrationServiceSoapClient.EndpointConfiguration.AdministrationServiceSoap,
url);
WindowsClientCredential credential = client.ClientCredentials.Windows;
credential.ClientCredential.UserName = "username";
credential.ClientCredential.Password = "password";
credential.ClientCredential.Domain = "DOMAIN";
GetServerInfoRequest getServerInfoRequest = new GetServerInfoRequest
{
// some stuff set here
};
GetServerInfoRequest getServerInfoReply = await client.GetServerInfoAsync(getServerInfoRequest);
As far as I know, BasicHttpBinding has security disabled by default, but can be added setting the BasicHttpSecurityMode to a value other than None in the constructor. It can be configured according to the instructions in BasicHttpBinding and BasicHttpBinding Constructors.
By default, setting up client credentials involves two steps: determining the type of client credential required by the service and specifying an actual client credential, as described in this document.
After waiting a day it is working. It seems that the cached credentials became invalid somehow.
Strange enough the simple service creation from above is not working anymore. Instead I have to use the following.
var client = new AdministrationServiceSoapClient(
new BasicHttpBinding()
{
Security = new BasicHttpSecurity()
{
Mode = BasicHttpSecurityMode.TransportCredentialOnly,
Message = new BasicHttpMessageSecurity()
{
ClientCredentialType = BasicHttpMessageCredentialType.UserName,
},
Transport = new HttpTransportSecurity()
{
ClientCredentialType = HttpClientCredentialType.Windows,
ProxyCredentialType = HttpProxyCredentialType.Windows,
}
},
},
new EndpointAddress(url));

LDAP search fails on server, not in Visual Studio

I'm creating a service to search for users in LDAP. This should be fairly straightforward and probably done a thousand times, but I cannot seem to break through properly. I thought I had it, but then I deployed this to IIS and it all fell apart.
The following is setup as environment variables:
ldapController
ldapPort
adminUsername 🡒 Definitely a different user than the error reports
adminPassword
baseDn
And read in through my Startup.Configure method.
EDIT I know they are available to IIS, because I returned them in a REST endpoint.
This is my code:
// Connect to LDAP
LdapConnection conn = new LdapConnection();
conn.Connect(ldapController, ldapPort);
conn.Bind(adminUsername, adminPassword);
// Run search
LdapSearchResults lsc = conn.Search(
baseDn,
LdapConnection.SCOPE_SUB,
lFilter,
new string[] { /* lots of attributes to fetch */ },
false
);
// List out entries
var entries = new List<UserDto>();
while (lsc.hasMore() && entries.Count < 10) {
LdapEntry ent = lsc.next(); // <--- THIS FAILS!
// ...
}
return entries;
As I said, when debugging this in visual studio, it all works fine. When deployed to IIS, the error is;
Login failed for user 'DOMAIN\IIS_SERVER$'
Why? The user specified in adminUsername should be the user used to login (through conn.Bind(adminUsername, adminPassword);), right? So why does it explode stating that the IIS user is the one doing the login?
EDIT I'm using Novell.Directory.Ldap.NETStandard
EDIT The 'user' specified in the error above, is actually NOT a user at all. It is the AD registered name of the computer running IIS... If that makes any difference at all.
UPDATE After consulting with colleagues, I set up a new application pool on IIS, and tried to run the application as a specified user instead of the default passthrough. Exactly the same error message regardless of which user I set.
Try going via Network credentials that allows you to specify domain:
var networkCredential = new NetworkCredential(userName, password, domain);
conn.Bind(networkCredential);
If that does not work, specify auth type basic (not sure that the default is) before the call to bind.
conn.AuthType = AuthType.Basic;

LdapConnection Bind() always fails over SSL

I need to query some information with Active Directory that is only accessible when authenticated over SSL. I can make anonymous connections without issue, but I always get an "LDAP server is unavailable error" when trying to use SSL. There's a lot of forum topics about this, and I've reviewed them but have not found a solution. This code is an ASP.NET MVC app being run on IIS Express.
LdapConnection conn = new LdapConnection("ldap.xxx.com:636/OU=xxx,DC=xxx,DC=xxx,DC=xxx");
//conn.AutoBind = false;
conn.SessionOptions.ProtocolVersion = 3;
conn.AuthType = AuthType.Basic; //Tried with Negotiate as well
conn.Credential = new NetworkCredential(#"domain\user", "userPW", "domain");
conn.SessionOptions.SecureSocketLayer = true;
conn.SessionOptions.VerifyServerCertificate = new VerifyServerCertificateCallback((dev, cer) => true);
//conn.Timeout = new TimeSpan(1, 0, 0);
conn.Bind();
I use that container string with a PrincipalContext to validate the credentials for this account, that validation is successful (that takes place before this code). The container places me into the correct node and the account credentials are a match. Which is why this error puzzles me. Protocol version is correct as well. I've set the verifycert callback to return true regardless of the certificate.
The error is being thrown on the first line, if I remove the container it creates the connection, but then hangs indefinitely when I call .Bind(). If I specify the domain after the connection is made, the "unavailable error" is thrown on .Bind(). I don't understand why it fails with the container because it works when passed in with a PrincipalContext.
Thank you for any help.

Acumatica Web Services API Login

I am attempting to perform some basic integration using Acumatica's web services. Unfortunatly, I'm having problems logging in. According to their documentation, this process should look something like:
apitest.Screen context = new apitest.Screen();
context.CookieContainer = new System.Net.CookieContainer();
context.AllowAutoRedirect = true;
context.EnableDecompression = true;
context.Timeout = 1000000;
context.Url = "http://localhost/WebAPIVirtual/Soap/APITEST.asmx";
LoginResult result = context.Login("admin", "E618");
Simple enough. However, after creating and importing a WSDL file from Acumatica into Visual Studio, I found I don't have a Screen object. I do, however have a ScreenSoapClient object, which has a similar Login() method.
ScreenSoapClient context = new Acumatica.ScreenSoapClient("ScreenSoap");
LoginResult result = context.Login("username", "password");
That part works. In fact, the LoginResult give me a session ID. However, if I try to make any calls to the service, such as:
CR401000Content cr401000 = context.CR401000GetSchema();
I get an error: System.Web.Services.Protocols.SoapException: Server was unable to process request. ---> PX.Data.PXNotLoggedInException: Error #185: You are not currently logged in.
While the version of Acumatica we're using does appear to be slightly newer, I'm unsure why the Screen() object isn't available. Consequently, if I try a bad username/password, Login() does fail (as it should). From what I can the tell, the ScreenSoapClient class is using service model details from web.config, so it's getting the endpoint address and other details there.
Is there something I'm missing or doing wrong?
As i see, you use WCF to create your service reference.
So you should enable cookies in service binding:
var binding = new BasicHttpBinding()
{
AllowCookies = true
};
var address = new EndpointAddress("http://localhost/WebAPIVirtual/Soap/APITEST.asmx");
var c = new ServiceReference1.ScreenSoapClient(binding, address);
Or, you can use old asmx web service reference (http://msdn.microsoft.com/en-us/library/bb628649.aspx).
Then everything will be same as in Acumatica`s documentation.
As noted in a comment above, I was able to make contact with a representative from Acumatica. He had me remove then recreate the service references in our project and try again. That apparently did the trick and the "Error #185: You are not currently logged in" error went away.

Impersonation, Active Directory, and "user does not have authority to xxxx" issues

I have 2 ASP.NET MVC 3 applications. I am using impersonation via the web.config to allow me to query Active Directory to get details on the user. The application uses Windows authentication and does not allow anonymous users. One application is the primary application where the user performs their tasks. The other allows the user to set up other user's to look like them in application one.
The test user's are getting the following error:
SQL1092N "<DOMAIN ID>" does not have the authority to perform the requested command.
This happens after I send a web request from my primary application to the secondary one. To get that working I had to make the request impersonate the actual user and not the identity the application uses for impersonation. This is actually an SO question I posted and had answered. That's here: How do I call an MVC Action via a WebRequest and validate the request through Active Directory?
At the end of that code, I call:
impersonationContext.Undo();
It is after this web request takes place, that the primary application tries accessing the database and now it seems that the above call has undone the impersonation of the application, so the user's attempt to do anything that opens a database connection fails. At least, that's my working theory after a day of head bashing.
My question is, how can I get the impersonation of the application to revert back to the user in the web.config? Or, when making my web request, is there a way to ensure the impersonation context only applies to that request?
The whole point of all of this is that the second application has its own sql server database. The primary application uses DB2. I would like to write the database access code once, but use it in both applications. Currently that's what I've done, but my method of relying on the web request to get the data may not be the best approach.
I'm open to any thoughts, comments, suggestions, and/or criticism. How should I go about handling this?
Okay...my theory that the IPrincipal context was changed when making the web request proved accurate, which made this fix extremely easy. Best part is, I can keep using the api I built to make this request without duplicating the Sql Server Entity Framework parts.
I have the following call to my api library:
proxyRequestResultDetails = ProxyApiWrapper.GetProxies(
adUserInfo.AssociateId,
context.User);
This code is being called by an authorization filter attribute. The method prototype looks like
public void OnAuthorization(AuthorizationContext filterContext)
Internally, the call makes the GetProxies method following call:
public static StreamReader GetWebRequestStream(
string url,
string contentType,
bool useDefaultCredentials,
IPrincipal user)
{
var impersonationContext = ((WindowsIdentity)user.Identity).Impersonate();
var request = WebRequest.Create(url);
try
{
request.ContentType = contentType;
//request.ImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
//request.UseDefaultCredentials = useDefaultCredentials;
//IWebProxy p = new WebProxy();
//request.Proxy = p.
request.AuthenticationLevel = System.Net.Security.AuthenticationLevel.MutualAuthRequested;
request.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
var response = (HttpWebResponse)request.GetResponse();
return new StreamReader(response.GetResponseStream());
}
catch (Exception e)
{
impersonationContext.Undo();
throw e;
}
finally
{
impersonationContext.Undo();
}
}
When the calling method returns, the identity of user is no longer that of the one set for the application to
impersonate. The fix is pretty simple:
//Track current identity before proxy call
IPrincipal user = context.User;
proxyRequestResultDetails = ProxyApiWrapper.GetProxies(
adUserInfo.AssociateId,
context.User);
//Undo any impersonating done in the GetProxies call
context.User = user;
2 lines of code resolved 12 hours of head ache. It could have been worse. Anyhow. Thanks for being a sounding board. I tried
having this conversion with the duck, but the duck got confused.

Categories