How to manipulate LDAP using JNDI on websphere? - ldap

I am facing a problem with an LDAP operation. I want to dynamically add a member to an LDAP group when selected by the user from GUI / browser. I paste the code below which works perfectly well when I run it in a Test class (using com.sun.jndi.ldap.LdapCtxFactory). But, when I package it in my build, deploy on websphere app server 7.0 (using com.ibm.websphere.naming.WsnInitialContextFactory), and invoke this method according to user's selection, then I get the error below. I wonder what's wrong I am doing. Doesn't WAS provide implementation of ldap connection factory? I also tried deploying on WAS with the sun's ldap which otherwise works on the Test class, but I am getting the same exception as below. I'd appreciate if anybody can give a clue.
Problem adding member: javax.naming.OperationNotSupportedException: [LDAP: error code 53 - 00000561: SvcErr: DSID-031A120C, problem 5003 (WILL_NOT_PERFORM), data 0
My Code:
public class LDAPManager
{
String GROUPS_OU = "cn=users,dc=mit,dc=hq,dc=com";
public Boolean addMember(String user, String group)
{
Hashtable env = new Hashtable();
String adminName = "CN=Administrator,CN=Users,DC=mit,DC=hq,DC=com";
String adminPassword = "asdfasdf21Q";
String ldapURL = "ldap://mybox451Dev.mit.hq.com:389";
String userName = "CN="+user+",CN=Users,DC=mit,DC=hq,DC=com";
String groupName = "CN="+group+",CN=Users,DC=mit,DC=hq,DC=com";
//env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.ibm.websphere.naming.WsnInitialContextFactory");
//set security credentials, note using simple cleartext authentication
env.put(Context.SECURITY_AUTHENTICATION,"simple");
env.put(Context.SECURITY_PRINCIPAL,adminName);
env.put(Context.SECURITY_CREDENTIALS,adminPassword);
//connect to my domain controller
env.put(Context.PROVIDER_URL, "ldap://mybox451Dev.mit.hq.com:389");
try {
// Create the initial directory context
InitialDirContext ctx = new InitialDirContext(env);
//Create a LDAP add attribute for the member attribute
ModificationItem mods[] = new ModificationItem[1];
mods[0]= new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("member", userName));
//update the group
ctx.modifyAttributes(groupName,mods);
ctx.close();
//System.out.println("Added " + userName + " to " + groupName);
}
catch (NamingException e) {
System.err.println("Problem adding member: " + e);
}
return true;
}
}
I got it solved. Posting solution here, hope this helps someone.
Use the standard JNDI context of sun, not websphere.
Additional properties I was missing in the hashtable, once I added them, it worked like a charm.
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
//env.put(Context.INITIAL_CONTEXT_FACTORY,"com.ibm.websphere.naming.WsnInitialContextFactory");
//set security credentials, note using simple cleartext authentication
env.put(Context.SECURITY_AUTHENTICATION,"simple");
env.put(Context.SECURITY_PRINCIPAL,adminName);
env.put(Context.SECURITY_CREDENTIALS,adminPassword);
env.put(Context.URL_PKG_PREFIXES, "com.sun.jndi.url");
env.put(Context.REFERRAL, "ignore");

Well, it's been more than a year since this question has been asked; so, I don't know answering will add any value. But, here it is. See WAS Javadocs for details on how what that factory class actually does and how it works. You may need to adjust your jndiprovider.properties file for WAS.

Related

Improving scale out performance for multiple web instances using SignalR Redis Backplane

I have SignalR integrated in our application, and it has been working just fine.
Couple of days ago, due to some requirements, we had to support scale out of our application – and hence we opted for SignalR scale out using Redis.
However, since integration, the SignalR itself has stopped working, and the error we get is : NO TRANSPORT could be initialized successfully. try specifying a different transport or none at all for auto initialization.
Approaches applied :
- Tried with different versions of SignalR, as suggested online - Did not help
- Increased connection timeout – Did not help
Need some help in resolving this. Suggestion on using any other approach is also welcome.
[Update1] Adding code snippets
public class Startup
{
public void Configuration(IAppBuilder app)
{
// Any connection or hub wire up and configuration should go here
GlobalHost.DependencyResolver.UseRedis("server", port, "password", "AppName");
app.MapSignalR();
}
}
For more reference, I followed this link :https://learn.microsoft.com/en-us/aspnet/signalr/overview/performance/scaleout-with-redis
[Update2]
public void Configuration(IAppBuilder app)
{
GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);
GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(30);
GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(10);
GlobalHost.Configuration.TransportConnectTimeout = TimeSpan.FromSeconds(45);
ConfigureAuth(app);
ConfigureSignalR(app);
// SignalR backplane code changes
string server = RoleEnvironment.IsAvailable ?
RoleEnvironment.GetConfigurationSettingValue(Constant.ConfigKeys.RedisCacheEndpoint) :
ConfigurationManager.AppSettings[Constant.ConfigKeys.RedisCacheEndpoint];
string port = RoleEnvironment.IsAvailable ?
RoleEnvironment.GetConfigurationSettingValue(Constant.ConfigKeys.RedisCachePort) :
ConfigurationManager.AppSettings[Constant.ConfigKeys.RedisCachePort];
string password = RoleEnvironment.IsAvailable ?
RoleEnvironment.GetConfigurationSettingValue(Constant.ConfigKeys.RedisCachePassword) :
ConfigurationManager.AppSettings[Constant.ConfigKeys.RedisCachePassword];
const string SIGNALR_REDIS_APPNAME = "Phoenix 2.0 Admin Tool";
string connectionString = server + ":" + Int32.Parse(port) + ";password=" + password + ",ssl=True,abortConnect=False";
RedisScaleoutConfiguration cfg = new RedisScaleoutConfiguration(connectionString, SIGNALR_REDIS_APPNAME);
GlobalHost.DependencyResolver.UseRedis(cfg);
app.MapSignalR();
}
We have an Azure AppService and are able to use SignalR w/ the Redis backplane. I did observe that things did not work properly depending on the connection string content. We used the RedisScaleoutConfiguration overload of the GlobalHost.DependencyResolver.UseRedis API instead of using the overload that you show.
Here is a block of code based on our working startup (values changed to protect the vulnerable):
const string SIGNALR_REDIS_APPNAME = "OurAppName";
string connectionString = "thename.redis.cache.windows.net:6380;password=somelongsecret,ssl=True,abortConnect=False";
RedisScaleoutConfiguration cfg = new RedisScaleoutConfiguration(connectionString, SIGNALR_REDIS_APPNAME);
GlobalHost.DependencyResolver.UseRedis(cfg);
Obviously you can get an actual connection string from web.config with more code. We also had trouble when specifying a non-default DB name so are using the default here.
Hope this helps.

ASP.NET Active Directory Search

I'm trying to create an intranet Website on ASP.NET MVC 4 using Windows Login. I have successfully done the windows login. The only thing I am stuck up with is searching the active directory with partial username. I tried searching the web and stackoverflow website but still couldn't find the answer.
DirectoryEntry directory = new DirectoryEntry("LDAP://DC=NUAXIS");
string filter = "(&(cn=jinal*))";
string[] strCats = { "cn" };
List<string> items = new List<string>();
DirectorySearcher dirComp = new DirectorySearcher(directory, filter, strCats, SearchScope.Subtree);
SearchResultCollection results = dirComp.FindAll();
You can use a PrincipalSearcher and a "query-by-example" principal to do your searching:
// create your domain context
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// define a "query-by-example" principal - here, we search for a UserPrincipal
// and with the first name (GivenName) of "Jinal*"
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.GivenName = "Jinal*";
// create your principal searcher passing in the QBE principal
using (PrincipalSearcher srch = new PrincipalSearcher(qbeUser))
{
// find all matches
foreach(var found in srch.FindAll())
{
// do whatever here - "found" is of type "Principal" -
// it could be user, group, computer.....
}
}
}
If you haven't already - absolutely read the MSDN article Managing Directory Security Principals in the .NET Framework 3.5 which shows nicely how to make the best use of the new features in System.DirectoryServices.AccountManagement. Or see the MSDN documentation on the System.DirectoryServices.AccountManagement namespace.
Of course, depending on your need, you might want to specify other properties on that "query-by-example" user principal you create:
DisplayName (typically: first name + space + last name)
SAM Account Name - your Windows/AD account name
User Principal Name - your "username#yourcompany.com" style name
You can specify any of the properties on the UserPrincipal and use those as "query-by-example" for your PrincipalSearcher.
Your current code is on the right track.
I think you had your wildcard backwards.
Consider this:
search.Filter = string.Format("(&(sn={0}*)(givenName={1}*)(objectSid=*))", lastName, firstName);

Set callback for System.DirectoryServices.DirectoryEntry to handle self-signed SSL certificate?

I have an application replicating data from a directory service using typical System.DirectoryServices.DirectoryEntry code. I now have a requirement to replicate from Novell eDirectory using SSL with a self-signed certificate. I suspect that the existing code would work with a valid certificate that could be verified, or perhaps if the self-signed cert is added to the local machine keystore. In order to make it work for sure with a self-signed cert however, the only solution I can find is to use the System.DirectoryServices.Protocols namespace and the LdapConnection class, whereby I can wire up a VerifyServerCertificate callback. I can't find any way of applying the same concept to a DirectoryEntry instance, or of connecting with an LdapConnection instance and somehow "converting" that to a DirectoryEntry instance. Maybe it isn't possible, I'd just like to confirm that really. Any other thoughts welcome.
The only pertinent link I've found is at: http://www.codeproject.com/Articles/19097/eDirectory-Authentication-using-LdapConnection-and
This is a phenomenal question.
I've been battling this same issue for a few days now, and I've finally got some definitive proof on why the DirectoryEntry object will not work in this scenario.
This particular Ldap server (running on LDAPS 636) also issues it's own self signed certificate. Using LdapConnection (and monitoring the traffic via Wireshark), I noticed a handshake taking place that does not occur when using DirectoryEntry :
The first sequence is the from the secured ldap server, the second sequence is from my machine. The code that prompts the second sequence is :
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
There are others way to "fake out" the callback, but this what I've been using.
Unfortunately, DirectoryEntry does not have an option or method to verify a self signed cert, thus the acceptance of the certificate never happens (second sequence), and the connection fails to initialize.
The only feasible way to accomplish this is by using LdapConnection, in conjunction with a SearchRequest and SearchResponse. This is what I've got so far :
LdapConnection ldapConnection = new LdapConnection("xxx.xxx.xxx:636");
var networkCredential = new NetworkCredential("Hey", "There", "Guy");
ldapConnection.SessionOptions.SecureSocketLayer = true;
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
ldapConnection.AuthType = AuthType.Negotiate;
ldapConnection.Bind(networkCredential);
SearchRequest request = new SearchRequest("DC=xxx,DC=xxx,DC=xxx", "(sAMAccountName=3074861)", SearchScope.Subtree);
SearchResponse response = (SearchResponse)ldapConnection.SendRequest(request);
if(response.Entries.Count == 1)
{SearchResultEntry entry = response.Entries[0];
string DN = entry.DistinguishedName;}
From there you can gather AD Properties from the SearchResponse, and process accordingly. This is a total bummer though, because the SearchRequest seems to be much slower then using the DirectoryEntry.
Hope this helps!
I promise, this will be my last post on this particular question. :)
After another week of research and development, I have a nice solution to this, and it has worked exceedingly well for me thus far.
The approach is somewhat different then my first answer, but in general, it's the same concept; using the LdapConnection to force validation of the certificate.
//I set my Domain, Filter, and Root-AutoDiscovery variables from the config file
string Domain = config.LdapAuth.LdapDomain;
string Filter = config.LdapAuth.LdapFilter;
bool AutoRootDiscovery = Convert.ToBoolean(config.LdapAuth.LdapAutoRootDiscovery);
//I start off by defining a string array for the attributes I want
//to retrieve for the user, this is also defined in a config file.
string[] AttributeList = config.LdapAuth.LdapPropertyList.Split('|');
//Delcare your Network Credential with Username, Password, and the Domain
var credentials = new NetworkCredential(Username, Password, Domain);
//Here I create my directory identifier and connection, since I'm working
//with a host address, I set the 3rd parameter (IsFQDNS) to false
var ldapidentifier = new LdapDirectoryIdentifier(ServerName, Port, false, false);
var ldapconn = new LdapConnection(ldapidentifier, credentials);
//This is still very important if the server has a self signed cert, a certificate
//that has an invalid cert path, or hasn't been issued by a root certificate authority.
ldapconn.SessionOptions.VerifyServerCertificate += delegate { return true; };
//I use a boolean to toggle weather or not I want to automatically find and query the absolute root.
//If not, I'll just use the Domain value we already have from the config.
if (AutoRootDiscovery)
{
var getRootRequest = new SearchRequest(string.Empty, "objectClass=*", SearchScope.Base, "rootDomainNamingContext");
var rootResponse = (SearchResponse)ldapconn.SendRequest(getRootRequest);
Domain = rootResponse.Entries[0].Attributes["rootDomainNamingContext"][0].ToString();
}
//This is the filter I've been using : (&(objectCategory=person)(objectClass=user)(&(sAMAccountName={{UserName}})))
string ldapFilter = Filter.Replace("{{UserName}}", UserName);
//Now we can start building our search request
var getUserRequest = new SearchRequest(Domain, ldapFilter, SearchScope.Subtree, AttributeList);
//I only want one entry, so I set the size limit to one
getUserRequest.SizeLimit = 1;
//This is absolutely crucial in getting the request speed we need (milliseconds), as
//setting the DomainScope will suppress any refferal creation from happening during the search
SearchOptionsControl SearchControl = new SearchOptionsControl(SearchOption.DomainScope);
getUserRequest.Controls.Add(SearchControl);
//This happens incredibly fast, even with massive Active Directory structures
var userResponse = (SearchResponse)ldapconn.SendRequest(getUserRequest);
//Now, I have an object that operates very similarly to DirectoryEntry, mission accomplished
SearchResultEntry ResultEntry = userResponse.Entries[0];
The other thing I wanted to note here is that SearchResultEntry will return user "attributes" instead of "properties".
Attributes are returned as byte arrays, so you have to encode those in order to get the string representation. Thankfully, System.Text.Encoding contains a native ASCIIEncoding class that can handle this very easily.
string PropValue = ASCIIEncoding.ASCII.GetString(PropertyValueByteArray);
And that's about it! Very happy to finally have this figured out.
Cheers!
I have used below code to connect with ldaps using DirectoryEntry.
What i understood in my scenerio is directoryEntry does not work when ldaps is specified in server path or authentication type is mentioned as "AuthenticationTypes.SecureSocketsLayer" but if only ldaps port is mentioned at the end of server name it work. After having a look at wireshark log i can see handshake taking place as mentioned in above post.
Handshake:
Code:
public static SearchResultCollection GetADUsers()
{
try
{
List<Users> lstADUsers = new List<Users>();
DirectoryEntry searchRoot = new DirectoryEntry("LDAP://adserver.local:636", "username", "password");
DirectorySearcher search = new DirectorySearcher(searchRoot);
search.PropertiesToLoad.Add("samaccountname");
SearchResult result;
SearchResultCollection resultCol = search.FindAll();
Console.WriteLine("Record count " + resultCol.Count);
return resultCol;
}
catch (Exception ex)
{
Console.WriteLine("exception" + ex.Message);
return null;
}
}

Changing connection string at runtime for OData/WCF Data Service which uses basic authentication

I have an ODATA services with a single schema. These point to a development database, and is served through a WCF Data Service which is then used by clients running Excel/Powerpivot to fetch their own data for reports and such.
The service is secured at runtime through pretty much the same basic authentication explained here: http://msdn.microsoft.com/en-us/data/gg192997
Now how this needs to work in the live environment is sit on the server and connect to different databases based on the username/password supplied. the Users will be typing in 'username#clientID' and 'password'. 'username#clientID' is then split() and username/password is checked against the SQL database. But the database server URL to check against will be determined by ClientID.
Also, once it is authorized the WCF data service needs to return data from the Database corresponding to the ClientID.
The approach I tried was to modify the connection string in the web.config file, but this doesn't work because it says the file is read-only. I'm not even sure if this would have worked at all. What I need to do is get the EDMX/WCF Data service to return the data from the correct database. Here's what I tried to do:
private static bool TryAuthenticate(string user, string password, out IPrincipal principal)
{
Configuration myWebConfig = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
myWebConfig.AppSettings.Settings["test"].Value = "Hello";
myWebConfig.Save();
string newConnStr = myWebConfig.ConnectionStrings.ConnectionStrings["IntelCorpEntities"].ToString();
newConnStr.ToString().Replace("SERGEIX01", "SERVERX01");
myWebConfig.ConnectionStrings.ConnectionStrings["IntelCorpEntities"].ConnectionString = newConnStr;
myWebConfig.Save();
if (user.ToLower().Equals("admin") && password.Equals("password"))
{
principal = new GenericPrincipal(new GenericIdentity(user), new string[] { "Users" });
return true;
}
else
{
principal = null;
return false;
}
}
In your DataService derived class override the CreateDataSource method and in it figure out the right connect string, create a new instance of the EF object context for the connection string and return it.
The WCF DS Service will not use the default constructor on the EF object context then, it's completely up to you construct the instance with the right connection string.
In your svc.cs file add following :
protected override NorthWindEntity CreateDataSource()
{
System.Data.EntityClient.EntityConnection connection = new System.Data.EntityClient.EntityConnection();
connection.ConnectionString = "";
NorthWindEntity ctx = new NorthWindEntity(connection);
return ctx;
}

Simple login for multi-domain intranet?

I have an intranet server on a Windows domain (server is Windows 2003, IIS6, NTFS permissions). It is on the domain Domain01. I have users from two domains in the same forest that access this intranet: Domain01 and Domain02 (DCs also running Windows 2003). Currently, the users are required to login by entering either:
Domain01\username or username#Domain01
My users are completely and thoroughly confused by having to enter the domain each time they log in.
Is there any way to simply allow them to log in by entering just their username and password WITHOUT the domain? For example, have the server try Domain01 by default, and if the login fails to try Domain02?
NOTE: I would like to do this via IIS or server settings if possible, rather than programmatically (for reference, I am using ASP.NET 2.0).
Yes. Usually what I do is do a global catalog search using the supplied user name as the sAMAccountName. Doing this with a PrincipalSearcher requires getting the underlying DirectorySearcher and replacing it's SearchRoot. Once I find the corresponding user object I extract the domain from the user object's path and use that as the domain for the authentication step. How you do the authentication varies depending on what you need it to do. If you don't need impersonation you can use PrincipalContext.ValidateCredentials to make sure that the username/password match using a PrincipalContext that matches the domain of the user account that you previously found. If you need impersonation check out this reference.
// NOTE: implement IDisposable and dispose of this if not null when done.
private DirectoryEntry userSearchRoot = null;
private UserPrincipal FindUserInGlobalContext( string userName )
{
using (PrincipalSearcher userSearcher = new PrincipalSearcher())
{
using (PrincipalContext context
= new PrincipalContext( ContextType.Domain ))
{
userSearcher.QueryFilter = new UserPrincipal( context );
DirectorySearcher searcher
= (DirectorySearcher)userSearcher.GetUnderlyingSearcher();
// I usually set the GC path from the existing search root
// by doing some string manipulation based on our domain
// Your code would be different.
string GCPath = ...set GC path..
// lazy loading of the search root entry.
if (userSearchRoot == null)
{
userSearchRoot = new DirectoryEntry( GCPath );
}
searcher.SearchRoot = userSearchRoot;
using (PrincipalContext gcContext =
new PrincipalContext( ContextType.Domain,
null,
GCPath.Replace("GC://",""))
{
UserPrincipal userFilter = new UserPrincipal( gcContext );
userFilter.SamAccountName = userName;
userSearcher.QueryFilter = userFilter;
return userSearcher.FindOne() as UserPrincipal;
}
}
}
}