jakartaee / authentication

Jakarta Authentication
https://eclipse.org/ee4j/jaspic
Other
23 stars 30 forks source link

Clarify state and concurrency expectations of Jakarta Authentication APIs #119

Open darranl opened 3 years ago

darranl commented 3 years ago

This issue is to explore and clarify the state expectations of the Jakarta Authentication APIs and the parameters passed to those APIs.

This is to follow up on some discussions regarding state in the CallbackHandler but I think we should also consider some of the Authentication APIs where state may be shared.

arjantijms commented 3 years ago

Jakarta Authentication is clear about the usage of the MessageInfo to share state between calls to a ServerAuthModule, but not super clear with respect to state in the CallbackHandler.

We have already seen that this leads to some portability issues; Soteria considers the CallbackHandler completely stateless and assumes all state is stored in either thread locals or in the Subject.

This brings us to a second state issue. The assumption now would be only "state is not in the CallbackHandler", but a step beyond that is saying "state must be in the Subject"

arjantijms commented 3 years ago

(from https://github.com/eclipse-ee4j/authentication/issues/110#issuecomment-830020432)

Well would it be unreasonable to discuss some minimal requirements on the resulting Subject? It seems as though it would be useful to have some common expectations set for the resulting Subject.

From Soteria this is roughly what current implementations do:

private Principal getVendorCallerPrincipal(Principal principal, boolean isEjb) {
        switch (principal.getClass().getName()) {
            case "org.glassfish.security.common.PrincipalImpl": // GlassFish/Payara
                return getAuthenticatedPrincipal(principal, "ANONYMOUS", isEjb);
            case "weblogic.security.principal.WLSUserImpl": // WebLogic
                return getAuthenticatedPrincipal(principal, "<anonymous>", isEjb);
            case "com.ibm.ws.security.authentication.principals.WSPrincipal": // Liberty
                return getAuthenticatedPrincipal(principal, "UNAUTHENTICATED", isEjb);
            // JBoss EAP/WildFly convention 1 - single top level principal of the below type
            case "org.jboss.security.SimplePrincipal":
                return getAuthenticatedPrincipal(principal, "anonymous", isEjb);
            // JBoss EAP/WildFly convention 2 - the one and only principal in group called CallerPrincipal
            case "org.jboss.security.SimpleGroup":
                if (principal.getName().equals("CallerPrincipal") && principal.getClass().getName().equals("org.jboss.security.SimpleGroup")) {
                    Enumeration<? extends Principal> groupMembers = null;
                    try {
                        groupMembers = (Enumeration<? extends Principal>) Class.forName(className("org.jboss.security.SimpleGroup"))
                                .getMethod("members")
                                .invoke(principal);
                    } catch (Exception e) {

                    }

                    if (groupMembers != null && groupMembers.hasMoreElements()) {
                        return getAuthenticatedPrincipal(groupMembers.nextElement(), "anonymous", isEjb);
                    }
                }
                break;
            case "org.apache.tomee.catalina.TomcatSecurityService$TomcatUser": // TomEE
                try {
                    Principal tomeePrincipal = (Principal) Class.forName(className("org.apache.catalina.realm.GenericPrincipal"))
                            .getMethod("getUserPrincipal")
                            .invoke(
                                    Class.forName(className("org.apache.tomee.catalina.TomcatSecurityService$TomcatUser"))
                                            .getMethod("getTomcatPrincipal")
                                            .invoke(principal));

                    return getAuthenticatedPrincipal(tomeePrincipal, "guest", isEjb);
                } catch (Exception e) {

                }
                break;
        }

        if (CallerPrincipal.class.isAssignableFrom(principal.getClass())) {
            return principal;
        }

        return null;
    }

The easiest thing to say is perhaps that "some vendor specific representation of the caller principal and groups" must be present in the subject. The CallerPrincipalCallback or GroupPrincipalCallback are currently able to write these into the Subject. The test to see if they are stored correctly would be to have both these two callbacks being able to retrieve exactly what they wrote into it.

so say (pseudo code)

handle CallerPrincipalCallback subject write fooWritten

string fooRead = handle CallerPrincipalCallback subject read

assert fooRead == fooWritten
darranl commented 3 years ago

I am going to put together some examples, IMO there are some parts of the API to consider before we get to the CallbackHandler. For the CallbackHandler there are three areas state may be relevent:

  1. Request level state across calls to handle();
  2. Deployment level state in relation to configuration referenced by the CallbackHandler.
  3. Deployment level state in relation to selecting a different CallbackHandler implementation.
arjantijms commented 2 years ago

@darranl time is running out quickly for Jakarta Authentication 3.0 and Jakarta EE 10. Do we still want to address something here for 3.0 though? I think at this point with about 2 weeks to go we can only really go for the low hanging fruit.