diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASLoginService.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASLoginService.java index 35b1cfd1337d..45470de1e2c8 100644 --- a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASLoginService.java +++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASLoginService.java @@ -20,15 +20,16 @@ import java.io.IOException; import java.security.Principal; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; +import java.util.List; 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.NameCallback; -import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.Configuration; import javax.security.auth.login.FailedLoginException; @@ -37,9 +38,6 @@ import javax.servlet.ServletRequest; import org.eclipse.jetty.jaas.callback.DefaultCallbackHandler; -import org.eclipse.jetty.jaas.callback.ObjectCallback; -import org.eclipse.jetty.jaas.callback.RequestParameterCallback; -import org.eclipse.jetty.jaas.callback.ServletRequestCallback; import org.eclipse.jetty.security.DefaultIdentityService; import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.LoginService; @@ -47,7 +45,7 @@ import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.ArrayUtil; import org.eclipse.jetty.util.Loader; -import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -58,13 +56,14 @@ * Implementation of jetty's LoginService that works with JAAS for * authorization and authentication. */ -public class JAASLoginService extends AbstractLifeCycle implements LoginService +public class JAASLoginService extends ContainerLifeCycle implements LoginService { private static final Logger LOG = Log.getLogger(JAASLoginService.class); public static final String DEFAULT_ROLE_CLASS_NAME = "org.eclipse.jetty.jaas.JAASRole"; public static final String[] DEFAULT_ROLE_CLASS_NAMES = {DEFAULT_ROLE_CLASS_NAME}; - + public static final ThreadLocal INSTANCE = new ThreadLocal<>(); + protected String[] _roleClassNames = DEFAULT_ROLE_CLASS_NAMES; protected String _callbackHandlerClass; protected String _realmName; @@ -183,6 +182,7 @@ protected void doStart() throws Exception { if (_identityService == null) _identityService = new DefaultIdentityService(); + addBean(new PropertyUserStoreManager()); super.doStart(); } @@ -193,59 +193,27 @@ public UserIdentity login(final String username, final Object credentials, final { CallbackHandler callbackHandler = null; if (_callbackHandlerClass == null) - { - callbackHandler = new CallbackHandler() - { - @Override - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException - { - for (Callback callback : callbacks) - { - if (callback instanceof NameCallback) - { - ((NameCallback)callback).setName(username); - } - else if (callback instanceof PasswordCallback) - { - ((PasswordCallback)callback).setPassword(credentials.toString().toCharArray()); - } - else if (callback instanceof ObjectCallback) - { - ((ObjectCallback)callback).setObject(credentials); - } - else if (callback instanceof RequestParameterCallback) - { - RequestParameterCallback rpc = (RequestParameterCallback)callback; - if (request != null) - rpc.setParameterValues(Arrays.asList(request.getParameterValues(rpc.getParameterName()))); - } - else if (callback instanceof ServletRequestCallback) - { - ((ServletRequestCallback)callback).setRequest(request); - } - else - throw new UnsupportedCallbackException(callback); - } - } - }; - } + callbackHandler = new DefaultCallbackHandler(); else { Class clazz = Loader.loadClass(_callbackHandlerClass); callbackHandler = (CallbackHandler)clazz.getDeclaredConstructor().newInstance(); - if (DefaultCallbackHandler.class.isAssignableFrom(clazz)) - { - DefaultCallbackHandler dch = (DefaultCallbackHandler)callbackHandler; - if (request instanceof Request) - dch.setRequest((Request)request); - dch.setCredential(credentials); - dch.setUserName(username); - } + } + + if (callbackHandler instanceof DefaultCallbackHandler) + { + DefaultCallbackHandler dch = (DefaultCallbackHandler)callbackHandler; + if (request instanceof Request) + dch.setRequest((Request)request); + dch.setCredential(credentials); + dch.setUserName(username); } //set up the login context Subject subject = new Subject(); - LoginContext loginContext = (_configuration == null ? new LoginContext(_loginModuleName, subject, callbackHandler) + INSTANCE.set(this); + LoginContext loginContext = + (_configuration == null ? new LoginContext(_loginModuleName, subject, callbackHandler) : new LoginContext(_loginModuleName, subject, callbackHandler, _configuration)); loginContext.login(); @@ -265,6 +233,10 @@ else if (callback instanceof ServletRequestCallback) { LOG.ignore(e); } + finally + { + INSTANCE.remove(); + } return null; } @@ -306,52 +278,36 @@ public void logout(UserIdentity user) protected String[] getGroups(Subject subject) { Collection groups = new LinkedHashSet<>(); - Set principals = subject.getPrincipals(); - for (Principal principal : principals) + for (Principal principal : subject.getPrincipals()) { - Class c = principal.getClass(); - while (c != null) - { - if (roleClassNameMatches(c.getName())) - { - groups.add(principal.getName()); - break; - } - - boolean added = false; - for (Class ci : c.getInterfaces()) - { - if (roleClassNameMatches(ci.getName())) - { - groups.add(principal.getName()); - added = true; - break; - } - } - - if (!added) - { - c = c.getSuperclass(); - } - else - break; - } + if (isRoleClass(principal.getClass(), Arrays.asList(getRoleClassNames()))) + groups.add(principal.getName()); } return groups.toArray(new String[groups.size()]); } - - private boolean roleClassNameMatches(String classname) + + /** + * Check whether the class, its superclasses or any interfaces they implement + * is one of the classes that represents a role. + * + * @param clazz the class to check + * @param roleClassNames the list of classnames that represent roles + * @return true if the class is a role class + */ + private static boolean isRoleClass(Class clazz, List roleClassNames) { - boolean result = false; - for (String roleClassName : getRoleClassNames()) + Class c = clazz; + + //add the class, its interfaces and superclasses to the list to test + List classnames = new ArrayList<>(); + while (c != null) { - if (roleClassName.equals(classname)) - { - result = true; - break; - } + classnames.add(c.getName()); + Arrays.stream(c.getInterfaces()).map(Class::getName).forEach(classnames::add); + c = c.getSuperclass(); } - return result; + + return roleClassNames.stream().anyMatch(classnames::contains); } } diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/PropertyUserStoreManager.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/PropertyUserStoreManager.java new file mode 100644 index 000000000000..bf9f00e677bd --- /dev/null +++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/PropertyUserStoreManager.java @@ -0,0 +1,101 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.jaas; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.jetty.jaas.spi.PropertyFileLoginModule; +import org.eclipse.jetty.security.PropertyUserStore; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * PropertyUserStoreManager + * + * Maintains a map of PropertyUserStores, keyed off the location of the property file containing + * the authentication and authorization information. + * + * This class is used to enable the PropertyUserStores to be cached and shared. This is essential + * for the PropertyFileLoginModules, whose lifecycle is controlled by the JAAS api and instantiated + * afresh whenever a user needs to be authenticated. Without this class, every PropertyFileLoginModule + * instantiation would re-read and reload in all the user information just to authenticate a single user. + */ +public class PropertyUserStoreManager extends AbstractLifeCycle +{ + private static final Logger LOG = Log.getLogger(PropertyFileLoginModule.class); + + /** + * Map of user authentication and authorization information loaded in from a property file. + * The map is keyed off the location of the file. + */ + private Map _propertyUserStores; + + public PropertyUserStore getPropertyUserStore(String file) + { + synchronized (this) + { + if (_propertyUserStores == null) + return null; + + return _propertyUserStores.get(file); + } + } + + public PropertyUserStore addPropertyUserStore(String file, PropertyUserStore store) + { + synchronized (this) + { + Objects.requireNonNull(_propertyUserStores); + PropertyUserStore existing = _propertyUserStores.get(file); + if (existing != null) + return existing; + + _propertyUserStores.put(file, store); + return store; + } + } + + @Override + protected void doStart() throws Exception + { + _propertyUserStores = new HashMap(); + super.doStart(); + } + + @Override + protected void doStop() throws Exception + { + for (Map.Entry entry: _propertyUserStores.entrySet()) + { + try + { + entry.getValue().stop(); + } + catch (Exception e) + { + LOG.warn("Error stopping PropertyUserStore at {}", entry.getKey(), e); + } + } + _propertyUserStores = null; + super.doStop(); + } +} diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/DefaultCallbackHandler.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/DefaultCallbackHandler.java index 1b3523fcb4d7..c6316c077b1b 100644 --- a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/DefaultCallbackHandler.java +++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/DefaultCallbackHandler.java @@ -26,7 +26,6 @@ import javax.security.auth.callback.UnsupportedCallbackException; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.util.security.Password; /** * DefaultCallbackHandler @@ -47,38 +46,34 @@ public void setRequest(Request request) public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - for (int i = 0; i < callbacks.length; i++) + for (Callback callback : callbacks) { - if (callbacks[i] instanceof NameCallback) + if (callback instanceof NameCallback) { - ((NameCallback)callbacks[i]).setName(getUserName()); + ((NameCallback)callback).setName(getUserName()); } - else if (callbacks[i] instanceof ObjectCallback) + else if (callback instanceof ObjectCallback) { - ((ObjectCallback)callbacks[i]).setObject(getCredential()); + ((ObjectCallback)callback).setObject(getCredential()); } - else if (callbacks[i] instanceof PasswordCallback) + else if (callback instanceof PasswordCallback) { - if (getCredential() instanceof Password) - ((PasswordCallback)callbacks[i]).setPassword(getCredential().toString().toCharArray()); - else if (getCredential() instanceof String) - { - ((PasswordCallback)callbacks[i]).setPassword(((String)getCredential()).toCharArray()); - } - else - throw new UnsupportedCallbackException(callbacks[i], "User supplied credentials cannot be converted to char[] for PasswordCallback: try using an ObjectCallback instead"); + ((PasswordCallback)callback).setPassword(getCredential().toString().toCharArray()); } - else if (callbacks[i] instanceof RequestParameterCallback) + else if (callback instanceof RequestParameterCallback) { - RequestParameterCallback callback = (RequestParameterCallback)callbacks[i]; - callback.setParameterValues(Arrays.asList(_request.getParameterValues(callback.getParameterName()))); + if (_request != null) + { + RequestParameterCallback rpc = (RequestParameterCallback)callback; + rpc.setParameterValues(Arrays.asList(_request.getParameterValues(rpc.getParameterName()))); + } } - else if (callbacks[i] instanceof ServletRequestCallback) + else if (callback instanceof ServletRequestCallback) { - ((ServletRequestCallback)callbacks[i]).setRequest(_request); + ((ServletRequestCallback)callback).setRequest(_request); } else - throw new UnsupportedCallbackException(callbacks[i]); + throw new UnsupportedCallbackException(callback); } } } diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java index 79d59fd36263..47ab456c7d0c 100644 --- a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java +++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java @@ -21,11 +21,12 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; +import org.eclipse.jetty.jaas.JAASLoginService; +import org.eclipse.jetty.jaas.PropertyUserStoreManager; import org.eclipse.jetty.security.AbstractLoginService; import org.eclipse.jetty.security.PropertyUserStore; import org.eclipse.jetty.server.UserIdentity; @@ -41,14 +42,12 @@ public class PropertyFileLoginModule extends AbstractLoginModule public static final String DEFAULT_FILENAME = "realm.properties"; private static final Logger LOG = Log.getLogger(PropertyFileLoginModule.class); - - private static ConcurrentHashMap _propertyUserStores = new ConcurrentHashMap(); - - private int _refreshInterval = 0; - private String _filename = DEFAULT_FILENAME; + + private PropertyUserStore _store; /** - * Read contents of the configured property file. + * Use a PropertyUserStore to read the authentication and authorizaton information contained in + * the file named by the option "file". * * @param subject the subject * @param callbackHandler the callback handler @@ -64,40 +63,61 @@ public void initialize(Subject subject, CallbackHandler callbackHandler, Map options) { - parseConfig(options); + String filename = (String)options.get("file"); + filename = (filename == null ? DEFAULT_FILENAME : filename); - if (_propertyUserStores.get(_filename) == null) - { - PropertyUserStore propertyUserStore = new PropertyUserStore(); - propertyUserStore.setConfig(_filename); + PropertyUserStoreManager mgr = JAASLoginService.INSTANCE.get().getBean(PropertyUserStoreManager.class); + if (mgr == null) + throw new IllegalStateException("No PropertyUserStoreManager"); - PropertyUserStore prev = _propertyUserStores.putIfAbsent(_filename, propertyUserStore); - if (prev == null) + _store = mgr.getPropertyUserStore(filename); + if (_store == null) + { + boolean hotReload = false; + String tmp = (String)options.get("hotReload"); + if (tmp != null) + hotReload = Boolean.parseBoolean(tmp); + else { - LOG.debug("setupPropertyUserStore: Starting new PropertyUserStore. PropertiesFile: " + _filename + " refreshInterval: " + _refreshInterval); - - try - { - propertyUserStore.start(); - } - catch (Exception e) + //refreshInterval is deprecated, use hotReload instead + tmp = (String)options.get("refreshInterval"); + if (tmp != null) { - LOG.warn("Exception while starting propertyUserStore: ", e); + LOG.warn("Use 'hotReload' boolean property instead of 'refreshInterval'"); + try + { + hotReload = (Integer.parseInt(tmp) > 0); + } + catch (NumberFormatException e) + { + LOG.warn("'refreshInterval' is not an integer"); + } } } + PropertyUserStore newStore = new PropertyUserStore(); + newStore.setConfig(filename); + newStore.setHotReload(hotReload); + _store = mgr.addPropertyUserStore(filename, newStore); + try + { + _store.start(); + } + catch (Exception e) + { + LOG.warn("Exception starting propertyUserStore {} ", filename, e); + } } } - private void parseConfig(Map options) - { - String tmp = (String)options.get("file"); - _filename = (tmp == null ? DEFAULT_FILENAME : tmp); - tmp = (String)options.get("refreshInterval"); - _refreshInterval = (tmp == null ? _refreshInterval : Integer.parseInt(tmp)); - } - /** * @param userName the user name * @throws Exception if unable to get the user information @@ -105,12 +125,8 @@ private void parseConfig(Map options) @Override public UserInfo getUserInfo(String userName) throws Exception { - PropertyUserStore propertyUserStore = _propertyUserStores.get(_filename); - if (propertyUserStore == null) - throw new IllegalStateException("PropertyUserStore should never be null here!"); - - LOG.debug("Checking PropertyUserStore " + _filename + " for " + userName); - UserIdentity userIdentity = propertyUserStore.getUserIdentity(userName); + LOG.debug("Checking PropertyUserStore {} for {}", _store.getConfig(), userName); + UserIdentity userIdentity = _store.getUserIdentity(userName); if (userIdentity == null) return null; @@ -123,7 +139,6 @@ public UserInfo getUserInfo(String userName) throws Exception .collect(Collectors.toList()); Credential credential = (Credential)userIdentity.getSubject().getPrivateCredentials().iterator().next(); - LOG.debug("Found: " + userName + " in PropertyUserStore " + _filename); return new UserInfo(userName, credential, roles); } } diff --git a/jetty-jaas/src/test/java/org/eclipse/jetty/jaas/JAASLoginServiceTest.java b/jetty-jaas/src/test/java/org/eclipse/jetty/jaas/JAASLoginServiceTest.java index 52a45952bfdd..f62fa0070ed3 100644 --- a/jetty-jaas/src/test/java/org/eclipse/jetty/jaas/JAASLoginServiceTest.java +++ b/jetty-jaas/src/test/java/org/eclipse/jetty/jaas/JAASLoginServiceTest.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.jaas; import java.security.Principal; +import java.util.Arrays; import java.util.Collections; import javax.security.auth.Subject; import javax.security.auth.login.AppConfigurationEntry; @@ -29,25 +30,17 @@ import org.eclipse.jetty.server.Request; import org.junit.jupiter.api.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; /** * JAASLoginServiceTest */ public class JAASLoginServiceTest { - public static class TestConfiguration extends Configuration - { - AppConfigurationEntry _entry = new AppConfigurationEntry(TestLoginModule.class.getCanonicalName(), LoginModuleControlFlag.REQUIRED, Collections.emptyMap()); - - @Override - public AppConfigurationEntry[] getAppConfigurationEntry(String name) - { - return new AppConfigurationEntry[]{_entry}; - } - } - interface SomeRole { @@ -94,18 +87,31 @@ public String getName() @Test public void testServletRequestCallback() throws Exception { + Configuration config = new Configuration() + { + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) + { + return new AppConfigurationEntry[] { + new AppConfigurationEntry(TestLoginModule.class.getCanonicalName(), + LoginModuleControlFlag.REQUIRED, + Collections.emptyMap()) + }; + } + }; + //Test with the DefaultCallbackHandler JAASLoginService ls = new JAASLoginService("foo"); ls.setCallbackHandlerClass("org.eclipse.jetty.jaas.callback.DefaultCallbackHandler"); ls.setIdentityService(new DefaultIdentityService()); - ls.setConfiguration(new TestConfiguration()); + ls.setConfiguration(config); Request request = new Request(null, null); ls.login("aaardvaark", "aaa", request); //Test with the fallback CallbackHandler ls = new JAASLoginService("foo"); ls.setIdentityService(new DefaultIdentityService()); - ls.setConfiguration(new TestConfiguration()); + ls.setConfiguration(config); ls.login("aaardvaark", "aaa", request); } @@ -137,12 +143,8 @@ public void testLoginServiceRoles() throws Exception subject.getPrincipals().add(new AnotherTestRole("z")); String[] groups = ls.getGroups(subject); - assertEquals(3, groups.length); - for (String g : groups) - { - assertTrue(g.equals("x") || g.equals("y") || g.equals("z")); - } - + assertThat(Arrays.asList(groups), containsInAnyOrder("x", "y", "z")); + //test a custom role class ls.setRoleClassNames(new String[]{AnotherTestRole.class.getName()}); Subject subject2 = new Subject(); @@ -150,8 +152,9 @@ public void testLoginServiceRoles() throws Exception subject2.getPrincipals().add(new TestRole("x")); subject2.getPrincipals().add(new TestRole("y")); subject2.getPrincipals().add(new AnotherTestRole("z")); - assertEquals(1, ls.getGroups(subject2).length); - assertEquals("z", ls.getGroups(subject2)[0]); + String[] s2groups = ls.getGroups(subject2); + assertThat(s2groups, is(notNullValue())); + assertThat(Arrays.asList(s2groups), containsInAnyOrder("z")); //test a custom role class that implements an interface ls.setRoleClassNames(new String[]{SomeRole.class.getName()}); @@ -160,11 +163,9 @@ public void testLoginServiceRoles() throws Exception subject3.getPrincipals().add(new TestRole("x")); subject3.getPrincipals().add(new TestRole("y")); subject3.getPrincipals().add(new AnotherTestRole("z")); - assertEquals(3, ls.getGroups(subject3).length); - for (String g : groups) - { - assertTrue(g.equals("x") || g.equals("y") || g.equals("z")); - } + String[] s3groups = ls.getGroups(subject3); + assertThat(s3groups, is(notNullValue())); + assertThat(Arrays.asList(s3groups), containsInAnyOrder("x", "y", "z")); //test a class that doesn't match ls.setRoleClassNames(new String[]{NotTestRole.class.getName()}); diff --git a/jetty-jaas/src/test/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModuleTest.java b/jetty-jaas/src/test/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModuleTest.java index 49fad3f36dfd..529bc95b54d8 100644 --- a/jetty-jaas/src/test/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModuleTest.java +++ b/jetty-jaas/src/test/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModuleTest.java @@ -19,34 +19,72 @@ package org.eclipse.jetty.jaas.spi; import java.io.File; -import java.util.HashMap; -import javax.security.auth.Subject; +import java.util.Collections; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; +import javax.security.auth.login.Configuration; -import org.eclipse.jetty.jaas.callback.DefaultCallbackHandler; +import org.eclipse.jetty.jaas.JAASLoginService; +import org.eclipse.jetty.jaas.PropertyUserStoreManager; +import org.eclipse.jetty.security.DefaultIdentityService; +import org.eclipse.jetty.security.PropertyUserStore; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.not; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; public class PropertyFileLoginModuleTest { @Test - public void testRoles() - throws Exception + public void testPropertyFileLoginModule() throws Exception { - File file = MavenTestingUtils.getTestResourceFile("login.properties"); - PropertyFileLoginModule module = new PropertyFileLoginModule(); - Subject subject = new Subject(); - HashMap options = new HashMap<>(); - options.put("file", file.getCanonicalPath()); - module.initialize(subject, new DefaultCallbackHandler(), new HashMap(), options); - UserInfo fred = module.getUserInfo("fred"); - assertEquals("fred", fred.getUserName()); - assertThat(fred.getRoleNames(), containsInAnyOrder("role1", "role2", "role3")); - assertThat(fred.getRoleNames(), not(contains("fred"))); + //configure for PropertyFileLoginModule + File loginProperties = MavenTestingUtils.getTestResourceFile("login.properties"); + + Configuration testConfig = new Configuration() + { + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) + { + return new AppConfigurationEntry[]{new AppConfigurationEntry(PropertyFileLoginModule.class.getName(), + LoginModuleControlFlag.REQUIRED, + Collections.singletonMap("file", loginProperties.getAbsolutePath()))}; + } + }; + + JAASLoginService ls = new JAASLoginService("foo"); + ls.setCallbackHandlerClass("org.eclipse.jetty.jaas.callback.DefaultCallbackHandler"); + ls.setIdentityService(new DefaultIdentityService()); + ls.setConfiguration(testConfig); + ls.start(); + + //test that the manager is created when the JAASLoginService starts + PropertyUserStoreManager mgr = ls.getBean(PropertyUserStoreManager.class); + assertThat(mgr, notNullValue()); + + //test the PropertyFileLoginModule authentication and authorization + Request request = new Request(null, null); + UserIdentity uid = ls.login("fred", "pwd", request); + assertThat(uid.isUserInRole("role1", null), is(true)); + assertThat(uid.isUserInRole("role2", null), is(true)); + assertThat(uid.isUserInRole("role3", null), is(true)); + assertThat(uid.isUserInRole("role4", null), is(false)); + + //Test that the PropertyUserStore is created by the PropertyFileLoginModule + PropertyUserStore store = mgr.getPropertyUserStore(loginProperties.getAbsolutePath()); + assertThat(store, is(notNullValue())); + assertThat(store.isRunning(), is(true)); + assertThat(store.isHotReload(), is(false)); + + //test that the PropertyUserStoreManager is stopped and all PropertyUserStores stopped + ls.stop(); + assertThat(mgr.isStopped(), is(true)); + assertThat(mgr.getPropertyUserStore(loginProperties.getAbsolutePath()), is(nullValue())); + assertThat(store.isStopped(), is(true)); } }