| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| 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 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 { |
| |
| @Test |
| public void testCustomCallbackHandlerCreation() throws Exception { |
| testCallbackHandlerCreation("org.apache.catalina.authenticator.TesterCallbackHandlerImpl", |
| TesterCallbackHandlerImpl.class); |
| } |
| |
| |
| @Test |
| public void testDefaultCallbackHandlerCreation() throws Exception { |
| testCallbackHandlerCreation(null, CallbackHandlerImpl.class); |
| } |
| |
| |
| private void testCallbackHandlerCreation(String callbackHandlerImplClassName, Class<?> callbackHandlerImplClass) |
| throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, |
| InvocationTargetException { |
| CallbackHandler callbackHandler = createCallbackHandler(callbackHandlerImplClassName); |
| Assert.assertTrue(callbackHandlerImplClass.isInstance(callbackHandler)); |
| } |
| |
| |
| /* |
| * This shouldn't happen in real usage. In the very unlikely event there is more than one CallerPrincipalCallback, |
| * it should return the same Principal each time. |
| * |
| * If there are multiple CallerPrincipalCallbacks, Tomcat uses the name from the most recent. |
| */ |
| @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() == 1); |
| Set<String> names = new HashSet<>(Arrays.asList(new String[] { "name2" })); |
| for (Object o : credentials) { |
| names.remove(((GenericPrincipal) o).getName()); |
| } |
| Assert.assertTrue(names.isEmpty()); |
| } |
| |
| |
| /* |
| * This shouldn't happen in real usage. In the very unlikely event there is more than one GroupPrincipalCallback, |
| * the groups from each should be merged into the final Principal. |
| * |
| * If there are multiple CallerPrincipalCallbacks, Tomcat uses the name from the most recent. |
| */ |
| @Test |
| public void testGroupPrincipalCallback() throws Exception { |
| CallbackHandler callbackHandler = createCallbackHandler(null); |
| Subject clientSubject = new Subject(); |
| CallerPrincipalCallback cpc = new CallerPrincipalCallback(clientSubject, "name"); |
| GroupPrincipalCallback gpc1 = new GroupPrincipalCallback(clientSubject, new String[] { "group1", "group2" }); |
| callbackHandler.handle(new Callback[] { cpc, gpc1 }); |
| GroupPrincipalCallback gpc2 = new GroupPrincipalCallback(clientSubject, new String[] { "group3", "group4" }); |
| callbackHandler.handle(new Callback[] { cpc, gpc2 }); |
| Set<Object> credentials = clientSubject.getPrivateCredentials(); |
| Assert.assertTrue(credentials.size() == 1); |
| GenericPrincipal gp = (GenericPrincipal) credentials.iterator().next(); |
| Assert.assertEquals("name", gp.getName()); |
| Assert.assertEquals(4, gp.getRoles().length); |
| Assert.assertTrue(gp.hasRole("group1")); |
| Assert.assertTrue(gp.hasRole("group2")); |
| Assert.assertTrue(gp.hasRole("group3")); |
| Assert.assertTrue(gp.hasRole("group4")); |
| } |
| |
| |
| @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 }); |
| Assert.assertTrue(pvc1.getResult()); |
| PasswordValidationCallback pvc2 = |
| new PasswordValidationCallback(clientSubject, "name2", "invalid".toCharArray()); |
| callbackHandler.handle(new Callback[] { pvc2 }); |
| Assert.assertFalse(pvc2.getResult()); |
| 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(); |
| if (callbackHandlerImplClassName != null) { |
| authenticator.setJaspicCallbackHandlerClass(callbackHandlerImplClassName); |
| } |
| Method createCallbackHandlerMethod = AuthenticatorBase.class.getDeclaredMethod("createCallbackHandler"); |
| createCallbackHandlerMethod.setAccessible(true); |
| return (CallbackHandler) createCallbackHandlerMethod.invoke(authenticator); |
| } |
| |
| |
| private static class TestAuthenticator extends AuthenticatorBase { |
| |
| @Override |
| protected boolean doAuthenticate(Request request, HttpServletResponse response) throws IOException { |
| return false; |
| } |
| |
| @Override |
| protected String getAuthMethod() { |
| return null; |
| } |
| |
| } |
| |
| |
| 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); |
| } |
| } |
| } |