Wednesday, June 3, 2015

OmniFaces 2.1 released!

We're proud to announce that today we've released OmniFaces 2.1. OmniFaces is a utility library for JSF that provides a lot of utilities to make working with JSF much easier.

OmniFaces 2.1 is the second release that will depend on JSF 2.2 and CDI 1.1 from Java EE 7. Since Java EE 7 availability remains somewhat scarce, we maintain a no-frills 1.x branch for JSF 2.0 (without CDI) as well.

The easiest way to use OmniFaces 2.1 is via Maven by adding the following to pom.xml:

<dependency>
    <groupId>org.omnifaces</groupId>
    <artifactId>omnifaces</artifactId>
    <version>2.1</version>
</dependency>

Alternatively the jars files can be downloaded directly.

A complete overview of all that's new can be found on the what's new page, and some more details can be found in BalusC's blogpost about this release.

As usual the release contains an assortment of new features, some changes and a bunch of fixes. One particular fix that took some time to get right is getting a CDI availability check to work correctly with Tomcat + OpenWebBeans (OWB). After a long discussion we finally got this to work, with special thanks to Mark Struberg and Ludovic PĂ©net.

One point worth noting is that since we joined the JSF EG, our time has to be shared between that and working on OmniFaces. In addition some code that's now in OmniFaces might move to JSF core (such as already happened for the IterableDataModel in order to support the Iterable interface in UIData and UIRepeat). For the OmniFaces 2.x line this will have no effect though, but for OmniFaces 3.x (which will focus on JSF 2.3) it may.

We will start planning soon for OmniFaces 2.2. Feature requests are always welcome ;)

Arjan Tijms

Tuesday, May 12, 2015

NEC's WebOTX - a commercial GlassFish derivative

In a previous article we took a look at an obscure Java EE application server that's only known in Korea and virtually unknown everywhere else. Korea is not the only country that has a national application server though. Japan is the other country. In fact, it has not one, but three obscure application servers.

These Japanese servers, the so-called obscure 3, are so unknown outside of Japan that major news events like a Java EE 7 certification simply just does not make it out here.

Those servers are the following:

  1. NEC WebOTX
  2. Hitachi Application Server
  3. Fujitsu Interstage AS

In this article we're going to take a quick look at the first one of this list: NEC WebOTX.

While NEC does have an international English page where a trial can be downloaded it only contains a very old version of WebOTX; 8.4, which implements Java EE 5. This file is called otx84_win32bitE.exe and is about 92MB in size.

As with pretty much all of the Asian application servers, the native language pages contain much more and much newer versions. In this case the Japanese page contains a recent version of WebOTX; 9.2, which implements Java EE 6. This file is called OTXEXP92.exe and is about 111MB in size. A bit of research revealed that a OTXEXP91.exe also once existed, but no other versions were found.

The file is a Windows installer, that presents several dialogs in Japanese. If you can't read Japanese it's a bit difficult to follow. Luckily, there are English instructions for the older WebOTX 8.4 available that still apply to the WebOTX 9.2 installer process as well. Installation takes a while and several scripts seem to start running, and it even wants to reboot the computer (a far cry from download & unzip, start server), but after a while WebOTX was installed in e:\webotx.

Jar and file comparison

One of the first things I often do after installing a new server is browse a little through the folders of the installation. This gives me some general idea about how the server is structured, and quite often will reveal what implementation components a particular server is using.

Surprisingly, the folder structure somewhat resembled that of GlassFish, but with some extra directories. E.g.

GlassFish 3.1.2.2 main dirWebOTX 9.2 main dir

 

Looking at the modules directory in fact did make it clear that WebOTX is in fact strongly based on GlassFish:

GlassFish 3.1.2.2 modules dirWebOTX 9.2 modules dir

 

The jar files are largely identical in the part shown, although WebOTX does have the extra jar here and there. It's a somewhat different story when it comes to the glassfish-* and gf-* jars. None of these are present in WebOTX, although for many similar ones are present but just prefixed by webotx- as shown below:

glassfish- prefixed jarswebotx- prefixed jars

 

When actually looking inside one of the jars with a matching name except for the prefix e.g. glassfish.jar vs webotx.jar, then it becomes clear that at least the file names are largely the same again, except for the package being renamed. See below:

glassfish.jarwebotx.jar

 

Curiously a few jars with similar names have internally renamed package names. This is for instance the case for the well known Jersey (JAX-RS) jar, but for some reason not for Mojarra (JSF). See below:

glassfish jersey-core.jarwebotx jersey-core.jar

 

Besides the differences shown above, name changes occur at a number of other places. For instance well known GlassFish environment variables have been renamed to corresponding WebOTX ones, and pom.xml as well as MANIFEST.FM files in jar files have some renamed elements as well. For instance, the embedded pom.xml for the mojarra jar contains this:

<project>
    <modelVersion>4.0.0</modelVersion>
    <!-- upds start 20121122 org.glassfish to com.nec.webotx.as -->
    <groupId>com.nec.webotx.as</groupId>
    <!-- upds end   20121122 org.glassfish to com.nec.webotx.as -->
    <artifactId>javax.faces</artifactId>
    <version>9.2.1</version>
    <packaging>jar</packaging>
    <name>
        Oracle's implementation of the JSF 2.1 specification.
    </name>
    <description>
        This is the master POM file for Oracle's Implementation of the JSF 2.1 Specification.
    </description>
With the MANIFEST.FM containing this:
Implementation-Title: Mojarra
Implementation-Version: 9.2.1
Tool: Bnd-0.0.249
DSTAMP: 20131217
TODAY: December 17 2013
Bundle-Name: Mojarra JSF Implementation 9.2.1 (20131217-1350) https://
 swf0200036.swf.nec.co.jp/app/svn/WebOTX-SWFactory/dev/mojarra/branche
 s/mojarra2.1.26@96979
TSTAMP: 1350
DocName: Mojarra Implementation Javadoc
Implementation-Vendor: Oracle America, Inc.

 

Trying out the server

Rather peculiar to say the least for a workstation is that WebOTX is automatically started when the computer is rebooted. Unlike most other Java EE servers the default HTTP port after installation is 80. There's no default application installed and requesting http://localhost results in the following screen:

The admin interface is present on port 5858. For some reason the initial login screen asks for very specific browser versions though:

After logging in with username "admin", password "adminadmin", we're presented with a colorful admin console:

As is not rarely the case with admin consoles for Java EE servers there's a lot of ancient J2EE stuff there. Options for generating stubs for EJB CMP beans are happily being shown to the user. In a way this is not so strange. Modern Java EE doesn't mandate a whole lot of things to be configured via a console, thanks to the ongoing standardization and simplification efforts, so what's left is not rarely old J2EE stuff.

I tried to upload a .war file of the OmniFaces showcase, but unfortunately this part of the admin console was still really stuck in ancient J2EE times as it politely told me it only accepted .ear files:

After zipping the .war file into a second zip file and then renaming it to .ear (a rather senseless exercise), the result was accepted and after requesting http://localhost again the OmniFaces showcase home screen was displayed:

As we can see, it's powered by Mojarra 9.2.1. Now we all know that Mojarra moves at an amazing pace, but last time I looked it was still at 2.3 m2. Either NEC travelled some time into the future and got its Mojarra version there, or the renaming in MANIFEST.FM as shown above was a little bit too eagerly done ;)

At any length, all of the functionality in the showcase seemed to work, but as it was tested on GlassFish 3 before this wasn't really surprising.

Conclusion

We took a short look at NEC's WebOTX and discovered it's a GlassFish derivative. This is perhaps a rather interesting thing. Since Oracle stopped commercial support for GlassFish a while ago, many wondered if the code base wouldn't wither at least a little when potentially fewer people would use it in production. However, if a large and well known company such as NEC offers a commercial offering based on GlassFish then this means that next to Payara there remains more interest in the GlassFish code beyond being "merely" an example for other vendors.

While we mainly looked at the similarities with respect to the jar files in the installed product we didn't look at what value NEC exactly added to GlassFish. From a very quick glance it seems that at least some of it is related to management and monitoring, but to be really sure a more in depth study would be needed.

It remains remarkable though that while the company NEC is well known outside Japan for many products, it has its own certified Java EE server that's virtually unheard of outside of Japan.

Arjan Tijms

Monday, May 4, 2015

OmniFaces 2.1-RC1 has been released!

We are proud to announce that OmniFaces 2.1 release candidate 1 has been made available for testing.

OmniFaces 2.1 is the second release that will depend on JSF 2.2 and CDI 1.1 from Java EE 7. Since Java EE 7 availability remains somewhat scarce, we maintain a no-frills 1.x branch for JSF 2.0 (without CDI). For this branch we've simultaneously released a release candidate as well: 1.11-RC1.

A full list of what's new and changed is available here.

OmniFaces 2.1 RC1 can be tested by adding the following dependency to your pom.xml:

<dependency>
    <groupId>org.omnifaces</groupId>
    <artifactId>omnifaces</artifactId>
    <version>2.1-RC1</version>
</dependency>

Alternatively the jars files can be downloaded directly.

For the 1.x branch the coordinates are:

<dependency>
    <groupId>org.omnifaces</groupId>
    <artifactId>omnifaces</artifactId>
    <version>1.11-RC1</version>
</dependency>
This one too can be downloaded directly.

If no major bugs surface we hope to release OmniFaces 2.1 final soon.

Arjan Tijms

Wednesday, April 22, 2015

Testing JASPIC 1.1 on IBM Liberty EE 7 beta

In this article we take a look at the latest April 2015 beta version of IBM's Liberty server, and specifically look at how well it implements the Java EE authentication standard JASPIC.

The initial version of Liberty implemented only a seemingly random assortment of Java EE APIs, but the second version that we looked at last year officially implemented the (Java EE 6) web profile. This year however the third incarnation is well on target to implement the full profile of Java EE 7.

This means IBM's newer and much lighter Liberty (abbreviated WLP), will be a true alternative for the older and incredibly obese WebSphere (abbreviated WAS) where it purely concerns the Java EE standard APIs. From having by far the most heavyweight server on the market (weighing in at well over 2GB), IBM can now offer a server that's as light and small as various offerings from its competition.

For this article we'll be specifically looking at how well JASPIC works on Liberty. Please take into account that the EE 7 version of Liberty is still a beta, so this only concerns an early look. Bugs and missing functionality are basically expected.

We started by downloading Liberty from the beta download page. The download page initially looked a little confusing, but it's constantly improving and by the time that this article was written it was already a lot clearer. Just like the GlassFish download page, IBM now offers a very straightforward Java EE Web profile download and a Java EE full profile one.

For old time WebSphere users who were used to installers that were themselves 200GB in size and only run on specific operating systems, and then happily downloaded 2GB of data that represented the actual server, it beggars belief that Liberty is now just an archive that you unzip. While the last release of Liberty already greatly improved matters by having an executable jar as download, effectively a self-extracting archive, nothing beats the ultimate simplicity of an "install" that solely consists of an archive that you unzip. This represents the pure zen of installing, shaving every non-essential component off it and leaving just the bare essentials. GlassFish has an unzip install, JBoss has it, TomEE and Tomcat has it, even the JDK has it these days, and now finally IBM has one too :)

We downloaded the Java EE 7 archive, wlp-beta-javaee7-2015.4.0.0.zip, weighing in at a very reasonable 100MB, which is about the same size as the latest beta of JBoss (WildFly 9.0 beta2). Like last year there is no required registration or anything. A license has to be accepted (just like e.g. the JDK), but that's it. The experience up to this point is as perfect as can be.

A small disappointment is that the download page lists a weird extra step that supposedly needs to be performed. It says something called a "server" needs to be created after the unzip, but luckily it appeared this is not the case. After unzipping Liberty can be started directly on OS X by pointing Eclipse to the directory where Liberty was extracted, or by typing the command "./server start" from the "./bin" directory where Liberty was extracted. Why this unnecessary step is listed is not clear. Hopefully it's just a remainder of some early alpha version. On Linux (we tried Ubuntu 14.10) there's an extra bug. The file permissions of the unzipped archive are wrong, and a "chmod +x ./bin/server" is needed to get Liberty to start using either Eclipse or the commandline.

(UPDATE: IBM responded right away by removing the redundant step mentioned by the download page)

A bigger disappointment is that the Java EE full profile archive is by default configured to only be a JSP/Servlet container. Java EE 7 has to be "activated" by manually editing a vendor specific XML file called "server.xml" and finding out that in its "featureManager" section one needs to type <feature>javaee-7.0</feature>. For some reason or the other this doesn't include JASPIC and JACC. Even though they really are part of Java EE (7), they have to be activated separately. In the case of JASPIC this means adding the following as well: <feature>jaspic-1.1</feature>. Hopefully these two issues are just packaging errors and will be resolved in the next beta or at least in the final version.

On to trying out JASPIC, we unfortunately learned that by default JASPIC doesn't really work as it should. Liberty inherited a spec compliance issue from WebSphere 8.x where the runtime insists that usernames and groups that an auth module wishes to set as the authenticated identity also exist in an IBM specific server internal identity store that IBM calls "user registry". This is however not the intend of JASPIC, and existing JASPIC modules will not take this somewhat strange requirement into account which means they will therefor not work on WebSphere and now Liberty. We'll be looking at a hack to work around this below.

Another issue is that Liberty still mandates so called group to role mapping, even when such mapping is not needed. Unlike some other servers that also mandate this by default there's currently no option to switch this requirement off, but there's an open issue for this in IBM's tracker. Another problem is that the group to role mapping file can only be supplied by the application when using an EAR archive. With lighter weight applications a war archive is often the initial choice, but when security is needed and you don't want or can't pollute the server itself with (meaningless) application specific data, then the current beta of Liberty forces the EAR archive upon you. Here too however there's already an issue filed to remedy this.

One way to work around the spec compliance issue mentioned above is by implementing a custom user registry that effectively does nothing. IBM has some documentation on how to do this, but unfortunately it's not giving exact instructions but merely outlines the process. The structure is also not entirely logical.

For instance, step 1 says "Implement the custom user registry (FileRegistrysample.java)". But in what kind of project? Where should the dependencies come from? Then step 2 says: "Creating an OSGi bundle with Bundle Activation. [...] Import the FileRegistrysample.java file". Why not create the bundle project right away and then create the mentioned file inside that bundle project? Step 4 says "Register the services", but gives no information on how to do this. Which services are we even talking about, and should they be put in an XML file or so and if so which one and what syntax? Step 3.4 asks to install the feature into Liberty using Eclipse (this works very nicely), but then step 4 and 5 are totally redundant, since they explain another more manually method to install the feature.

Even though it's outdated, IBM's general documentation on how to create a Liberty feature is much clearer. With those two articles side by side and cross checking it with the source code of the example used in the first article, I was able to build a working NOOP user registry. I had to Google for the example's source code though as the link in the article resulted in a 404. A good thing to realize is that the .esa file that's contained in the example .jar is also an archive that once unzipped contains the actual source code. Probably a trivial bit of knowledge for OSGi users, but myself being an OSGi n00b completely overlooked this and spent quite some time looking for the .java files.

The source code of the actual user registry is as follows:

package noopregistrybundle;

import static java.util.Collections.emptyList;

import java.rmi.RemoteException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;

import com.ibm.websphere.security.CertificateMapFailedException;
import com.ibm.websphere.security.CertificateMapNotSupportedException;
import com.ibm.websphere.security.CustomRegistryException;
import com.ibm.websphere.security.EntryNotFoundException;
import com.ibm.websphere.security.NotImplementedException;
import com.ibm.websphere.security.PasswordCheckFailedException;
import com.ibm.websphere.security.Result;
import com.ibm.websphere.security.UserRegistry;
import com.ibm.websphere.security.cred.WSCredential;

public class NoopUserRegistry implements UserRegistry {

    @Override
    public void initialize(Properties props) throws CustomRegistryException, RemoteException {
    }

    @Override
    public String checkPassword(String userSecurityName, String password) throws PasswordCheckFailedException, CustomRegistryException, RemoteException {
        return userSecurityName;
    }

    @Override
    public String mapCertificate(X509Certificate[] certs) throws CertificateMapNotSupportedException, CertificateMapFailedException, CustomRegistryException, RemoteException {
        try {
            for (X509Certificate cert : certs) {
                for (Rdn rdn : new LdapName(cert.getSubjectX500Principal().getName()).getRdns()) {
                    if (rdn.getType().equalsIgnoreCase("CN")) {
                        return rdn.getValue().toString();
                    }
                }
            }
        } catch (InvalidNameException e) {
        }

        throw new CertificateMapFailedException("No valid CN in any certificate");
    }

    @Override
    public String getRealm() throws CustomRegistryException, RemoteException {
        return "customRealm"; // documentation says can be null, but should really be non-null!
    }

    @Override
    public Result getUsers(String pattern, int limit) throws CustomRegistryException, RemoteException {
        return emptyResult();
    }

    @Override
    public String getUserDisplayName(String userSecurityName) throws EntryNotFoundException, CustomRegistryException, RemoteException {
        return userSecurityName;
    }

    @Override
    public String getUniqueUserId(String userSecurityName) throws EntryNotFoundException, CustomRegistryException, RemoteException {
        return userSecurityName;
    }

    @Override
    public String getUserSecurityName(String uniqueUserId) throws EntryNotFoundException, CustomRegistryException, RemoteException {
        return uniqueUserId;
    }

    @Override
    public boolean isValidUser(String userSecurityName) throws CustomRegistryException, RemoteException {
        return true;
    }

    @Override
    public Result getGroups(String pattern, int limit) throws CustomRegistryException, RemoteException {
        return emptyResult();
    }

    @Override
    public String getGroupDisplayName(String groupSecurityName) throws EntryNotFoundException, CustomRegistryException, RemoteException {
        return groupSecurityName;
    }

    @Override
    public String getUniqueGroupId(String groupSecurityName) throws EntryNotFoundException, CustomRegistryException, RemoteException {
        return groupSecurityName;
    }

    @Override
    public List<String> getUniqueGroupIds(String uniqueUserId) throws EntryNotFoundException, CustomRegistryException, RemoteException {
        return new ArrayList<>(); // Apparently needs to be mutable
    }

    @Override
    public String getGroupSecurityName(String uniqueGroupId) throws EntryNotFoundException, CustomRegistryException, RemoteException {
        return uniqueGroupId;
    }

    @Override
    public boolean isValidGroup(String groupSecurityName) throws CustomRegistryException, RemoteException {
        return true;
    }

    @Override
    public List<String> getGroupsForUser(String groupSecurityName) throws EntryNotFoundException, CustomRegistryException, RemoteException {
        return emptyList();
    }

    @Override
    public Result getUsersForGroup(String paramString, int paramInt) throws NotImplementedException, EntryNotFoundException, CustomRegistryException, RemoteException {
        return emptyResult();
    }

    @Override
    public WSCredential createCredential(String userSecurityName) throws NotImplementedException, EntryNotFoundException, CustomRegistryException, RemoteException {
        return null;
    }
    
    private Result emptyResult() {
        Result result = new Result();
        result.setList(emptyList());
        return result;
    }
}

There were two small caveats here. The first is that the documentation for getRealm says it may return null and that "customRealm" will be used as the default then. But when you actually return null authentication will fail with many null pointer exceptions appearing in the log. The second is that getUniqueGroupIds() has to return a mutable collection. If Collections#emptyList is returned it will throw an exception that no element can be inserted. Likely IBM merges the list of groups this method returns with those that are being provided by the JASPIC auth module, and directly uses this collection for that merging.

The Activator class that's mentioned in the article referenced above looks as follows:

package noopregistrybundle;

import static org.osgi.framework.Constants.SERVICE_PID;

import java.util.Dictionary;
import java.util.Hashtable;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;

import com.ibm.websphere.security.UserRegistry;

public class Activator extends NoopUserRegistry implements BundleActivator, ManagedService {

    private static final String CONFIG_PID = "noopUserRegistry";
    
    private ServiceRegistration<ManagedService> managedServiceRegistration;
    private ServiceRegistration<UserRegistry> userRegistryRegistration;

    @SuppressWarnings({ "rawtypes", "unchecked" })
    Hashtable getDefaults() {
        Hashtable defaults = new Hashtable();
        defaults.put(SERVICE_PID, CONFIG_PID);
        return defaults;
    }

    @SuppressWarnings("unchecked")
    public void start(BundleContext context) throws Exception {
        managedServiceRegistration = context.registerService(ManagedService.class, this, getDefaults());
        userRegistryRegistration = context.registerService(UserRegistry.class, this, getDefaults());
    }
    
    @Override
    public void updated(Dictionary<String, ?> properties) throws ConfigurationException {

    }

    public void stop(BundleContext context) throws Exception {
        if (managedServiceRegistration != null) {
            managedServiceRegistration.unregister();
            managedServiceRegistration = null;
        }
        if (userRegistryRegistration != null) {
            userRegistryRegistration.unregister();
            userRegistryRegistration = null;
        }
    }
}

Here we learned what that cryptic "Register the services" instruction from the article meant; it are the two calls to context.registerService here. Surely something that's easy to guess, or isn't it?

Finally a MANIFEST.FM file had to be created. The Eclipse tooling should normally help here, but it our case it worked badly. The "Analyze code and add dependencies to the MANIFEST.MF" command in the manifest editor (under the Dependencies tab) didn't work at all, and "org.osgi.service.cm" couldn't be chosen from the Imported Packages -> Add dialog. Since this import is actually used (and OSGi requires you to list each and every import used by your code) I added this manually. The completed file looks as follows:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: NoopRegistryBundle
Bundle-SymbolicName: NoopRegistryBundle
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: noopregistrybundle.Activator
Import-Package: com.ibm.websphere.security;version="1.1.0",
 javax.naming,
 javax.naming.ldap,
 org.osgi.service.cm,
 org.osgi.framework
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Export-Package: noopregistrybundle

Creating yet another project for the so-called feature, importing this OSGi bundle there and installing the build feature into Liberty was all pretty straightforward when following the above mentioned articles.

The final step consisted of adding the noop user registry to Liberty's server.xml, which looked as follows:

<?xml version="1.0" encoding="UTF-8"?>
<server description="new server">

    <featureManager>
        <feature>javaee-7.0</feature>
        <feature>jaspic-1.1</feature>
        <feature>localConnector-1.0</feature>
        <feature>usr:NoopRegistryFeature</feature>
    </featureManager>

    <httpEndpoint httpPort="9080" httpsPort="9443" id="defaultHttpEndpoint"/>

    <noopUserRegistry/>
</server>

With this in place, JASPIC indeed worked on Liberty, which is absolutely great! To do some more thorough testing of how compatible Liberty exactly is we used the JASPIC tests that I contributed to the Java EE 7 samples project. These tests have been used by various other server vendors already and give a basic impression of what things work and do not work.

The tests had to be adjusted for Liberty because of its requirement to add an EAR wrapper that hosts the mandated group to role mapping.

After running the tests, the following failures were reported:

Test Class Comment
testPublicPageNotRememberLogin org.javaee7.jaspic.basicauthentication.BasicAuthenticationPublicTest
testPublicPageLoggedin org.javaee7.jaspic.basicauthentication.BasicAuthenticationPublicTest
testProtectedAccessIsStateless org.javaee7.jaspic.basicauthentication.BasicAuthenticationStatelessTest
testPublicServletWithLoginCallingEJB org.javaee7.jaspic.ejbpropagation.ProtectedEJBPropagationTest
testProtectedServletWithLoginCallingEJB org.javaee7.jaspic.ejbpropagation.PublicEJBPropagationLogoutTest
testProtectedServletWithLoginCallingEJB org.javaee7.jaspic.ejbpropagation.PublicEJBPropagationTest
testLogout org.javaee7.jaspic.lifecycle.AuthModuleMethodInvocationTest SAM method cleanSubject not called, but should have been
testJoinSessionIsOptional org.javaee7.jaspic.registersession.RegisterSessionTest
testRemembersSession org.javaee7.jaspic.registersession.RegisterSessionTest
testResponseWrapping org.javaee7.jaspic.wrapping.WrappingTest Response wrapped by SAM did not arrive in Servlet
testRequestWrapping org.javaee7.jaspic.wrapping.WrappingTest Request wrapped by SAM did not arrive in Servlet

Specifically the EJB, "logout calls cleanSubject" & register session (both new JASPIC 1.1 features) and request/response wrapper tests failed.

Two of those are new JASPIC 1.1 features and likely IBM just hasn't implemented those yet for the beta. Request/response wrapper failures is a known problem from JASPIC 1.0 times. Although most servers implement it now curiously not a single JASPIC implementation did so back in the Java EE 6 time frame (even though it was a required feature by the spec).

First Java EE 7 production ready server?

At the time of writing, which is 694 days (1 year, ~10 months) after the Java EE 7 spec was finalized, there are 3 certified Java EE servers but none of them is deemed by their vendor as "production ready". With the implementation cycle of Java EE 6 we saw that IBM was the first vendor to release a production ready server after 559 days (1 year, 6 months), with Oracle following suit at 721 days (1 year, 11 months).

Oracle (perhaps unfortunately) doesn't do public beta releases and is a little tight lipped about their up coming Java EE 7 WebLogic 12.2.1 release, but it's not difficult to guess that they are working hard on it (I have it on good authority that they indeed are). Meanwhile IBM has just released a beta that starts to look very complete. Looking at the amount of time it took both vendors last time around it might be a tight race between the two for releasing the first production ready Java EE 7 server. Although JBoss' WildFly 8.x is certified, a production ready and supported release is likely still at least a full year ahead when looking at the current state of the WildFly branch and if history is anything to go by (it took JBoss 923 days (2 years, 6 months) last time).

Conclusion

Despite a few bugs in the packaging of the full and web profile servers, IBM's latest beta shows incredible promise. The continued effort in making its application server yet again simpler to install for developers is nothing but applaudable. IBM clearly meant it when they started the Liberty project a few years ago and told their mission was to optimize the developer experience.

There are a few small bugs and one somewhat larger violation in its JASPIC implementation, but we have to realize it's just a beta. In fact, IBM engineers are already looking at the JASPIC issues.

To summarize the good and not so good points:

Good

  • Runs on all operating systems (no special IBM JDK required)
  • Monthly betas of EE 7 server
  • Liberty to support Java EE 7 full profile
  • Possibly on its way to become the first production ready EE 7 server
  • Public download page without required registration
  • Very good file size for full profile (100MB)
  • Extremely easy "download - unzip - ./server start" experience

Not (yet) so good

  • Download page lists totally unnecessary step asking to "create a server" (update: now fixed by IBM)
  • Wrong file permissions in archive for usage on Linux; executable attribute missing on bin/server
  • Wrong configuration of server.xml; both web and full profile by default configured as JSP/Servlet only
  • "javaee-7.0" feature in server.xml doesn't imply JASPIC and JACC, while both are part of Java EE
  • JASPIC runtime tries to validate usernames/groups in internal identity store (violation of JASPIC spec)
  • Mandatory group to role mapping, even when this is not needed
  • Mandatory usage of EAR archive when group to role mapping has to be provided by the application
  • Not all JASPIC features implemented yet (but remember that we looked at a beta version)

Arjan Tijms

Thursday, April 2, 2015

How Java EE translates web.xml constraints to Permission instances

It's a well known fact that in Java EE security one can specify security constraints in web.xml. It's perhaps a little lesser known fact that in full profile Java EE servers those constraints are translated by the container to instances of the Permission class. The specifications responsible for this are Servlet and JACC. This article shows a simple example of what this translation looks like.

Web.xml constraints

We're putting the following constraints in web.xml:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Forbidden Pattern</web-resource-name>
        <url-pattern>/forbidden/*</url-pattern>
    </web-resource-collection>
    <auth-constraint/>
</security-constraint>

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Protected Pattern</web-resource-name>
        <url-pattern>/protected/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>architect</role-name>
        <role-name>administrator</role-name>
    </auth-constraint>
</security-constraint>

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Protected Exact</web-resource-name>
        <url-pattern>/adminservlet</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>administrator</role-name>
    </auth-constraint>
</security-constraint>

<security-role>
    <role-name>architect</role-name>
</security-role>
<security-role>
    <role-name>administrator</role-name>
</security-role>

Java Permissions

Given the above shown constraints in web.xml the following WebResourcePermission instances will be generated, in 3 collections as shown below. For brevity only WebResourcePermission is shown. The other types are omitted.

Excluded

  • WebResourcePermission "/forbidden/*"

Unchecked

  • WebResourcePermission "/:/adminservlet:/protected/*:/forbidden/*"

Per Role

  • architect
    • WebResourcePermission "/protected/*"
  • administrator
    • WebResourcePermission "/protected/*"
    • WebResourcePermission "/adminservlet"

Below is very short explanation for the different permission types normally used for the translation. The interested reader is suggested to study the Javadoc of each type for more detailed information.


Java EE will generate 3 types of Permission instances when translating constraints expressed in web.xml; WebRoleRefPermission, WebUserDataPermission and WebResourcePermission.

WebRoleRefPermission

A web role ref permission is about mapping Servlet local roles to application roles. Especially with MVC frameworks like JSF and the upcoming JAX-RS based MVC 1.0 the use for this is perhaps questionable, as there's only one Servlet in that case that serves many different views.

WebUserDataPermission

A web user data permission is about the transport level guarantees for accessing resources (practically this almost always means HTTP vs HTTPS). This can be specified using the <user-data-constraint> element in web.xml, which we have omitted here.

WebResourcePermission

The web resource permission is about the actual access to a resource. This can be specified using the <web-resource-collection> element in web.xml, which we have used in the example above.

So let's take a look at what's going on here.

Our first web.xml constraint shown above defined so-called "excluded access", which means that nobody can access the resources defined by that pattern. In XML this is accomplished by simply omitting the auth-constraint element. This was translated to Java code by means of putting a WebResourcePermission with the pattern "/forbidden/*" in the "Excluded" collection. Although there are some differences, this is a reasonably direct translation from the XML form.

The permission shown above for the "Unchecked" collection concerns the so-called "unchecked access", which means that everyone can access those resources. This one wasn't explicitly defined in XML, although XML does have syntax for explicitly defining unchecked access. The permission shown here concerns the Servlet default mapping (a fallback for everything that doesn't match any other declared Servlet pattern).

The pattern used here may need some further explanation. In the pattern the colon (:) is a separator of a list of patterns. The first pattern is the one we grant access to, while the rest of the patterns are the exceptions to that. So unchecked access for "/:/adminservlet:/protected/*:/forbidden/*" means access to everything (e.g. /foo/readme.text) is granted to everyone, with the exception of "/adminservlet" and paths that starts with either "/protected" or "/forbidden". In this case the translation from the XML form to Java is not as direct.

The next two constraints that we showed in web.xml concerned "role-based access", which means that only callers who are in the associated roles can access resources defined by those patterns. In XML this is accomplished by putting one or more patterns together with one or more roles in a security constraint. This is translated to Java by generating {role, permission} pairs for each unique combination that appears in the XML file. It's typically most convenient then to put these entries in a map, with role the key and permission the value, as was done above, but this is not strictly necessary. Here we see that the translation doesn't directly reflect the XML structure, but the link to the XML version can surely be seen in the translation.

Obtaining the generated Permissions

There is unfortunately no API available in Java EE to directly obtain the generated Permission instances. Instead, one has to install a JACC provider that is called by the container for each individual Permission that is generated. A ready to use provider was given in a previous article, but as we saw before they are not entirely trivial to install.

Conclusion

We've shown a few simple web.xml based security constraints and saw how they translated to Java Permission instances.

There are quite a few things that we did not look at, like the option to specify one or more HTTP Methods (GET, POST, etc) with or without the deny uncovered methods feature, the option to specify a transport level guarantee, the "any authenticated user" role, combinations of overlapping patterns with different constraints, etc etc. This was done intentionally to keep the example simple and to focus on the main concept of translation without going in to too many details. In a future article we may take a look at some more advanced cases.

Arjan Tijms

Thursday, March 19, 2015

Java EE authorization - JACC revisited part III

This is the third and final part of a series where we revisit JACC after taking an initial look at it last year.

In the first part we mainly looked at various role mapping strategies, while the main topic of the second part was obtaining the container specific role mapper and the container specific way of how a JACC provider is deployed.

In this third and final part we'll be bringing it all together and present a fully working JACC provider for a single application module (e.g. a single war).

Architecture

As explained before, implementing a JACC provider requires implementing three classes:

  1. PolicyConfigurationFactory
  2. PolicyConfiguration
  3. Policy
Zooming into these, the following is what is more accurately required to be implemented:
  1. A factory that provides an object that collects permissions
  2. A state machine that controls the life-cyle of this permission collector
  3. Linking permissions of multiple modules and utilities
  4. Collecting and managing permissions
  5. Processing permissions after collecting
  6. An "authorization module" using permissions for authorization decisions

In the implementation given before we put all this functionality in the specified three classes. Here we'll split out each item to a separate class (we'll skip linking though, which is only required for EARs where security constraints are defined in multiple modules). This will result in more classes in total, but each class is hopefully easier to understand.

A factory that provides an object that collects permissions

The factory is largely as given earlier, but contains a few fixes and makes use of the state machine that is shown below.

import static javax.security.jacc.PolicyContext.getContextID;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.security.jacc.PolicyConfiguration;
import javax.security.jacc.PolicyConfigurationFactory;
import javax.security.jacc.PolicyContextException;

public class TestPolicyConfigurationFactory extends PolicyConfigurationFactory {
    
    private static final ConcurrentMap<String, TestPolicyConfigurationStateMachine> configurators = new ConcurrentHashMap<>();

    @Override
    public PolicyConfiguration getPolicyConfiguration(String contextID, boolean remove) throws PolicyContextException {
        
        if (!configurators.containsKey(contextID)) {
            configurators.putIfAbsent(contextID, new TestPolicyConfigurationStateMachine(new TestPolicyConfiguration(contextID)));
        }
        
        TestPolicyConfigurationStateMachine testPolicyConfigurationStateMachine = configurators.get(contextID);
        
        if (remove) {
            testPolicyConfigurationStateMachine.delete();
        }
        
        // According to the contract of getPolicyConfiguration() every PolicyConfiguration returned from here
        // should always be transitioned to the OPEN state.
        testPolicyConfigurationStateMachine.open();
        
        return testPolicyConfigurationStateMachine;
    }
    
    @Override
    public boolean inService(String contextID) throws PolicyContextException {
        TestPolicyConfigurationStateMachine testPolicyConfigurationStateMachine = configurators.get(contextID);
        if (testPolicyConfigurationStateMachine == null) {
            return false;
        }
        
        return testPolicyConfigurationStateMachine.inService();
    }
    
    public static TestPolicyConfiguration getCurrentPolicyConfiguration() {
        return (TestPolicyConfiguration) configurators.get(getContextID()).getPolicyConfiguration();
    }
    
}

A state machine that controls the life-cyle of this permission collector

The state machine as required by the spec was left out in the previous example, but we've implemented it now. A possible implementation could have been to actually use a generic state machine that's been given some kind of rules file. Indeed, some implementations take this approach. But as the rules are actually not that complicated and there are not much transitions to speak of I found that just providing a few checks was a much easier method.

A class such as this would perhaps better be provided by the container, as it seems unlikely individual PolicyConfigurations would often if ever need to do anything specific here.

import static test.TestPolicyConfigurationStateMachine.State.DELETED;
import static test.TestPolicyConfigurationStateMachine.State.INSERVICE;
import static test.TestPolicyConfigurationStateMachine.State.OPEN;

import java.security.Permission;
import java.security.PermissionCollection;

import javax.security.jacc.PolicyConfiguration;
import javax.security.jacc.PolicyConfigurationFactory;
import javax.security.jacc.PolicyContextException;

public class TestPolicyConfigurationStateMachine implements PolicyConfiguration {

    public static enum State {
        OPEN, INSERVICE, DELETED
    };

    private State state = OPEN;
    private PolicyConfiguration policyConfiguration;
    

    public TestPolicyConfigurationStateMachine(PolicyConfiguration policyConfiguration) {
        this.policyConfiguration = policyConfiguration;
    }
    
    public PolicyConfiguration getPolicyConfiguration() {
        return policyConfiguration;
    }

    
    // ### Methods that can be called in any state and don't change state
    
    @Override
    public String getContextID() throws PolicyContextException {
        return policyConfiguration.getContextID();
    }
    
    @Override
    public boolean inService() throws PolicyContextException {
        return state == INSERVICE;
    }
    
    
    // ### Methods where state should be OPEN and don't change state
    
    @Override
    public void addToExcludedPolicy(Permission permission) throws PolicyContextException {
        checkStateIs(OPEN);
        policyConfiguration.addToExcludedPolicy(permission);
    }

    @Override
    public void addToUncheckedPolicy(Permission permission) throws PolicyContextException {
        checkStateIs(OPEN);
        policyConfiguration.addToUncheckedPolicy(permission);
    }

    @Override
    public void addToRole(String roleName, Permission permission) throws PolicyContextException {
        checkStateIs(OPEN);
        policyConfiguration.addToRole(roleName, permission);
    }
    
    @Override
    public void addToExcludedPolicy(PermissionCollection permissions) throws PolicyContextException {
        checkStateIs(OPEN);
        policyConfiguration.addToExcludedPolicy(permissions);
    }
    
    @Override
    public void addToUncheckedPolicy(PermissionCollection permissions) throws PolicyContextException {
        checkStateIs(OPEN);
        policyConfiguration.addToUncheckedPolicy(permissions);
    }
    
    @Override
    public void addToRole(String roleName, PermissionCollection permissions) throws PolicyContextException {
        checkStateIs(OPEN);
        policyConfiguration.addToRole(roleName, permissions);
    }
    
    @Override
    public void linkConfiguration(PolicyConfiguration link) throws PolicyContextException {
        checkStateIs(OPEN);
        policyConfiguration.linkConfiguration(link);
    }
    
    @Override
    public void removeExcludedPolicy() throws PolicyContextException {
        checkStateIs(OPEN);
        policyConfiguration.removeExcludedPolicy();
        
    }

    @Override
    public void removeRole(String roleName) throws PolicyContextException {
        checkStateIs(OPEN);
        policyConfiguration.removeRole(roleName);
    }

    @Override
    public void removeUncheckedPolicy() throws PolicyContextException {
        checkStateIs(OPEN);
        policyConfiguration.removeUncheckedPolicy();
    }
    
    
    // Methods that change the state
    //
    // commit() can only be called when the state is OPEN or INSERVICE and next state is always INSERVICE
    // delete() can always be called and target state will always be DELETED
    // open()   can always be called and target state will always be OPEN
    
    @Override
    public void commit() throws PolicyContextException {
        checkStateIsNot(DELETED);
        
        if (state == OPEN) {
            // Not 100% sure; allow double commit, or ignore double commit?
            // Here we ignore and only call commit on the actual policyConfiguration
            // when the state is OPEN
            policyConfiguration.commit();
            state = INSERVICE;
        }
    }

    @Override
    public void delete() throws PolicyContextException {
        policyConfiguration.delete();
        state = DELETED;
    }
    
    /**
     * Transition back to open. This method is required because of the {@link PolicyConfigurationFactory} contract, but is
     * mysteriously missing from the interface.
     */
    public void open() {
        state = OPEN;
    }
    
    
    // ### Private methods
    
    private void checkStateIs(State requiredState) {
        if (state != requiredState) {
            throw new IllegalStateException("Required status is " + requiredState + " but actual state is " + state);
        }
    }
    
    private void checkStateIsNot(State undesiredState) {
        if (state == undesiredState) {
            throw new IllegalStateException("State could not be " + undesiredState + " but actual state is");
        }
    }

}

Linking permissions of multiple modules and utilities

As mentioned we did not implement linking (perhaps we'll look at this in a future article), but as its an interface method we have to put an (empty) implementation somewhere. At the same time JACC curiously requires us to implement a couple of variations on the permission collection methods that don't even seem to be called in practice by any container we looked at. Finally the PolicyConfiguration interface requires an explicit life-cycle method and an identity method. The life-cycle method is not implemented either since all life-cycle managing is done by the state machine that wraps our actual PolicyConfiguration.

All these "distracting" methods were conveniently shoved into a base class as follows:

import static java.util.Collections.list;

import java.security.Permission;
import java.security.PermissionCollection;

import javax.security.jacc.PolicyConfiguration;
import javax.security.jacc.PolicyContextException;

public abstract class TestPolicyConfigurationBase implements PolicyConfiguration {
    
    private final String contextID;
    
    public TestPolicyConfigurationBase(String contextID) {
        this.contextID = contextID;
    }
    
    @Override
    public String getContextID() throws PolicyContextException {
        return contextID;
    }
    
    @Override
    public void addToExcludedPolicy(PermissionCollection permissions) throws PolicyContextException {
        for (Permission permission : list(permissions.elements())) {
            addToExcludedPolicy(permission);
        }
    }
    
    @Override
    public void addToUncheckedPolicy(PermissionCollection permissions) throws PolicyContextException {
        for (Permission permission : list(permissions.elements())) {
            addToUncheckedPolicy(permission);
        }
    }
    
    @Override
    public void addToRole(String roleName, PermissionCollection permissions) throws PolicyContextException {
        for (Permission permission : list(permissions.elements())) {
            addToRole(roleName, permission);
        }
    }

    @Override
    public void linkConfiguration(PolicyConfiguration link) throws PolicyContextException {
    }
    
    @Override
    public boolean inService() throws PolicyContextException {
        // Not used, taken care of by PolicyConfigurationStateMachine
        return true;
    }

}

Collecting and managing permissions

The next step concerns a base class for a PolicyConfiguration that takes care of the actual collection of permissions, and making those collected permissions available later on. For each permission that the container discovers it calls the appropriate method in this class.

This kind of permission collecting, like the state machine, is actually pretty generic. One wonders if it wouldn't be a great deal simpler if the container just called a single init() method once (or even better, used injection) with a simple data structure containing collections of all permission types. Looking at some container implementations it indeed looks like the container has those collections already and just loops over them handing them one by one to our PolicyConfiguration.

import java.security.Permission;
import java.security.Permissions;
import java.util.HashMap;
import java.util.Map;

import javax.security.jacc.PolicyContextException;

public abstract class TestPolicyConfigurationPermissions extends TestPolicyConfigurationBase {

    private Permissions excludedPermissions = new Permissions();
    private Permissions uncheckedPermissions = new Permissions();
    private Map<String, Permissions> perRolePermissions = new HashMap<String, Permissions>();
    
    public TestPolicyConfigurationPermissions(String contextID) {
        super(contextID);
    }

    @Override
    public void addToExcludedPolicy(Permission permission) throws PolicyContextException {
        excludedPermissions.add(permission);
    }

    @Override
    public void addToUncheckedPolicy(Permission permission) throws PolicyContextException {
        uncheckedPermissions.add(permission);
    }

    @Override
    public void addToRole(String roleName, Permission permission) throws PolicyContextException {
        Permissions permissions = perRolePermissions.get(roleName);
        if (permissions == null) {
            permissions = new Permissions();
            perRolePermissions.put(roleName, permissions);
        }
        
        permissions.add(permission);
    }
    
    @Override
    public void delete() throws PolicyContextException {
        removeExcludedPolicy();
        removeUncheckedPolicy();
        perRolePermissions.clear();
    }

    @Override
    public void removeExcludedPolicy() throws PolicyContextException {
        excludedPermissions = new Permissions();
    }

    @Override
    public void removeRole(String roleName) throws PolicyContextException {
        if (perRolePermissions.containsKey(roleName)) {
            perRolePermissions.remove(roleName);
        } else if ("*".equals(roleName)) {
            perRolePermissions.clear();
        }
    }

    @Override
    public void removeUncheckedPolicy() throws PolicyContextException {
        uncheckedPermissions = new Permissions();
    }
    
    public Permissions getExcludedPermissions() {
        return excludedPermissions;
    }

    public Permissions getUncheckedPermissions() {
        return uncheckedPermissions;
    }

    public Map<String, Permissions> getPerRolePermissions() {
        return perRolePermissions;
    }

}

Processing permissions after collecting

The final part of the PolicyConfiguration concerns a kind of life cycle method again, namely a method that the container calls to indicate all permissions have been handed over to the PolicyConfiguration. In a more modern implementation this might have been an @PostConstruct annotated method.

Contrary to most methods of the PolicyConfiguration that we've seen until now, what happens here is pretty specific to the custom policy provider. Some implementations do a lot of work here and generate a .policy file in the standard Java SE format and write that to disk. This file is then intended to be read back by a standard Java SE Policy implementation.

Other implementations use this moment to optimize the collected permissions by transforming them into their own internal data structure.

In our case we keep the permissions as we collected them and just instantiate a role mapper implementation at this point. The full set of roles that are associated with permissions that each depend on a certain role are passed into the role mapper.

import javax.security.jacc.PolicyContextException;

public class TestPolicyConfiguration extends TestPolicyConfigurationPermissions {

    public TestPolicyConfiguration(String contextID) {
        super(contextID);
    }
    
    private TestRoleMapper roleMapper;

    @Override
    public void commit() throws PolicyContextException {
        roleMapper = new TestRoleMapper(getContextID(), getPerRolePermissions().keySet());
    }
    
    public TestRoleMapper getRoleMapper() {
        return roleMapper;
    }

}
The role mapper referenced in the code shown above was presented in part II of this article and didn't change between parts, but for completeness we'll present it here again:
import static java.util.Arrays.asList;
import static java.util.Collections.list;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.Principal;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.security.auth.Subject;

public class TestRoleMapper {
    
    private static Object geronimoPolicyConfigurationFactoryInstance;
    private static ConcurrentMap<String, Map<Principal, Set<String>>> geronimoContextToRoleMapping;
    
    private Map<String, List<String>> groupToRoles = new HashMap<>();

    private boolean oneToOneMapping;
    private boolean anyAuthenticatedUserRoleMapped = false;
    
    public static void onFactoryCreated() {
        tryInitGeronimo();
    }
    
    private static void tryInitGeronimo() {
        try {
            // Geronimo 3.0.1 contains a protection mechanism to ensure only a Geronimo policy provider is installed.
            // This protection can be beat by creating an instance of GeronimoPolicyConfigurationFactory once. This instance
            // will statically register itself with an internal Geronimo class
            geronimoPolicyConfigurationFactoryInstance = Class.forName("org.apache.geronimo.security.jacc.mappingprovider.GeronimoPolicyConfigurationFactory").newInstance();
            geronimoContextToRoleMapping = new ConcurrentHashMap<>();
        } catch (Exception e) {
            // ignore
        }
    }
    
    public static void onPolicyConfigurationCreated(final String contextID) {
        
        // Are we dealing with Geronimo?
        if (geronimoPolicyConfigurationFactoryInstance != null) {
            
            // PrincipalRoleConfiguration
            
            try {
                Class<?> geronimoPolicyConfigurationClass = Class.forName("org.apache.geronimo.security.jacc.mappingprovider.GeronimoPolicyConfiguration");
                
                Object geronimoPolicyConfigurationProxy = Proxy.newProxyInstance(TestRoleMapper.class.getClassLoader(), new Class[] {geronimoPolicyConfigurationClass}, new InvocationHandler() {
                    
                    @SuppressWarnings("unchecked")
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        // Take special action on the following method:
                        
                        // void setPrincipalRoleMapping(Map<Principal, Set<String>> principalRoleMap) throws PolicyContextException;
                        if (method.getName().equals("setPrincipalRoleMapping")) {
                            
                            geronimoContextToRoleMapping.put(contextID, (Map<Principal, Set<String>>) args[0]);
                            
                        }
                        return null;
                    }
                });
                
                // Set the proxy on the GeronimoPolicyConfigurationFactory so it will call us back later with the role mapping via the following method:
                
                // public void setPolicyConfiguration(String contextID, GeronimoPolicyConfiguration configuration) {
                Class.forName("org.apache.geronimo.security.jacc.mappingprovider.GeronimoPolicyConfigurationFactory")
                     .getMethod("setPolicyConfiguration", String.class, geronimoPolicyConfigurationClass)
                     .invoke(geronimoPolicyConfigurationFactoryInstance, contextID, geronimoPolicyConfigurationProxy);
                
                
            } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                // Ignore
            }
        }
    }
    

    public TestRoleMapper(String contextID, Collection<String> allDeclaredRoles) {
        // Initialize the groupToRoles map

        // Try to get a hold of the proprietary role mapper of each known
        // AS. Sad that this is needed :(
        if (tryGlassFish(contextID, allDeclaredRoles)) {
            return;
        } else if (tryWebLogic(contextID, allDeclaredRoles)) {
            return;
        } else if (tryGeronimo(contextID, allDeclaredRoles)) {
            return;
        } else {
            oneToOneMapping = true;
        }
    }

    public List<String> getMappedRolesFromPrincipals(Principal[] principals) {
        return getMappedRolesFromPrincipals(asList(principals));
    }

    public boolean isAnyAuthenticatedUserRoleMapped() {
        return anyAuthenticatedUserRoleMapped;
    }

    public List<String> getMappedRolesFromPrincipals(Iterable<Principal> principals) {

        // Extract the list of groups from the principals. These principals typically contain
        // different kind of principals, some groups, some others. The groups are unfortunately vendor
        // specific.
        List<String> groups = getGroupsFromPrincipals(principals);

        // Map the groups to roles. E.g. map "admin" to "administrator". Some servers require this.
        return mapGroupsToRoles(groups);
    }

    private List<String> mapGroupsToRoles(List<String> groups) {

        if (oneToOneMapping) {
            // There is no mapping used, groups directly represent roles.
            return groups;
        }

        List<String> roles = new ArrayList<>();

        for (String group : groups) {
            if (groupToRoles.containsKey(group)) {
                roles.addAll(groupToRoles.get(group));
            }
        }

        return roles;
    }

    private boolean tryGlassFish(String contextID, Collection<String> allDeclaredRoles) {

        try {
            Class<?> SecurityRoleMapperFactoryClass = Class.forName("org.glassfish.deployment.common.SecurityRoleMapperFactory");

            Object factoryInstance = Class.forName("org.glassfish.internal.api.Globals")
                                          .getMethod("get", SecurityRoleMapperFactoryClass.getClass())
                                          .invoke(null, SecurityRoleMapperFactoryClass);

            Object securityRoleMapperInstance = SecurityRoleMapperFactoryClass.getMethod("getRoleMapper", String.class)
                                                                              .invoke(factoryInstance, contextID);

            @SuppressWarnings("unchecked")
            Map<String, Subject> roleToSubjectMap = (Map<String, Subject>) Class.forName("org.glassfish.deployment.common.SecurityRoleMapper")
                                                                                .getMethod("getRoleToSubjectMapping")
                                                                                .invoke(securityRoleMapperInstance);

            for (String role : allDeclaredRoles) {
                if (roleToSubjectMap.containsKey(role)) {
                    Set<Principal> principals = roleToSubjectMap.get(role).getPrincipals();

                    List<String> groups = getGroupsFromPrincipals(principals);
                    for (String group : groups) {
                        if (!groupToRoles.containsKey(group)) {
                            groupToRoles.put(group, new ArrayList<String>());
                        }
                        groupToRoles.get(group).add(role);
                    }

                    if ("**".equals(role) && !groups.isEmpty()) {
                        // JACC spec 3.2 states:
                        //
                        // "For the any "authenticated user role", "**", and unless an application specific mapping has
                        // been established for this role,
                        // the provider must ensure that all permissions added to the role are granted to any
                        // authenticated user."
                        //
                        // Here we check for the "unless" part mentioned above. If we're dealing with the "**" role here
                        // and groups is not
                        // empty, then there's an application specific mapping and "**" maps only to those groups, not
                        // to any authenticated user.
                        anyAuthenticatedUserRoleMapped = true;
                    }
                }
            }

            return true;

        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e) {
            return false;
        }
    }

    private boolean tryWebLogic(String contextID, Collection<String> allDeclaredRoles) {

        try {

            // See http://docs.oracle.com/cd/E21764_01/apirefs.1111/e13941/weblogic/security/jacc/RoleMapperFactory.html
            Class<?> roleMapperFactoryClass = Class.forName("weblogic.security.jacc.RoleMapperFactory");

            // RoleMapperFactory implementation class always seems to be the value of what is passed on the commandline
            // via the -Dweblogic.security.jacc.RoleMapperFactory.provider option.
            // See http://docs.oracle.com/cd/E57014_01/wls/SCPRG/server_prot.htm
            Object roleMapperFactoryInstance = roleMapperFactoryClass.getMethod("getRoleMapperFactory")
                                                                     .invoke(null);

            // See http://docs.oracle.com/cd/E21764_01/apirefs.1111/e13941/weblogic/security/jacc/RoleMapperFactory.html#getRoleMapperForContextID(java.lang.String)
            Object roleMapperInstance = roleMapperFactoryClass.getMethod("getRoleMapperForContextID", String.class)
                                                              .invoke(roleMapperFactoryInstance, contextID);

            // This seems really awkward; the Map contains BOTH group names and user names, without ANY way to
            // distinguish between the two.
            // If a user now has a name that happens to be a role as well, we have an issue :X
            @SuppressWarnings("unchecked")
            Map<String, String[]> roleToPrincipalNamesMap = (Map<String, String[]>) Class.forName("weblogic.security.jacc.simpleprovider.RoleMapperImpl")
                                                                                         .getMethod("getRolesToPrincipalNames")
                                                                                         .invoke(roleMapperInstance);

            for (String role : allDeclaredRoles) {
                if (roleToPrincipalNamesMap.containsKey(role)) {

                    List<String> groupsOrUserNames = asList(roleToPrincipalNamesMap.get(role));

                    for (String groupOrUserName : roleToPrincipalNamesMap.get(role)) {
                        // Ignore the fact that the collection also contains user names and hope
                        // that there are no user names in the application with the same name as a group
                        if (!groupToRoles.containsKey(groupOrUserName)) {
                            groupToRoles.put(groupOrUserName, new ArrayList<String>());
                        }
                        groupToRoles.get(groupOrUserName).add(role);
                    }

                    if ("**".equals(role) && !groupsOrUserNames.isEmpty()) {
                        // JACC spec 3.2 states: [...]
                        anyAuthenticatedUserRoleMapped = true;
                    }
                }
            }

            return true;

        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e) {
            return false;
        }
    }
    
    private boolean tryGeronimo(String contextID, Collection<String> allDeclaredRoles) {
        if (geronimoContextToRoleMapping != null) {
            
            if (geronimoContextToRoleMapping.containsKey(contextID)) {
                Map<Principal, Set<String>> principalsToRoles = geronimoContextToRoleMapping.get(contextID);
                
                for (Map.Entry<Principal, Set<String>> entry : principalsToRoles.entrySet()) {
                    
                    // Convert the principal that's used as the key in the Map to a list of zero or more groups.
                    // (for Geronimo we know that using the default role mapper it's always zero or one group)
                    for (String group : principalToGroups(entry.getKey())) {
                        if (!groupToRoles.containsKey(group)) {
                            groupToRoles.put(group, new ArrayList<String>());
                        }
                        groupToRoles.get(group).addAll(entry.getValue());
                        
                        if (entry.getValue().contains("**")) {
                            // JACC spec 3.2 states: [...]
                            anyAuthenticatedUserRoleMapped = true;
                        }
                    }
                }
            }
            
            return true;
        }
        
        return false;
    }

    /**
     * Extracts the roles from the vendor specific principals. SAD that this is needed :(
     * 
     * @param principals
     * @return
     */
    public List<String> getGroupsFromPrincipals(Iterable<Principal> principals) {
        List<String> groups = new ArrayList<>();

        for (Principal principal : principals) {
            if (principalToGroups(principal, groups)) {
                // return value of true means we're done early. This can be used
                // when we know there's only 1 principal holding all the groups
                return groups;
            }
        }

        return groups;
    }
    
    public List<String> principalToGroups(Principal principal) {
        List<String> groups = new ArrayList<>();
        principalToGroups(principal, groups);
        return groups;
    }
    
    public boolean principalToGroups(Principal principal, List<String> groups) {
        switch (principal.getClass().getName()) {

            case "org.glassfish.security.common.Group": // GlassFish
            case "org.apache.geronimo.security.realm.providers.GeronimoGroupPrincipal": // Geronimo
            case "weblogic.security.principal.WLSGroupImpl": // WebLogic
            case "jeus.security.resource.GroupPrincipalImpl": // JEUS
                groups.add(principal.getName());
                break;
    
            case "org.jboss.security.SimpleGroup": // JBoss
                if (principal.getName().equals("Roles") && principal instanceof Group) {
                    Group rolesGroup = (Group) principal;
                    for (Principal groupPrincipal : list(rolesGroup.members())) {
                        groups.add(groupPrincipal.getName());
                    }
    
                    // Should only be one group holding the roles, so can exit the loop
                    // early
                    return true;
                }
            }
        return false;
    }

}

An "authorization module" using permissions for authorization decisions

At long last we present the actual "authorization module" (called Policy in Java SE and JACC). Compared to the version we presented before this now delegates extracting the list of roles from the principles that are associated with the authenticated user to the role mapper we showed above. In addition to that we also added the case where we check for the so-called "any authenticated user", which means it doesn't matter which roles a user has, but only the fact if this user is authenticated or not counts.

This authorization module implements the default authorization algorithm defined by the Servlet and JACC specs, which does the following checks in order:

  1. Is permission excluded? (nobody can access those)
  2. Is permission unchecked? (everyone can access those)
  3. Is permission granted to every authenticated user?
  4. Is permission granted to any of the roles the current user is in?
  5. Is permission granted by the previous (if any) authorization module?

The idea of a custom authorization module is often to do something specific authorization wise, so this would be the most likely place to put custom code. In fact, if only this particular class could be injected with the permissions that now have to be collected by our own classes as shown above, then JACC would be massively simplified in one fell swoop.

In that case only this class would be have to be implemented. Even better would be if the default algorithm was also provided in a portable way. With that we could potentially only implement the parts that are really different for our custom implementation and leave the rest to the default implementation.

import static java.util.Arrays.asList;
import static java.util.Collections.list;
import static test.TestPolicyConfigurationFactory.getCurrentPolicyConfiguration;

import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.Principal;
import java.security.ProtectionDomain;
import java.util.List;
import java.util.Map;

public class TestPolicy extends Policy {
    
    private Policy previousPolicy = Policy.getPolicy();
    
    @Override
    public boolean implies(ProtectionDomain domain, Permission permission) {
            
        TestPolicyConfiguration policyConfiguration = getCurrentPolicyConfiguration();
        TestRoleMapper roleMapper = policyConfiguration.getRoleMapper();
    
        if (isExcluded(policyConfiguration.getExcludedPermissions(), permission)) {
            // Excluded permissions cannot be accessed by anyone
            return false;
        }
        
        if (isUnchecked(policyConfiguration.getUncheckedPermissions(), permission)) {
            // Unchecked permissions are free to be accessed by everyone
            return true;
        }
        
        List<Principal> currentUserPrincipals = asList(domain.getPrincipals());
        
        if (!roleMapper.isAnyAuthenticatedUserRoleMapped() && !currentUserPrincipals.isEmpty()) {
            // The "any authenticated user" role is not mapped, so available to anyone and the current
            // user is assumed to be authenticated (we assume that an unauthenticated user doesn't have any principals
            // whatever they are)
            if (hasAccessViaRole(policyConfiguration.getPerRolePermissions(), "**", permission)) {
                // Access is granted purely based on the user being authenticated (the actual roles, if any, the user has it not important)
                return true;
            }
        }
        
        if (hasAccessViaRoles(policyConfiguration.getPerRolePermissions(), roleMapper.getMappedRolesFromPrincipals(currentUserPrincipals), permission)) {
            // Access is granted via role. Note that if this returns false it doesn't mean the permission is not
            // granted. A role can only grant, not take away permissions.
            return true;
        }
        
        // Access not granted via any of the JACC maintained Permissions. Check the previous (default) policy.
        // Note: this is likely to be called in case it concerns a Java SE type permissions.
        // TODO: Should we not distinguish between JACC and Java SE Permissions at the start of this method? Seems
        //       very unlikely that JACC would ever say anything about a Java SE Permission, or that the Java SE
        //       policy says anything about a JACC Permission. Why are these two systems even combined in the first place?
        if (previousPolicy != null) {
            return previousPolicy.implies(domain, permission);
        }
        
        return false;
    }

    @Override
    public PermissionCollection getPermissions(ProtectionDomain domain) {

        Permissions permissions = new Permissions();
        
        TestPolicyConfiguration policyConfiguration = getCurrentPolicyConfiguration();
        TestRoleMapper roleMapper = policyConfiguration.getRoleMapper();
        
        Permissions excludedPermissions = policyConfiguration.getExcludedPermissions();

        // First get all permissions from the previous (original) policy
        if (previousPolicy != null) {
            collectPermissions(previousPolicy.getPermissions(domain), permissions, excludedPermissions);
        }

        // If there are any static permissions, add those next
        if (domain.getPermissions() != null) {
            collectPermissions(domain.getPermissions(), permissions, excludedPermissions);
        }

        // Thirdly, get all unchecked permissions
        collectPermissions(policyConfiguration.getUncheckedPermissions(), permissions, excludedPermissions);

        // Finally get the permissions for each role *that the current user has*
        //
        // Note that the principles that are put into the ProtectionDomain object are those from the current user.
        // (for a Server application, passing in a Subject would have been more logical, but the Policy class was
        // made for Java SE with code-level security in mind)
        Map<String, Permissions> perRolePermissions = policyConfiguration.getPerRolePermissions();
        for (String role : roleMapper.getMappedRolesFromPrincipals(domain.getPrincipals())) {
            if (perRolePermissions.containsKey(role)) {
                collectPermissions(perRolePermissions.get(role), permissions, excludedPermissions);
            }
        }

        return permissions;
    }
    
    @Override
    public PermissionCollection getPermissions(CodeSource codesource) {

        Permissions permissions = new Permissions();
        
        TestPolicyConfigurationPermissions policyConfiguration = getCurrentPolicyConfiguration();
        Permissions excludedPermissions = policyConfiguration.getExcludedPermissions();

        // First get all permissions from the previous (original) policy
        if (previousPolicy != null) {
            collectPermissions(previousPolicy.getPermissions(codesource), permissions, excludedPermissions);
        }

        // Secondly get the static permissions. Note that there are only two sources possible here, without
        // knowing the roles of the current user we can't check the per role permissions.
        collectPermissions(policyConfiguration.getUncheckedPermissions(), permissions, excludedPermissions);

        return permissions;
    }
    
    private boolean isExcluded(Permissions excludedPermissions, Permission permission) {
        if (excludedPermissions.implies(permission)) {
            return true;
        }
        
        for (Permission excludedPermission : list(excludedPermissions.elements())) {
            if (permission.implies(excludedPermission)) {
                return true;
            }
        }
        
        return false;
    }
    
    private boolean isUnchecked(Permissions uncheckedPermissions, Permission permission) {
        return uncheckedPermissions.implies(permission);
    }
    
    private boolean hasAccessViaRoles(Map<String, Permissions> perRolePermissions, List<String> roles, Permission permission) {
        for (String role : roles) {
            if (hasAccessViaRole(perRolePermissions, role, permission)) {
                return true;
            }
        }
        
        return false;
    }
    
    private boolean hasAccessViaRole(Map<String, Permissions> perRolePermissions, String role, Permission permission) {
        return perRolePermissions.containsKey(role) && perRolePermissions.get(role).implies(permission);
    }
    
    /**
     * Copies permissions from a source into a target skipping any permission that's excluded.
     * 
     * @param sourcePermissions
     * @param targetPermissions
     * @param excludedPermissions
     */
    private void collectPermissions(PermissionCollection sourcePermissions, PermissionCollection targetPermissions, Permissions excludedPermissions) {
        
        boolean hasExcludedPermissions = excludedPermissions.elements().hasMoreElements();
        
        for (Permission permission : list(sourcePermissions.elements())) {
            if (!hasExcludedPermissions || !isExcluded(excludedPermissions, permission)) {
                targetPermissions.add(permission);
            }
        }
    }
    
}

Conclusion

This concludes our three parter on revisiting JACC. In this third and final part we have looked at an actual Policy Provider. We have broken up the implementation into several parts that each focused on a particular responsibility. While the Policy Provider is complete and working (tested on GlassFish, WebLogic and Geronimo) we did not implement module linking yet, so it's with the caveat that it only works within a single war.

To implement another custom Policy Provider many of these parts can probably be re-used as-is and likely only the Policy itself has to customized.

Arjan Tijms

Wednesday, March 11, 2015

The most popular Java EE servers in 2014/2015 according to OmniFaces users

For a little over 3 months (from half of November 2014 to late February 2015) we had a poll on the OmniFaces website asking what AS (Application Server) people used with OmniFaces (people could select multiple servers).

The response was quite overwhelming for our little project; no less than 840 people responded, choosing a grand total of 1108 servers.

The final results are as follows:

Position Server Votes (Percentage)
1 JBoss (AS/EAP/WildFly) 395 (47%)
2 GlassFish 206 (24%)
3 Tomcat/Mojarra/Weld 186 (22%)
4 TomEE 85 (10%)
5 WebSphere 55 (6%)
6 WebLogic 49 (6%)
7 Tomcat/MyFaces/OWB 33 (3%)
8 Jetty/Mojarra/Weld 19 (2%)
9 Geronimo 13 (1%)
10 JEUS 11 (1%)
11 Liberty 9 (1%)
12 Jetty/MyFaces/OWB 9 (1%)
13 JOnAS 8 (0%)
14 NetWeaver 8 (0%)
15 Resin 6 (0%)
16 InforSuite 5 (0%)
17 WebOTX 4 (0%)
18 Interstage AS 4 (0%)
19 (u)Cosminexus 3 (0%)

As can be seen the clear winner here is JBoss, which gets nearly half of all votes and nearly twice the amount of the runner up; GlassFish. Just slightly below GlassFish at number 3 is Tomcat in the specific combination with Mojarra and Weld.

It has be noted that Mojarra & Weld are typically but a small part of a homegrown Java EE stack, which often also includes things like Hibernate, Hibernate-Validations and many more components. For the specific case of OmniFaces however the Servlet, JSF and CDI implementations are what matter most so that's why we specifically included these in the poll. Another homegrown stack based on Tomcat, but using Myfaces and OWB (OpenWebBeans) instead scores significantly lower and ends up at place 7.

We acknowledge that people not necessarily have to use Mojarra and Weld together, but can also use Mojarra with OWB, or MyFaces with Weld. However we wanted to somewhat limit the options for homegrown stacks, and a little research ahead hinted these were the more popular combinations. In a follow up poll we may zoom into this and specifically address homegrown stacks by asking which individual components people use.

An interesting observation is that the entire top 4 consists solely out of open source servers, together good for 103% relative to the amount of people who voted (remember that 1 person could vote for multiple servers), or a total of 79% relative to all servers voted for.

While these are certainly impressive numbers, we do have to realize that the voters are self selected and specifically concern those who use OmniFaces. OmniFaces is an open source library without any form of commercial support. It's perhaps not entirely unreasonable to surmise that environments that favor closed source commercially supported servers are less likely to use OmniFaces. Taking that into account, the numbers thus don't necessarily mean that open source servers are indeed used that much in general.

That said, the two big commercial servers WebSphere and WebLogic still got a fair amount of votes; 104 together which is 9% relative to all servers voted for.

The fully open source and once much talked about server Geronimo got significantly few votes; only 13. The fact that Geronimo has more or less stopped developing its server and the lack of a visible community (people blogging about it, writing articles, responding to issues etc) probably contributes to that.

It's somewhat surprising that IBM's new lightweight AS Liberty got only 9 votes, where older (and more heavier) AS WebSphere got 55 votes. Maybe Liberty indeed isn't used that much yet, or maybe the name recognition isn't that big at the moment. A potential weakness in the poll is that we left out the company names. For well known servers such as JBoss and GlassFish you rarely see people calling it Red Hat JBoss or Oracle GlassFish, but in case of Liberty it might have been clearer to call it "IBM Liberty (WLP)".

Another small surprise is that the somewhat obscure server JEUS got as many votes as it did; 11 in total. This is perhaps extra surprising since creator TMaxSoft for some unknown reason consistently calls it a WAS instead of an AS, and the poll asked for the latter.

The "Japanese obscure three" (WebOTX, Interstage AS and (u)Cosminexus) are at the bottom of the list, yet at least 3 to 4 persons each claim to be using it with OmniFaces. Since not all of these servers are trivial to obtain, we've never tested OmniFaces on any of them so frankly have no idea how well OmniFaces runs on them. Even though according to this poll it concerns just a small amount of people, we're now quite eager to try out a few of these servers in the future, just to see how things work there.

Conclusion

For the particular community of those who use Omnifaces, we've seen that open source servers in general and particularly JBoss, GlassFish and TomEE are the most popular Java EE servers. Tomcat and Jetty were included as well, but aren't officially Java EE (although one can build stacks on them that get close).

A couple of servers, which really are complete Java EE implementations just as well and one might think take just as much work to build and maintain, only see a very low amount of users according to this poll. That's of course not to say that they aren't used much in general, but may just gather to a different audience.

Arjan Tijms