Authenticating to a Complex Active Directory Infrastructure
I am assuming you already have Active Directory Authentication in place. If not, refer to Active Directory Authenticatin for setup.
You have an AD Forest hosting multiple domains as shown below:
domain1.com (root) | |-- us.domain1.com | |-- ca.us.domain1.com | |-- domain2.com | |-- domain3.com |-- sales.domain3.com
Now, since you want a fully collaborative and integrated intranet, you must make sure that all employees have access to the same content across the entire organization, requiring you to authenticate to the same Plone instance.
Is there a way to have multiple Plone instances talk to each other?????
You accomplish this by using LDAPUserFolder to authenticate to AD and list each Domain Controller in LDAPUserFolder as an authenticating LDAP Server.
It is perfectly legal in Active Directory to have a duplicate usernames but in different domains. This is because Windows authenticates by both username and the domain they belong to.
Plone does not understand the concept of "authenticating domains". Zope differentiates each user, by using their username only. If you list different authenticating DC's in LDAPUserFolder, Zope will authenticate you by going to each LDAP server, and binding. Upon failure, it will go to the next one (thinking you set up redundancy). Once it finds a DC that returns successful, it applies your username and successfully authenticates.
Problem is, depending on who registered with Plone first, your user may get someone else's profile and permissions from a different domain, if their usernames are equal.
Here's what LDAPUserFolder currently does and how Zope handles it:
Chris Jones (Director of Credit Processing for the Parent Company), signs into the intranet.
- Step1: Chris Jones (firstname.lastname@example.org) attempts to sign into Plone. - Step2: Zope binds cjones to domain1's LDAP server. - Step3: Bind is successful, and cjones is authenticated. He then adds various financial documents for an executive board meeting next week in his member folder.
Now, Cheryl Jones (Sales Rep of division3) attempts to sign into the intranet.
- Step1: Cheryl Jones (email@example.com) attempts to sign into Plone. - Step2: Zope binds cjones to domain1's LDAP server. - Step3: Bind fails (invalid password) - Step4: Zope binds cjones to domain3's LDAP server. - Step5: Bind is successful, and cjones is authenticated. - Step6: Zope assigns cjones it's already generated permissions and member folder (since cjones has already registered). - Step7: Cheryl goes to her Member Folder and sees Chris's Financial reports for the company, and realizes she's in Finance's intranet.
A bit of a security leak, it would seem, plus, Cheryl no longer can add content to her member folder.
Many administrators get around this problem, by setting up multiple instances of Zope/Plone, and just have each instance authenticating to a specific domain. While this is a great way to do it, it prevents the users from collaborating with users in the other divisions, since the Plone instance won't authenticate them to their own DC. This may be the way to go, if there's a way to get seperate Plone instances talking together.
There are two steps to creating this resolution. The first, is patching LDAPUserFolder to NOT authenticate users if it finds more than one (Very Easy).
The next step is to change the username of the user in Active Directory (fairly comlex depending on the direction you take).
Step1: In the LDAPUserFolder product, open the LDAPUserFolder.py module. In the _lookupattrbyuser function, you will see this conditional statement, which basically means, if no results were found, that user does not exist:
if res['size'] == 0 or res['exception']: msg = '_lookupuserbyattr: No user "%s=%s" (%s)' % ( name, value, res['exception'] or 'n/a' ) self.verbose > 3 and self._log().log(4, msg) return None, None, None
Under that conditional, build another, that, if it finds more than one user, do not bind, as we have more than one user found:
if res['size'] > 1: # More than one user found. Do not attempt to bind. Fail immed. msg = '_lookupuserbyattr: Found more than one user named "%s" %s' % (value,res['exception'] or 'n/a') self.verbose > 3 and self._log().log(4,msg) return None, None, None
Step2: Okay, so the user fails to sign in. Now what? They still need to get in. Well, there's a couple of ways to get around this. The easiest, is to just change their username.
Many seasoned Windows Administrator's shiver at the thought of this. Because, back in NT4.0 days, if you change the username, you change the user's profile, and have to set everything back up. However, with Active Directory, that's no longer the case. The username is merely an authentication attribute to the user's true account name, which is the SID. If you change the username, the SID still remains the same, ergo, same profile/permissions/preferences.
The last key ingredient in this recipe, is to direct LDAPUserFolder to authenticate to a domain controller's GC (Global Catalog) which is port 3268 (not 389). By redirecting authenticating to the GC, and removing the User and Group DN addresses in LDAPUserFolder's config page, you can now search and bind using the entire forest, rather than the specific domain controller.
The other method is used if the user does not want/cannot use a new username. In this case, you must extend the AD Schema with a new attribute called intranetID (or whatever you want). And when the user successfully binds, they retrieve their intranet username to use with Zope, while still authenticating using their Windows name.
The last option is beyond the scope of this "how-to" and can be written, if enough interest warrants it.
Of course, you should do this in a "test-lab" environment, before making changes to ANYONE's account, as every AD implementation is different.
NOTE: There is a problem with the AD-to-Zope group mappings if you use the groups in Active Directory, unless you use Universal Groups. This is because,
memberOf is not replicated in the Global Catalog, and therefore, won't be found. And don't try modifying the schema to allow it in the GC, as Microsoft won't let you ( I've tried ). Universal Groups, however, should work, as they do not exist on any single DC.
The only way around this, is to modify LDAPUserFolder to find the DC that hosts the account in question, and ask for
memberOf. I haven't found a way around this... yet.