2
Vote

Provide User information from AD to NegotiateAuthenticator and then to applications

description

Combining Waffle and Com4j + Active Directory COM access thru ADO (described: http://weblogs.java.net/blog/2008/01/10/active-directory-authentication-hudson ) works. I was able to authenticate NTLM login AND to get the UPN (User Principal Name: first-name.last-name@yourdomain.com) and other AD info like user first name/last name, telephone number, etc.

The result:
* Waffle (unmodified) to authentify the user at the Tomcat Container level (SSO)
* In the different Java/Tomcat applications, I call a rather simple bean I developed from Com4J Kohsuke example. It is shared (tomcat\sharedlib) and uses Com4J (shared also) to call the ActiveX objects IADs (to get the root LDAP context) and ADsDSOObject to query the AD (I do not need to bind with AD as Waffle already did it).
  This was needed because the JNA (used by Waffle) does not implement accesses to IAD and ADsDSOObject…
My idea of keeping unchanged Waffle and to retrieve the user’s Active Directory information in the different applications (using a bean calling Com4J) is bad: you may have to dig deeper than expected because applications have a lot of legacy code due to the evolution in authentication systems.

Why not improve the security Principal to store all useful Active Directory data directly from Waffle?

file attachments

comments

dblock wrote Dec 16, 2010 at 1:11 PM

Do post an article about how com4j lets you talk to AD, first (CodeProject, your blog, whereever). That post would be helpful and educational for everyone.

I think that forcing an AD connection in GenericWindowsPrincipal or extending GenericWindowsPrincipal itself may produce quite a mess, especially that some of the data in AD is redundant with what you get out of the security tokens themselves. It also may introduce a dependency on com4j and close the door to other implementations (Java has native ways of talking to AD for example and one day we'll run on *nix too over Samba).

I would implement another ActiveDirectoryWindowsPrincipal, compatible with a GenericWindowsPrincipal and do a bit of interface refactoring so that one can plug either type of principal via configuration.

ChristopheDupriez wrote Dec 16, 2010 at 6:09 PM

Thanks for the feedback!

May be to have a different Realm (waffle.apache.WindowsADRealm for instance) which would manage the configuration information and provide a factory to create WindowsADPrincipal (inheriting from GenericWindowsPrincipal)
(WindowsRealm would provide a factory from GenericWindowsPrincipal) ?

Here is attached the code of my UserInfo bean to request AD data from Com4J...

An example of AD data retrieval in the modified source in JSPWiki:
            loginName = context.getWikiSession().getLoginPrincipal().getName();
            UserInfo activeDirectoryUser = UserInfo.getInstance(loginName);
            if (activeDirectoryUser != null && activeDirectoryUser.getUpn() != null) {
                fullname = activeDirectoryUser.getUpn();
                int posAt = fullname.indexOf( '@' );
                if (posAt >= 0) {
                    email = fullname;
                    fullname = fullname.substring(0,posAt);
                }
            }
Have a nice evening!

Christophe

dblock wrote Dec 17, 2010 at 12:32 AM

Maybe :) Send working commitable patches with unit tests and stuff.

dblock wrote Dec 17, 2010 at 12:19 PM

Realms are very Tomcat-specific. It would be nice if this behavior is available to filters.

ChristopheDupriez wrote Dec 17, 2010 at 2:34 PM

Filters could be a good way!
Simple Filter
MyServlet

message


Hello!
Simple Filter
/*

I suppose the security principal can be modified at that point because Filters are often used for authentication.
The word "filter" can make one think they need to pipe (copy) the request or the response. It is not the case. I should have read more about Filters before!
The filter could:
1) add some request attributes containing supplementary AD data about the user (telephone, e-mail, address, etc.)
2) if desired, modify the security principal based on AD information. For instance, replace the login name by the User Principal Name without the @domain.com suffix (if the application is in need for this)
I would suggest filter parameters to be something like this:

principal.name

expression
to change the name of the principal (without changing the principal itself i.e. keeping its other fields untouched)

principal.role

expression
to modify the name of user roles (for instance removing domain...)

request-attribute

expression
to store some data coming from AD in a request attribute
The expression has to be designed to be efficient (but can be parsed once at initialization) for two tasks:
1) concatenates multiple AD fields like givenName and sn (surName)
2) remove unwanted prefix (like domain...) or suffix (like ...@domain.com) Anyone seeing other possible tasks ???

First proposal for expression syntax:
Operands:
  • constant strings are quoted like in Java
  • AD field names are put "as is"
  • a sub-expression between parenthesis
    Operators:
  • the comma to concatenate: operand,operand
  • operand^prefix to remove a prefix
  • operand$suffix to remove a suffix
    If needed:
  • operand/search-operand/replacement-operand to define a search regexp and the corresponding replacement
    The regexp would be precompiled at init time for fast processing
I suppose this plumbing can be defined either in the common server web.xml or in each application web.xml...

ChristopheDupriez wrote Dec 23, 2010 at 9:32 AM

Thinking twice, I would rather propose the classical property syntax for "expression" in filter parameterization:
fixed text ... {insert} ... fixed text ... {another insert} ... fixed text
An "insert" can be:
  • AD field names put "as is" e.g. {givenName}
  • AD field name with replacement of one constant string by another constant string: {userPrincipalName/@domain.com/}
  • AD field name with replacement of one constant string by another constant string: {principal.role/DOMAIN\/ANOTHER_DOMAIN\/}
    the delimiter ("/" in the example above) can be any punctuation character NOT in the searched string and NOT in the replacement string.

dblock wrote Dec 23, 2010 at 12:46 PM

So far there're a lot of ideas here. I recommend you implement a beginning of a useful feature in a patch. It can evolve depending on what people really want.

ChristopheDupriez wrote Dec 29, 2010 at 12:21 PM

OK! I wrote a version 0 (untested but attached) of the ActiveDirectoryDecoratorFilter that I will test next week (I must be at the office to do so). It adds AD data to the request attributes (not parameters!). It does NOT modify the Remote User Principal as this is not made in any way to be modified by a filter.

Filter parameters are:
  • query : the AD field to query for received user name (sAMAccountName by default)
  • query2 : another AD field to query for received user name (userPrincipalName by default)
  • peremption : the number of seconds AD data is cached (default is half an hour)
  • others: each parameter name indicates one desired request attribute. The parameter value gives the corresponding AD field to read.
Changing user principal name or associated roles is not really allowed to a filter. It could eventually ADD data but WindowsPrincipal should have fields for that.

Comments very welcome!

dblock wrote Jan 3, 2011 at 12:25 PM

Christophe: if you want any of it to make into waffle, you need to work on an end-to-end implementation.
  1. The code itself.
  2. Unit tests.
  3. Documentation (.aml changes/edits/additions).
It's some serious work, that's why I always advise to do something simple first ;) All this would need to come as a full patch (not just a couple of java files) that can be applied on waffle trunk. Let me know if you need help with building any of this.

I looked at the code itself, I think it's pretty good. I hate the idea of "query1" and "query2" and the name "peremption" (ca doit etre francais, non?). These things should be named in more explicit ways.

ChristopheDupriez wrote Jan 3, 2011 at 2:11 PM

"peremptio" in Latin roots French and English! But "cache-time-to-live" would be OK for you?
I understand you dislike query2. "fallback-query" would be OK for you?

Unit test: the problem is to have a answering AD for them... I see you have a user name for that purpose in your test AD.
Could you add him some telephone and e-mail so I could add tests on the returned result?
I will also make sample JSPs.

For the doc, I found the Microsoft ALM format specs but not an editor (developing in Java, I do not own a licence of the Visual Studio)
So, I will do my best with an XML editor.

I see that in Waffle Security Filter, the Principal is modified. Any Servlet Container portability constraints with that?

Thanks!

dblock wrote Jan 3, 2011 at 10:04 PM

"peremptio" in Latin roots French and English! But "cache-time-to-live" would be OK for you?
Better :) Although maybe a more java-type name is best. expireAfter or just expire?
I understand you dislike query2. "fallback-query" would be OK for you?
I am not clear on why a fallback query is necessary at all. Can you explain?
Unit test: the problem is to have a answering AD for them... I see you have a user name for that purpose in your test AD.
Could you add him some telephone and e-mail so I could add tests on the returned result?
I will also make sample JSPs.
Unit tests have to work on any machine. Many tests have a hack that avoids running them if you're not in an AD environment. But once you are, write tests that use the current user's username and featch his/her properties. Check that you got something back (maybe not phone, but OU or other mandatory fields will be returned).
For the doc, I found the Microsoft ALM format specs but not an editor (developing in Java, I do not own a licence of the Visual Studio)
Neither do I. Edit XML and build all from root. If you have Sandcastle (see contributing doc), you should be able to build. I don't promise it will be painless :)
I see that in Waffle Security Filter, the Principal is modified. Any Servlet Container portability constraints with that?
I copied that from other implementations :) Who knows. Nobody complained.

dblock wrote Jan 3, 2011 at 10:06 PM

One more thing. Waffle already uses guava, a library that has concurrent hash maps with expiration. It's used in WindowsAuthProviderImpl.java (ConcurrentMap _continueContexts), a structure from the library may be a much nicer one to use for your caching story AND it's threadsafe!

ChristopheDupriez wrote Jan 12, 2011 at 7:54 AM

I worked further and was confronted to "real life" needs:
1) allow an unidentified user to access some services: for instance when JSPWiki wants to produce a PDF of a page, pictures included in that page are retrieved by the server as an unidentified user (not an NTLM enabled browser).
I need to allow 127.0.0.1 to be logged in with a kind of "guest" id (even if the real "guest" account is deactivated in the network)
For this, modification of the NegotiateAuthenticator is necessary.
2) recoding (or addition) of some roles to take into account the fact that applications may need predefined roles that AD cannot provide (it may be not suitable to name a group in the whole network with the name of a role for a "closed source" application (for instance the "manager" role of the Tomcat Manager application: I don't want to modify Tomcat for now!).
Substitution rules may have to be specified differently in each application. I suggest a filter which attaches parameters to GenericWindowsPrincipals (with an override on getRoles) so the roles are modified on the fly when retrieved.
3) idem for names: the kind of user name requested by an application may not be the sAmAccountName: the UPN or sn, givenName may be better. Local domain may be subject to change in the fqn and one may want to remove it for applications
Substitution rules may have to be specified differently in each application. I suggest a filter which attaches an user data access object (with lazy evaluation using LDAP) to GenericWindowsPrincipals (with an override on getName) so the name is modified on the fly when retrieved.
4) additional AD info about the user would then be available by a method like "getADInfo("name of an AD field")" applied on the principal. AD information would be retrieved only if this method is called. It would be also cached as designed before.

So I need to:
  • create a class to represent an ADInfo object made of roles and names subtitutions parameters and list of fields to retrieve in the AD and a map of already retrieved fields if they have been read.
  • improve the ActiveDirectoryFilter to record its parameters in an ADInfo object and attach this to the GenericWindowsPrincipals (leaving AD information retrieval for later access through the ADInfo object attached to the GenericWindowsPrincipal)
  • add overrides in GenericWindowsPrincipal (getName, getRoles) to allow dynamic substitution for a given application
  • add method getADInfo to GenericWindowsPrincipal
  • modify NegotiateAuthenticator to allow unidentified local accesses (or even remote REST accesses) for precise IPs (those accesses would receive a minimal explicit role to be paremeterized in the Valve)
    I notice Valve parameters are NOT set to the NegotiateAuthenticator bean. What may be the misconfiguration I make?
How do you feel this set of additions to Waffle?

dblock wrote Jan 14, 2011 at 4:01 AM

I feel like your first addition to waffle should be a complete small patch for a small part of this. The rest will grow organically.

OhadR wrote Mar 18, 2013 at 2:41 PM

great idea!
"Do post an article about how com4j lets you talk to AD, first "
Did Christophe post the article?
Was his idea integrated into Waffle? or is it still a stand-alone java file?
I cannot find it in Waffle 1.5 ...

ChristopheDupriez wrote Mar 18, 2013 at 3:28 PM

OhadR, the Waffle project is moved to GITHUB. No, I did not went thru making an article or finalized contribution to Waffle. Yes, I adapted my previous code to the new version of Waffle and 64bits environment. I can double check tomorrow what changed. You can write me directly at "dupriez", domain "destin" in BElgium.