This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/master by this push:
     new 6a927fc  Fix BZ 64715. Add PasswordValidationCallback support.
6a927fc is described below

commit 6a927fcc3e3f1fdd40be4106e56f888bfebf04b8
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Fri Sep 18 09:12:53 2020 +0100

    Fix BZ 64715. Add PasswordValidationCallback support.
    
    https://bz.apache.org/bugzilla/show_bug.cgi?id=64715
    Patch provided by Robert Rodewald
---
 .../catalina/authenticator/AuthenticatorBase.java  |  55 +++++----
 .../authenticator/jaspic/CallbackHandlerImpl.java  |  52 +++++----
 .../authenticator/jaspic/LocalStrings.properties   |   2 +
 .../TestJaspicCallbackHandlerInAuthenticator.java  | 129 +++++++++++++++++++--
 webapps/docs/changelog.xml                         |   5 +
 5 files changed, 191 insertions(+), 52 deletions(-)

diff --git a/java/org/apache/catalina/authenticator/AuthenticatorBase.java 
b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
index 0911152..faf98cb 100644
--- a/java/org/apache/catalina/authenticator/AuthenticatorBase.java
+++ b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
@@ -43,6 +43,7 @@ import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 
 import org.apache.catalina.Authenticator;
+import org.apache.catalina.Contained;
 import org.apache.catalina.Container;
 import org.apache.catalina.Context;
 import org.apache.catalina.Globals;
@@ -51,7 +52,6 @@ import org.apache.catalina.Realm;
 import org.apache.catalina.Session;
 import org.apache.catalina.TomcatPrincipal;
 import org.apache.catalina.Valve;
-import org.apache.catalina.authenticator.jaspic.CallbackHandlerImpl;
 import org.apache.catalina.authenticator.jaspic.MessageInfoImpl;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
@@ -220,7 +220,7 @@ public abstract class AuthenticatorBase extends ValveBase
      * default {@link 
org.apache.catalina.authenticator.jaspic.CallbackHandlerImpl}
      * will be used.
      */
-    protected String jaspicCallbackHandlerClass = null;
+    protected String jaspicCallbackHandlerClass = 
"org.apache.catalina.authenticator.jaspic.CallbackHandlerImpl";
 
     /**
      * Should the auth information (remote user and auth type) be returned as 
response
@@ -247,6 +247,7 @@ public abstract class AuthenticatorBase extends ValveBase
 
     private volatile String jaspicAppContextID = null;
     private volatile Optional<AuthConfigProvider> jaspicProvider = null;
+    private volatile CallbackHandler jaspicCallbackHandler = null;
 
 
     // ------------------------------------------------------------- Properties
@@ -773,7 +774,7 @@ public abstract class AuthenticatorBase extends ValveBase
                 new MessageInfoImpl(request.getRequest(), 
response.getResponse(), authMandatory);
 
         try {
-            CallbackHandler callbackHandler = createCallbackHandler();
+            CallbackHandler callbackHandler = getCallbackHandler();
             ServerAuthConfig serverAuthConfig = 
jaspicProvider.getServerAuthConfig(
                     "HttpServlet", jaspicAppContextID, callbackHandler);
             String authContextID = 
serverAuthConfig.getAuthContextID(jaspicState.messageInfo);
@@ -787,29 +788,41 @@ public abstract class AuthenticatorBase extends ValveBase
         return jaspicState;
     }
 
+
+    private CallbackHandler getCallbackHandler() {
+        CallbackHandler handler = jaspicCallbackHandler;
+        if (handler == null) {
+            handler = createCallbackHandler();
+        }
+        return handler;
+    }
+
+
     private CallbackHandler createCallbackHandler() {
         CallbackHandler callbackHandler = null;
-        if (jaspicCallbackHandlerClass == null) {
-            callbackHandler = CallbackHandlerImpl.getInstance();
-        } else {
-            Class<?> clazz = null;
-            try {
-                clazz = Class.forName(jaspicCallbackHandlerClass, true,
-                        Thread.currentThread().getContextClassLoader());
-            } catch (ClassNotFoundException e) {
-                // Proceed with the retry below
-            }
 
-            try {
-                if (clazz == null) {
-                    clazz = Class.forName(jaspicCallbackHandlerClass);
-                }
-                callbackHandler = 
(CallbackHandler)clazz.getConstructor().newInstance();
-            } catch (ReflectiveOperationException e) {
-                throw new SecurityException(e);
+        Class<?> clazz = null;
+        try {
+            clazz = Class.forName(jaspicCallbackHandlerClass, true,
+                    Thread.currentThread().getContextClassLoader());
+        } catch (ClassNotFoundException e) {
+            // Proceed with the retry below
+        }
+
+        try {
+            if (clazz == null) {
+                clazz = Class.forName(jaspicCallbackHandlerClass);
             }
+            callbackHandler = 
(CallbackHandler)clazz.getConstructor().newInstance();
+        } catch (ReflectiveOperationException e) {
+            throw new SecurityException(e);
+        }
+
+        if (callbackHandler instanceof Contained) {
+            ((Contained) callbackHandler).setContainer(getContainer());
         }
 
+        jaspicCallbackHandler = callbackHandler;
         return callbackHandler;
     }
 
@@ -1305,7 +1318,7 @@ public abstract class AuthenticatorBase extends ValveBase
                 ServerAuthContext serverAuthContext;
                 try {
                     ServerAuthConfig serverAuthConfig = 
provider.getServerAuthConfig("HttpServlet",
-                            jaspicAppContextID, 
CallbackHandlerImpl.getInstance());
+                            jaspicAppContextID, getCallbackHandler());
                     String authContextID = 
serverAuthConfig.getAuthContextID(messageInfo);
                     serverAuthContext = 
serverAuthConfig.getAuthContext(authContextID, null, null);
                     serverAuthContext.cleanSubject(messageInfo, client);
diff --git 
a/java/org/apache/catalina/authenticator/jaspic/CallbackHandlerImpl.java 
b/java/org/apache/catalina/authenticator/jaspic/CallbackHandlerImpl.java
index dc539c7..59381a6 100644
--- a/java/org/apache/catalina/authenticator/jaspic/CallbackHandlerImpl.java
+++ b/java/org/apache/catalina/authenticator/jaspic/CallbackHandlerImpl.java
@@ -29,35 +29,24 @@ import 
javax.security.auth.callback.UnsupportedCallbackException;
 
 import jakarta.security.auth.message.callback.CallerPrincipalCallback;
 import jakarta.security.auth.message.callback.GroupPrincipalCallback;
+import jakarta.security.auth.message.callback.PasswordValidationCallback;
 
+import org.apache.catalina.Contained;
+import org.apache.catalina.Container;
 import org.apache.catalina.realm.GenericPrincipal;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.res.StringManager;
 
 /**
- * Implemented as a singleton since the class is stateless.
+ * Default implementation of a JASPIC CallbackHandler.
  */
-public class CallbackHandlerImpl implements CallbackHandler {
+public class CallbackHandlerImpl implements CallbackHandler, Contained {
 
     private static final StringManager sm = 
StringManager.getManager(CallbackHandlerImpl.class);
+    private final Log log = LogFactory.getLog(CallbackHandlerImpl.class); // 
must not be static
 
-    private static CallbackHandler instance;
-
-
-    static {
-        instance = new CallbackHandlerImpl();
-    }
-
-
-    public static CallbackHandler getInstance() {
-        return instance;
-    }
-
-
-    private  CallbackHandlerImpl() {
-        // Hide default constructor
-    }
+    private Container container;
 
 
     @Override
@@ -81,10 +70,19 @@ public class CallbackHandlerImpl implements CallbackHandler 
{
                 } else if (callback instanceof GroupPrincipalCallback) {
                     GroupPrincipalCallback gpc = (GroupPrincipalCallback) 
callback;
                     groups = gpc.getGroups();
+                } else if (callback instanceof PasswordValidationCallback) {
+                    if (container == null) {
+                        
log.warn(sm.getString("callbackHandlerImpl.containerMissing", 
callback.getClass().getName()));
+                    } else if (container.getRealm() == null) {
+                        
log.warn(sm.getString("callbackHandlerImpl.realmMissing",
+                                callback.getClass().getName(), 
container.getName()));
+                    } else {
+                        PasswordValidationCallback pvc = 
(PasswordValidationCallback) callback;
+                        principal = 
container.getRealm().authenticate(pvc.getUsername(),
+                                String.valueOf(pvc.getPassword()));
+                        subject = pvc.getSubject();
+                    }
                 } else {
-                    // This is a singleton so need to get correct Logger for
-                    // current TCCL
-                    Log log = LogFactory.getLog(CallbackHandlerImpl.class);
                     
log.error(sm.getString("callbackHandlerImpl.jaspicCallbackMissing",
                             callback.getClass().getName()));
                 }
@@ -119,4 +117,16 @@ public class CallbackHandlerImpl implements 
CallbackHandler {
 
         return new GenericPrincipal(name, roles, principal);
     }
+
+    // Contained interface methods
+    @Override
+    public Container getContainer() {
+        return this.container;
+    }
+
+
+    @Override
+    public void setContainer(Container container) {
+        this.container = container;
+    }
 }
diff --git 
a/java/org/apache/catalina/authenticator/jaspic/LocalStrings.properties 
b/java/org/apache/catalina/authenticator/jaspic/LocalStrings.properties
index 1f395a0..b86acb1 100644
--- a/java/org/apache/catalina/authenticator/jaspic/LocalStrings.properties
+++ b/java/org/apache/catalina/authenticator/jaspic/LocalStrings.properties
@@ -19,7 +19,9 @@ authConfigFactoryImpl.registerInstance=Registering instance 
of type[{0}] for lay
 authConfigFactoryImpl.zeroLengthAppContext=A zero length application context 
name is not valid
 authConfigFactoryImpl.zeroLengthMessageLayer=A zero length message layer name 
is not valid
 
+callbackHandlerImpl.containerMissing=Missing container for JASPIC callback of 
type [{0}] which was ignored
 callbackHandlerImpl.jaspicCallbackMissing=Unsupported JASPIC callback of type 
[{0}] received which was ignored
+callbackHandlerImpl.realmMissing=Missing realm for JASPIC callback of type 
[{0}] in container [{1}] which was ignored
 
 jaspicAuthenticator.authenticate=Authenticating request for [{0}] via JASPIC
 
diff --git 
a/test/org/apache/catalina/authenticator/TestJaspicCallbackHandlerInAuthenticator.java
 
b/test/org/apache/catalina/authenticator/TestJaspicCallbackHandlerInAuthenticator.java
index 0502ea4..b67320d 100644
--- 
a/test/org/apache/catalina/authenticator/TestJaspicCallbackHandlerInAuthenticator.java
+++ 
b/test/org/apache/catalina/authenticator/TestJaspicCallbackHandlerInAuthenticator.java
@@ -19,18 +19,31 @@ package org.apache.catalina.authenticator;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 
+import javax.security.auth.Subject;
 import javax.security.auth.callback.Callback;
 import javax.security.auth.callback.CallbackHandler;
 import javax.security.auth.callback.UnsupportedCallbackException;
 
+import jakarta.security.auth.message.callback.CallerPrincipalCallback;
+import jakarta.security.auth.message.callback.GroupPrincipalCallback;
+import jakarta.security.auth.message.callback.PasswordValidationCallback;
 import jakarta.servlet.http.HttpServletResponse;
 
 import org.junit.Assert;
 import org.junit.Test;
 
+import org.apache.catalina.Contained;
+import org.apache.catalina.Container;
 import org.apache.catalina.authenticator.jaspic.CallbackHandlerImpl;
 import org.apache.catalina.connector.Request;
+import org.apache.catalina.core.ContainerBase;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.catalina.realm.RealmBase;
 
 public class TestJaspicCallbackHandlerInAuthenticator {
 
@@ -40,26 +53,91 @@ public class TestJaspicCallbackHandlerInAuthenticator {
                 TestCallbackHandlerImpl.class);
     }
 
+
     @Test
     public void testDefaultCallbackHandlerCreation() throws Exception {
         testCallbackHandlerCreation(null, CallbackHandlerImpl.class);
     }
 
 
-    private void testCallbackHandlerCreation(String 
callbackHandlerImplClassName,
-            Class<?> callbackHandlerImplClass)
-            throws NoSuchMethodException, SecurityException, 
IllegalAccessException,
-            IllegalArgumentException, InvocationTargetException {
+    private void testCallbackHandlerCreation(String 
callbackHandlerImplClassName, Class<?> callbackHandlerImplClass)
+            throws NoSuchMethodException, SecurityException, 
IllegalAccessException, IllegalArgumentException,
+            InvocationTargetException {
+        CallbackHandler callbackHandler = 
createCallbackHandler(callbackHandlerImplClassName);
+        
Assert.assertTrue(callbackHandlerImplClass.isInstance(callbackHandler));
+    }
+
+
+    @Test
+    public void testCallerPrincipalCallback() throws Exception {
+        CallbackHandler callbackHandler = createCallbackHandler(null);
+        Subject clientSubject = new Subject();
+        CallerPrincipalCallback cpc1 = new 
CallerPrincipalCallback(clientSubject, "name1");
+        callbackHandler.handle(new Callback[] { cpc1 });
+        CallerPrincipalCallback cpc2 = new 
CallerPrincipalCallback(clientSubject, new Principal() {
+            @Override
+            public String getName() {
+                return "name2";
+            }
+        });
+        callbackHandler.handle(new Callback[] { cpc2 });
+        Set<Object> credentials = clientSubject.getPrivateCredentials();
+        Assert.assertTrue(credentials.size() == 2);
+        Set<String> names = new HashSet<>(Arrays.asList(new String[] { 
"name1", "name2" }));
+        for (Object o : credentials) {
+            names.remove(((GenericPrincipal) o).getName());
+        }
+        Assert.assertTrue(names.isEmpty());
+    }
+
+    @Test
+    public void testGroupPrincipalCallback() throws Exception {
+        CallbackHandler callbackHandler = createCallbackHandler(null);
+        Subject clientSubject = new Subject();
+        CallerPrincipalCallback cpc = new 
CallerPrincipalCallback(clientSubject, "name");
+        GroupPrincipalCallback gpc = new GroupPrincipalCallback(clientSubject,
+                new String[] { "group1", "group2" });
+        callbackHandler.handle(new Callback[] { cpc, gpc });
+        Set<Object> credentials = clientSubject.getPrivateCredentials();
+        Assert.assertTrue(credentials.size() == 1);
+        GenericPrincipal gp = (GenericPrincipal) credentials.iterator().next();
+        Assert.assertEquals("name", gp.getName());
+        Assert.assertTrue(gp.hasRole("group1"));
+        Assert.assertTrue(gp.hasRole("group2"));
+    }
+
+    @Test
+    public void testPasswordValidationCallback() throws Exception {
+        CallbackHandler callbackHandler = createCallbackHandler(null);
+        Container container = new TestContainer();
+        container.setRealm(new TestRealm());
+        ((Contained) callbackHandler).setContainer(container);
+        Subject clientSubject = new Subject();
+        PasswordValidationCallback pvc1 = new 
PasswordValidationCallback(clientSubject, "name1",
+                "password".toCharArray());
+        callbackHandler.handle(new Callback[] { pvc1 });
+        PasswordValidationCallback pvc2 = new 
PasswordValidationCallback(clientSubject, "name2",
+                "invalid".toCharArray());
+        callbackHandler.handle(new Callback[] { pvc2 });
+        Set<Object> credentials = clientSubject.getPrivateCredentials();
+        Assert.assertTrue(credentials.size() == 1);
+        GenericPrincipal gp = (GenericPrincipal) credentials.iterator().next();
+        Assert.assertEquals("name1", gp.getName());
+    }
+
+
+    private CallbackHandler createCallbackHandler(String 
callbackHandlerImplClassName) throws NoSuchMethodException,
+            SecurityException, IllegalAccessException, 
IllegalArgumentException, InvocationTargetException {
         TestAuthenticator authenticator = new TestAuthenticator();
-        
authenticator.setJaspicCallbackHandlerClass(callbackHandlerImplClassName);
-        Method createCallbackHandlerMethod =
-                
AuthenticatorBase.class.getDeclaredMethod("createCallbackHandler");
+        if (callbackHandlerImplClassName != null) {
+            
authenticator.setJaspicCallbackHandlerClass(callbackHandlerImplClassName);
+        }
+        Method createCallbackHandlerMethod = 
AuthenticatorBase.class.getDeclaredMethod("createCallbackHandler");
         createCallbackHandlerMethod.setAccessible(true);
-        CallbackHandler callbackHandler =
-                (CallbackHandler) 
createCallbackHandlerMethod.invoke(authenticator);
-        
Assert.assertTrue(callbackHandlerImplClass.isInstance(callbackHandler));
+        return (CallbackHandler) 
createCallbackHandlerMethod.invoke(authenticator);
     }
 
+
     private static class TestAuthenticator extends AuthenticatorBase {
 
         @Override
@@ -74,8 +152,39 @@ public class TestJaspicCallbackHandlerInAuthenticator {
         }
 
     }
+
+
+    private static class TestContainer extends ContainerBase {
+
+        @Override
+        protected String getObjectNameKeyProperties() {
+            return null;
+        }
+    }
+
+
+    private static class TestRealm extends RealmBase {
+
+        @Override
+        public Principal authenticate(String username, String password) {
+            if (getPassword(username).equals(password))
+                return getPrincipal(username);
+            return null;
+        }
+
+        @Override
+        protected String getPassword(String username) {
+            return "password";
+        }
+
+        @Override
+        protected Principal getPrincipal(String username) {
+            return new GenericPrincipal(username);
+        }
+    }
 }
 
+
 class TestCallbackHandlerImpl implements CallbackHandler {
 
     public TestCallbackHandlerImpl() {
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 5ed18f9..95b172a 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -62,6 +62,11 @@
         Correct numerous spellings throughout the code base. Based on a pull
         request from John Bampton. (markt)
       </fix>
+      <fix>
+        <bug>64715</bug>: Add PasswordValidationCallback to the Jakarta
+        Authentication implementation. Patch provided by Robert Rodewald.
+        (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to