http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginProperty.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginProperty.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginProperty.java new file mode 100644 index 0000000..139d6c6 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginProperty.java @@ -0,0 +1,41 @@ +/* + * 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.activemq.artemis.spi.core.security.jaas; + +public class LDAPLoginProperty { + + private String name; + private String value; + + public LDAPLoginProperty(String name) { + this.name = name; + } + + public LDAPLoginProperty(String name, String value) { + this.name = name; + this.value = value; + } + + public String getPropertyName() { + return this.name; + } + + public String getPropertyValue() { + return this.value; + } + +}
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalProperties.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalProperties.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalProperties.java new file mode 100644 index 0000000..97e2355 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalProperties.java @@ -0,0 +1,75 @@ +/* + * 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.activemq.artemis.spi.core.security.jaas; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; + +class PrincipalProperties { + + private final Properties principals; + private final long reloadTime; + + PrincipalProperties(final String type, final File source, final ActiveMQServerLogger log) { + Properties props = new Properties(); + long reloadTime = 0; + try { + load(source, props); + reloadTime = System.currentTimeMillis(); + } + catch (IOException ioe) { + ioe.printStackTrace(); + log.warn("Unable to load " + type + " properties file " + source); + } + this.reloadTime = reloadTime; + this.principals = props; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + Set<Map.Entry<String, String>> entries() { + return (Set) principals.entrySet(); + } + + String getProperty(String name) { + return principals.getProperty(name); + } + + long getReloadTime() { + return reloadTime; + } + + private void load(final File source, Properties props) throws FileNotFoundException, IOException { + FileInputStream in = new FileInputStream(source); + try { + props.load(in); + } + finally { + in.close(); + } + } + + Properties getPrincipals() { + return principals; + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java new file mode 100644 index 0000000..6b96ed0 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java @@ -0,0 +1,215 @@ +/* + * 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.activemq.artemis.spi.core.security.jaas; + +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.FailedLoginException; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; +import java.io.File; +import java.io.IOException; +import java.security.Principal; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; + +public class PropertiesLoginModule implements LoginModule { + + private static final String USER_FILE = "org.apache.activemq.jaas.properties.user"; + private static final String GROUP_FILE = "org.apache.activemq.jaas.properties.role"; + + private Subject subject; + private CallbackHandler callbackHandler; + + private boolean debug; + private boolean reload = false; + private static volatile PrincipalProperties users; + private static volatile PrincipalProperties roles; + private String user; + private final Set<Principal> principals = new HashSet<Principal>(); + private File baseDir; + private boolean loginSucceeded; + // private boolean decrypt = true; + + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { + this.subject = subject; + this.callbackHandler = callbackHandler; + loginSucceeded = false; + + debug = "true".equalsIgnoreCase((String) options.get("debug")); + if (options.get("reload") != null) { + reload = "true".equalsIgnoreCase((String) options.get("reload")); + } + + if (options.get("baseDir") != null) { + baseDir = new File((String) options.get("baseDir")); + } + + setBaseDir(); + String usersFile = options.get(USER_FILE) + ""; + File uf = baseDir != null ? new File(baseDir, usersFile) : new File(usersFile); + + if (reload || users == null || uf.lastModified() > users.getReloadTime()) { + if (debug) { + ActiveMQServerLogger.LOGGER.debug("Reloading users from " + uf.getAbsolutePath()); + } + users = new PrincipalProperties("user", uf, ActiveMQServerLogger.LOGGER); + // if( decrypt ) { + // try { + // EncryptionSupport.decrypt(users.getPrincipals()); + // } catch(NoClassDefFoundError e) { + // // this Happens whe jasypt is not on the classpath.. + // decrypt = false; + // ActiveMQServerLogger.LOGGER.info("jasypt is not on the classpath: password decryption disabled."); + // } + // } + } + + String groupsFile = options.get(GROUP_FILE) + ""; + File gf = baseDir != null ? new File(baseDir, groupsFile) : new File(groupsFile); + if (reload || roles == null || gf.lastModified() > roles.getReloadTime()) { + if (debug) { + ActiveMQServerLogger.LOGGER.debug("Reloading roles from " + gf.getAbsolutePath()); + } + roles = new PrincipalProperties("role", gf, ActiveMQServerLogger.LOGGER); + } + } + + private void setBaseDir() { + if (baseDir == null) { + if (System.getProperty("java.security.auth.login.config") != null) { + baseDir = new File(System.getProperty("java.security.auth.login.config")).getParentFile(); + if (debug) { + ActiveMQServerLogger.LOGGER.debug("Using basedir=" + baseDir.getAbsolutePath()); + } + } + } + } + + @Override + public boolean login() throws LoginException { + Callback[] callbacks = new Callback[2]; + + callbacks[0] = new NameCallback("Username: "); + callbacks[1] = new PasswordCallback("Password: ", false); + try { + callbackHandler.handle(callbacks); + } + catch (IOException ioe) { + throw new LoginException(ioe.getMessage()); + } + catch (UnsupportedCallbackException uce) { + throw new LoginException(uce.getMessage() + " not available to obtain information from user"); + } + user = ((NameCallback) callbacks[0]).getName(); + char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword(); + if (tmpPassword == null) { + tmpPassword = new char[0]; + } + if (user == null) { + throw new FailedLoginException("user name is null"); + } + String password = users.getProperty(user); + + if (password == null) { + throw new FailedLoginException("User does exist"); + } + if (!password.equals(new String(tmpPassword))) { + throw new FailedLoginException("Password does not match"); + } + loginSucceeded = true; + + if (debug) { + ActiveMQServerLogger.LOGGER.debug("login " + user); + } + return loginSucceeded; + } + + @Override + public boolean commit() throws LoginException { + boolean result = loginSucceeded; + if (result) { + principals.add(new UserPrincipal(user)); + + for (Map.Entry<String, String> entry : roles.entries()) { + String name = entry.getKey(); + if (debug) { + ActiveMQServerLogger.LOGGER.debug("Inspecting role '" + name + "' with user(s): " + entry.getValue()); + } + String[] userList = entry.getValue().split(","); + for (int i = 0; i < userList.length; i++) { + if (user.equals(userList[i])) { + principals.add(new RolePrincipal(name)); + break; + } + } + } + + subject.getPrincipals().addAll(principals); + } + + // will whack loginSucceeded + clear(); + + if (debug) { + ActiveMQServerLogger.LOGGER.debug("commit, result: " + result); + } + return result; + } + + @Override + public boolean abort() throws LoginException { + clear(); + + if (debug) { + ActiveMQServerLogger.LOGGER.debug("abort"); + } + return true; + } + + @Override + public boolean logout() throws LoginException { + subject.getPrincipals().removeAll(principals); + principals.clear(); + clear(); + if (debug) { + ActiveMQServerLogger.LOGGER.debug("logout"); + } + return true; + } + + private void clear() { + user = null; + loginSucceeded = false; + } + + /** + * For test-usage only. + */ + public static void resetUsersAndGroupsCache() { + users = null; + roles = null; + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/RolePrincipal.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/RolePrincipal.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/RolePrincipal.java new file mode 100644 index 0000000..3e5afd1 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/RolePrincipal.java @@ -0,0 +1,68 @@ +/* + * 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.activemq.artemis.spi.core.security.jaas; + +import java.security.Principal; + +public class RolePrincipal implements Principal { + + private final String name; + private transient int hash; + + public RolePrincipal(String name) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null"); + } + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final RolePrincipal that = (RolePrincipal) o; + + if (!name.equals(that.name)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + if (hash == 0) { + hash = name.hashCode(); + } + return hash; + } + + @Override + public String toString() { + return name; + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/TextFileCertificateLoginModule.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/TextFileCertificateLoginModule.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/TextFileCertificateLoginModule.java new file mode 100644 index 0000000..77f5a43 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/TextFileCertificateLoginModule.java @@ -0,0 +1,147 @@ +/* + * 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.activemq.artemis.spi.core.security.jaas; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.LoginException; +import java.io.File; +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +/** + * A LoginModule allowing for SSL certificate based authentication based on + * Distinguished Names (DN) stored in text files. The DNs are parsed using a + * Properties class where each line is <user_name>=<user_DN>. This class also + * uses a group definition file where each line is <group_name>=<user_name_1>,<user_name_2>,etc. + * The user and group files' locations must be specified in the + * org.apache.activemq.jaas.textfiledn.user and + * org.apache.activemq.jaas.textfiledn.user properties respectively. NOTE: This + * class will re-read user and group files for every authentication (i.e it does + * live updates of allowed groups and users). + */ +public class TextFileCertificateLoginModule extends CertificateLoginModule { + + private static final String USER_FILE = "org.apache.activemq.jaas.textfiledn.user"; + private static final String GROUP_FILE = "org.apache.activemq.jaas.textfiledn.group"; + + private File baseDir; + private String usersFilePathname; + private String groupsFilePathname; + + /** + * Performs initialization of file paths. A standard JAAS override. + */ + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { + super.initialize(subject, callbackHandler, sharedState, options); + if (System.getProperty("java.security.auth.login.config") != null) { + baseDir = new File(System.getProperty("java.security.auth.login.config")).getParentFile(); + } + else { + baseDir = new File("."); + } + + usersFilePathname = (String) options.get(USER_FILE) + ""; + groupsFilePathname = (String) options.get(GROUP_FILE) + ""; + } + + /** + * Overriding to allow DN authorization based on DNs specified in text + * files. + * + * @param certs The certificate the incoming connection provided. + * @return The user's authenticated name or null if unable to authenticate + * the user. + * @throws LoginException Thrown if unable to find user file or connection + * certificate. + */ + @Override + protected String getUserNameForCertificates(final X509Certificate[] certs) throws LoginException { + if (certs == null) { + throw new LoginException("Client certificates not found. Cannot authenticate."); + } + + File usersFile = new File(baseDir, usersFilePathname); + + Properties users = new Properties(); + + try { + java.io.FileInputStream in = new java.io.FileInputStream(usersFile); + users.load(in); + in.close(); + } + catch (IOException ioe) { + throw new LoginException("Unable to load user properties file " + usersFile); + } + + String dn = getDistinguishedName(certs); + + Enumeration<Object> keys = users.keys(); + for (Enumeration<Object> vals = users.elements(); vals.hasMoreElements(); ) { + if (((String) vals.nextElement()).equals(dn)) { + return (String) keys.nextElement(); + } + else { + keys.nextElement(); + } + } + + return null; + } + + /** + * Overriding to allow for group discovery based on text files. + * + * @param username The name of the user being examined. This is the same + * name returned by getUserNameForCertificates. + * @return A Set of name Strings for groups this user belongs to. + * @throws LoginException Thrown if unable to find group definition file. + */ + @Override + protected Set<String> getUserGroups(String username) throws LoginException { + File groupsFile = new File(baseDir, groupsFilePathname); + + Properties groups = new Properties(); + try { + java.io.FileInputStream in = new java.io.FileInputStream(groupsFile); + groups.load(in); + in.close(); + } + catch (IOException ioe) { + throw new LoginException("Unable to load group properties file " + groupsFile); + } + Set<String> userGroups = new HashSet<String>(); + for (Enumeration<Object> enumeration = groups.keys(); enumeration.hasMoreElements(); ) { + String groupName = (String) enumeration.nextElement(); + String[] userList = (groups.getProperty(groupName) + "").split(","); + for (int i = 0; i < userList.length; i++) { + if (username.equals(userList[i])) { + userGroups.add(groupName); + break; + } + } + } + + return userGroups; + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/UserPrincipal.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/UserPrincipal.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/UserPrincipal.java new file mode 100644 index 0000000..9e20f06 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/UserPrincipal.java @@ -0,0 +1,68 @@ +/* + * 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.activemq.artemis.spi.core.security.jaas; + +import java.security.Principal; + +public class UserPrincipal implements Principal { + + private final String name; + private transient int hash; + + public UserPrincipal(String name) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null"); + } + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final UserPrincipal that = (UserPrincipal) o; + + if (!name.equals(that.name)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + if (hash == 0) { + hash = name.hashCode(); + } + return hash; + } + + @Override + public String toString() { + return name; + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/CertificateLoginModuleTest.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/CertificateLoginModuleTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/CertificateLoginModuleTest.java new file mode 100644 index 0000000..9b3c33d --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/CertificateLoginModuleTest.java @@ -0,0 +1,154 @@ +/* + * 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.activemq.artemis.core.security.jaas; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginException; +import java.io.IOException; +import java.security.Principal; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.Vector; + +import org.apache.activemq.artemis.spi.core.security.jaas.JaasCertificateCallbackHandler; +import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal; +import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class CertificateLoginModuleTest extends Assert { + + private static final String USER_NAME = "testUser"; + private static final List<String> GROUP_NAMES = new Vector<String>(); + + private StubCertificateLoginModule loginModule; + + private Subject subject; + + public CertificateLoginModuleTest() { + GROUP_NAMES.add("testGroup1"); + GROUP_NAMES.add("testGroup2"); + GROUP_NAMES.add("testGroup3"); + GROUP_NAMES.add("testGroup4"); + } + + @Before + public void setUp() throws Exception { + subject = new Subject(); + } + + private void loginWithCredentials(String userName, Set<String> groupNames) throws LoginException { + loginModule = new StubCertificateLoginModule(userName, new HashSet<String>(groupNames)); + JaasCertificateCallbackHandler callbackHandler = new JaasCertificateCallbackHandler(null); + + loginModule.initialize(subject, callbackHandler, null, new HashMap()); + + loginModule.login(); + loginModule.commit(); + } + + private void checkPrincipalsMatch(Subject subject) { + boolean nameFound = false; + boolean[] groupsFound = new boolean[GROUP_NAMES.size()]; + for (int i = 0; i < groupsFound.length; ++i) { + groupsFound[i] = false; + } + + for (Iterator iter = subject.getPrincipals().iterator(); iter.hasNext(); ) { + Principal currentPrincipal = (Principal) iter.next(); + + if (currentPrincipal instanceof UserPrincipal) { + if (currentPrincipal.getName().equals(USER_NAME)) { + if (!nameFound) { + nameFound = true; + } + else { + fail("UserPrincipal found twice."); + } + + } + else { + fail("Unknown UserPrincipal found."); + } + + } + else if (currentPrincipal instanceof RolePrincipal) { + int principalIdx = GROUP_NAMES.indexOf(((RolePrincipal) currentPrincipal).getName()); + + if (principalIdx < 0) { + fail("Unknown GroupPrincipal found."); + } + + if (!groupsFound[principalIdx]) { + groupsFound[principalIdx] = true; + } + else { + fail("GroupPrincipal found twice."); + } + } + else { + fail("Unknown Principal type found."); + } + } + } + + @Test + public void testLoginSuccess() throws IOException { + try { + loginWithCredentials(USER_NAME, new HashSet<String>(GROUP_NAMES)); + } + catch (Exception e) { + fail("Unable to login: " + e.getMessage()); + } + + checkPrincipalsMatch(subject); + } + + @Test + public void testLoginFailure() throws IOException { + boolean loginFailed = false; + + try { + loginWithCredentials(null, new HashSet<String>()); + } + catch (LoginException e) { + loginFailed = true; + } + + if (!loginFailed) { + fail("Logged in with unknown certificate."); + } + } + + @Test + public void testLogOut() throws IOException { + try { + loginWithCredentials(USER_NAME, new HashSet<String>(GROUP_NAMES)); + } + catch (Exception e) { + fail("Unable to login: " + e.getMessage()); + } + + loginModule.logout(); + + assertEquals("logout should have cleared Subject principals.", 0, subject.getPrincipals().size()); + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/GuestLoginModuleTest.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/GuestLoginModuleTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/GuestLoginModuleTest.java new file mode 100644 index 0000000..54e743a --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/GuestLoginModuleTest.java @@ -0,0 +1,91 @@ +/* + * 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.activemq.artemis.core.security.jaas; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import java.io.IOException; +import java.net.URL; + +import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal; +import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal; +import org.junit.Assert; +import org.junit.Test; + +public class GuestLoginModuleTest extends Assert { + + static { + String path = System.getProperty("java.security.auth.login.config"); + if (path == null) { + URL resource = GuestLoginModuleTest.class.getClassLoader().getResource("login.config"); + if (resource != null) { + path = resource.getFile(); + System.setProperty("java.security.auth.login.config", path); + } + } + } + + @Test + public void testLogin() throws LoginException { + LoginContext context = new LoginContext("GuestLogin", new CallbackHandler() { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + assertEquals("Should have no Callbacks", 0, callbacks.length); + } + }); + context.login(); + + Subject subject = context.getSubject(); + + assertEquals("Should have two principals", 2, subject.getPrincipals().size()); + assertEquals("Should have one user principal", 1, subject.getPrincipals(UserPrincipal.class).size()); + assertTrue("User principal is 'foo'", subject.getPrincipals(UserPrincipal.class).contains(new UserPrincipal("foo"))); + + assertEquals("Should have one group principal", 1, subject.getPrincipals(RolePrincipal.class).size()); + assertTrue("Role principal is 'bar'", subject.getPrincipals(RolePrincipal.class).contains(new RolePrincipal("bar"))); + + context.logout(); + + assertEquals("Should have zero principals", 0, subject.getPrincipals().size()); + } + + @Test + public void testLoginWithDefaults() throws LoginException { + LoginContext context = new LoginContext("GuestLoginWithDefaults", new CallbackHandler() { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + assertEquals("Should have no Callbacks", 0, callbacks.length); + } + }); + context.login(); + + Subject subject = context.getSubject(); + + assertEquals("Should have two principals", 2, subject.getPrincipals().size()); + assertEquals("Should have one user principal", 1, subject.getPrincipals(UserPrincipal.class).size()); + assertTrue("User principal is 'guest'", subject.getPrincipals(UserPrincipal.class).contains(new UserPrincipal("guest"))); + + assertEquals("Should have one group principal", 1, subject.getPrincipals(RolePrincipal.class).size()); + assertTrue("Role principal is 'guests'", subject.getPrincipals(RolePrincipal.class).contains(new RolePrincipal("guests"))); + + context.logout(); + + assertEquals("Should have zero principals", 0, subject.getPrincipals().size()); + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/LDAPLoginModuleTest.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/LDAPLoginModuleTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/LDAPLoginModuleTest.java new file mode 100644 index 0000000..f9bcbef --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/LDAPLoginModuleTest.java @@ -0,0 +1,149 @@ +/* + * 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.activemq.artemis.core.security.jaas; + +import javax.naming.Context; +import javax.naming.NameClassPair; +import javax.naming.NamingEnumeration; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +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.LoginContext; +import javax.security.auth.login.LoginException; +import java.io.IOException; +import java.util.HashSet; +import java.util.Hashtable; + +import org.apache.directory.server.annotations.CreateLdapServer; +import org.apache.directory.server.annotations.CreateTransport; +import org.apache.directory.server.core.annotations.ApplyLdifFiles; +import org.apache.directory.server.core.integ.AbstractLdapTestUnit; +import org.apache.directory.server.core.integ.FrameworkRunner; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(FrameworkRunner.class) +@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP", port = 1024)}) +@ApplyLdifFiles("test.ldif") +public class LDAPLoginModuleTest extends AbstractLdapTestUnit { + + private static final String PRINCIPAL = "uid=admin,ou=system"; + private static final String CREDENTIALS = "secret"; + + private final String loginConfigSysPropName = "java.security.auth.login.config"; + private String oldLoginConfig; + + @Before + public void setLoginConfigSysProperty() { + oldLoginConfig = System.getProperty(loginConfigSysPropName, null); + System.setProperty(loginConfigSysPropName, "src/test/resources/login.config"); + } + + @After + public void resetLoginConfigSysProperty() { + if (oldLoginConfig != null) { + System.setProperty(loginConfigSysPropName, oldLoginConfig); + } + } + + @SuppressWarnings("unchecked") + @Test + public void testRunning() throws Exception { + + Hashtable env = new Hashtable(); + env.put(Context.PROVIDER_URL, "ldap://localhost:1024"); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, PRINCIPAL); + env.put(Context.SECURITY_CREDENTIALS, CREDENTIALS); + DirContext ctx = new InitialDirContext(env); + + HashSet set = new HashSet(); + + NamingEnumeration list = ctx.list("ou=system"); + + while (list.hasMore()) { + NameClassPair ncp = (NameClassPair) list.next(); + set.add(ncp.getName()); + } + + assertTrue(set.contains("uid=admin")); + assertTrue(set.contains("ou=users")); + assertTrue(set.contains("ou=groups")); + assertTrue(set.contains("ou=configuration")); + assertTrue(set.contains("prefNodeName=sysPrefRoot")); + + } + + @Test + public void testLogin() throws LoginException { + LoginContext context = new LoginContext("LDAPLogin", new CallbackHandler() { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof NameCallback) { + ((NameCallback) callbacks[i]).setName("first"); + } + else if (callbacks[i] instanceof PasswordCallback) { + ((PasswordCallback) callbacks[i]).setPassword("secret".toCharArray()); + } + else { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + }); + context.login(); + context.logout(); + } + + @Test + public void testUnauthenticated() throws LoginException { + LoginContext context = new LoginContext("UnAuthenticatedLDAPLogin", new CallbackHandler() { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof NameCallback) { + ((NameCallback) callbacks[i]).setName("first"); + } + else if (callbacks[i] instanceof PasswordCallback) { + ((PasswordCallback) callbacks[i]).setPassword("secret".toCharArray()); + } + else { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + }); + try { + context.login(); + } + catch (LoginException le) { + assertEquals(le.getCause().getMessage(), "Empty password is not allowed"); + return; + } + fail("Should have failed authenticating"); + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/LDAPModuleRoleExpansionTest.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/LDAPModuleRoleExpansionTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/LDAPModuleRoleExpansionTest.java new file mode 100644 index 0000000..b63f89d --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/LDAPModuleRoleExpansionTest.java @@ -0,0 +1,136 @@ +/* + * 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.activemq.artemis.core.security.jaas; + +import javax.naming.Context; +import javax.naming.NameClassPair; +import javax.naming.NamingEnumeration; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +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.LoginContext; +import javax.security.auth.login.LoginException; +import java.io.IOException; +import java.security.Principal; +import java.util.HashSet; +import java.util.Hashtable; + +import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal; +import org.apache.directory.server.annotations.CreateLdapServer; +import org.apache.directory.server.annotations.CreateTransport; +import org.apache.directory.server.core.annotations.ApplyLdifFiles; +import org.apache.directory.server.core.integ.AbstractLdapTestUnit; +import org.apache.directory.server.core.integ.FrameworkRunner; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertTrue; + +@RunWith(FrameworkRunner.class) +@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP", port = 1024)}) +@ApplyLdifFiles("test.ldif") +public class LDAPModuleRoleExpansionTest extends AbstractLdapTestUnit { + + private static final String PRINCIPAL = "uid=admin,ou=system"; + private static final String CREDENTIALS = "secret"; + private final String loginConfigSysPropName = "java.security.auth.login.config"; + private String oldLoginConfig; + + @Before + public void setLoginConfigSysProperty() { + oldLoginConfig = System.getProperty(loginConfigSysPropName, null); + System.setProperty(loginConfigSysPropName, "src/test/resources/login.config"); + } + + @After + public void resetLoginConfigSysProperty() { + if (oldLoginConfig != null) { + System.setProperty(loginConfigSysPropName, oldLoginConfig); + } + } + + @SuppressWarnings("unchecked") + @Test + public void testRunning() throws Exception { + + Hashtable env = new Hashtable(); + env.put(Context.PROVIDER_URL, "ldap://localhost:1024"); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, PRINCIPAL); + env.put(Context.SECURITY_CREDENTIALS, CREDENTIALS); + DirContext ctx = new InitialDirContext(env); + + HashSet set = new HashSet(); + + NamingEnumeration list = ctx.list("ou=system"); + + while (list.hasMore()) { + NameClassPair ncp = (NameClassPair) list.next(); + set.add(ncp.getName()); + } + + assertTrue(set.contains("uid=admin")); + assertTrue(set.contains("ou=users")); + assertTrue(set.contains("ou=groups")); + assertTrue(set.contains("ou=configuration")); + assertTrue(set.contains("prefNodeName=sysPrefRoot")); + + } + + @Test + public void testRoleExpansion() throws LoginException { + LoginContext context = new LoginContext("ExpandedLDAPLogin", new CallbackHandler() { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof NameCallback) { + ((NameCallback) callbacks[i]).setName("first"); + } + else if (callbacks[i] instanceof PasswordCallback) { + ((PasswordCallback) callbacks[i]).setPassword("secret".toCharArray()); + } + else { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + }); + context.login(); + Subject subject = context.getSubject(); + boolean isAdmin = false; + boolean isUser = false; + for (Principal principal : subject.getPrincipals()) { + if (principal instanceof RolePrincipal) { + RolePrincipal groupPrincipal = (RolePrincipal) principal; + if (groupPrincipal.getName().equalsIgnoreCase("admins")) + isAdmin = true; + if (groupPrincipal.getName().equalsIgnoreCase("users")) + isUser = true; + } + } + // Should be in users by virtue of being in admins + assertTrue(isAdmin && isUser); + context.logout(); + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleRaceConditionTest.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleRaceConditionTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleRaceConditionTest.java new file mode 100644 index 0000000..de72563 --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleRaceConditionTest.java @@ -0,0 +1,195 @@ +/* + * 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.activemq.artemis.core.security.jaas; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.activemq.artemis.spi.core.security.jaas.JaasCredentialCallbackHandler; +import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; +import org.junit.rules.TemporaryFolder; +import org.junit.rules.TestName; + +import static org.junit.Assert.assertTrue; + +public class PropertiesLoginModuleRaceConditionTest { + + private static final String GROUPS_FILE = "roles.properties"; + private static final String USERS_FILE = "users.properties"; + private static final String USERNAME = "first"; + private static final String PASSWORD = "secret"; + + @Rule + public final ErrorCollector e = new ErrorCollector(); + + @Rule + public final TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public final TestName name = new TestName(); + + private Map<String, String> options; + private BlockingQueue<Exception> errors; + private ExecutorService pool; + private CallbackHandler callback; + + private static class LoginTester implements Runnable { + + private final CountDownLatch finished; + private final BlockingQueue<Exception> errors; + private final Map<String, String> options; + private final CountDownLatch start; + private final CallbackHandler callback; + + LoginTester(CountDownLatch start, + CountDownLatch finished, + BlockingQueue<Exception> errors, + Map<String, String> options, + CallbackHandler callbackHandler) { + this.finished = finished; + this.errors = errors; + this.options = options; + this.start = start; + this.callback = callbackHandler; + } + + @Override + public void run() { + try { + start.await(); + + Subject subject = new Subject(); + PropertiesLoginModule module = new PropertiesLoginModule(); + module.initialize(subject, callback, new HashMap<Object, Object>(), options); + module.login(); + module.commit(); + } + catch (Exception e) { + errors.offer(e); + } + finally { + finished.countDown(); + } + } + } + + @Before + public void before() throws FileNotFoundException, IOException { + createUsers(); + createGroups(); + + options = new HashMap<String, String>(); + options.put("reload", "true"); // Used to simplify reproduction of the + // race condition + options.put("org.apache.activemq.jaas.properties.user", USERS_FILE); + options.put("org.apache.activemq.jaas.properties.role", GROUPS_FILE); + options.put("baseDir", temp.getRoot().getAbsolutePath()); + + errors = new ArrayBlockingQueue<Exception>(processorCount()); + pool = Executors.newFixedThreadPool(processorCount()); + callback = new JaasCredentialCallbackHandler(USERNAME, PASSWORD); + } + + @After + public void after() throws InterruptedException { + pool.shutdown(); + assertTrue(pool.awaitTermination(500, TimeUnit.SECONDS)); + PropertiesLoginModule.resetUsersAndGroupsCache(); + } + + @Test + public void raceConditionInUsersAndGroupsLoading() throws InterruptedException, FileNotFoundException, IOException { + + // Brute force approach to increase the likelihood of the race condition occurring + for (int i = 0; i < 25000; i++) { + final CountDownLatch start = new CountDownLatch(1); + final CountDownLatch finished = new CountDownLatch(processorCount()); + prepareLoginThreads(start, finished); + + // Releases every login thread simultaneously to increase our chances of + // encountering the race condition + start.countDown(); + + finished.await(); + if (isRaceConditionDetected()) { + e.addError(new AssertionError("At least one race condition in PropertiesLoginModule " + "has been encountered. Please examine the " + "following stack traces for more details:")); + for (Exception exception : errors) { + e.addError(exception); + } + return; + } + } + } + + private boolean isRaceConditionDetected() { + return errors.size() > 0; + } + + private void prepareLoginThreads(final CountDownLatch start, final CountDownLatch finished) { + for (int processor = 1; processor <= processorCount() * 2; processor++) { + pool.submit(new LoginTester(start, finished, errors, options, callback)); + } + } + + private int processorCount() { + return Runtime.getRuntime().availableProcessors(); + } + + private void store(Properties from, File to) throws FileNotFoundException, IOException { + FileOutputStream output = new FileOutputStream(to); + try { + from.store(output, "Generated by " + name.getMethodName()); + } + finally { + output.close(); + } + } + + private void createGroups() throws FileNotFoundException, IOException { + Properties groups = new Properties(); + for (int i = 0; i < 100; i++) { + groups.put("group" + i, "first,second,third"); + } + store(groups, temp.newFile(GROUPS_FILE)); + } + + private void createUsers() throws FileNotFoundException, IOException { + Properties users = new Properties(); + users.put(USERNAME, PASSWORD); + users.put("second", PASSWORD); + users.put("third", PASSWORD); + store(users, temp.newFile(USERS_FILE)); + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleTest.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleTest.java new file mode 100644 index 0000000..5aec37f --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleTest.java @@ -0,0 +1,130 @@ +/* + * 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.activemq.artemis.core.security.jaas; + +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.FailedLoginException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import java.io.IOException; +import java.net.URL; + +import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal; +import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal; +import org.junit.Assert; +import org.junit.Test; + +public class PropertiesLoginModuleTest extends Assert { + + static { + String path = System.getProperty("java.security.auth.login.config"); + if (path == null) { + URL resource = PropertiesLoginModuleTest.class.getClassLoader().getResource("login.config"); + if (resource != null) { + path = resource.getFile(); + System.setProperty("java.security.auth.login.config", path); + } + } + } + + @Test + public void testLogin() throws LoginException { + LoginContext context = new LoginContext("PropertiesLogin", new CallbackHandler() { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof NameCallback) { + ((NameCallback) callbacks[i]).setName("first"); + } + else if (callbacks[i] instanceof PasswordCallback) { + ((PasswordCallback) callbacks[i]).setPassword("secret".toCharArray()); + } + else { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + }); + context.login(); + + Subject subject = context.getSubject(); + + assertEquals("Should have three principals", 3, subject.getPrincipals().size()); + assertEquals("Should have one user principal", 1, subject.getPrincipals(UserPrincipal.class).size()); + assertEquals("Should have two group principals", 2, subject.getPrincipals(RolePrincipal.class).size()); + + context.logout(); + + assertEquals("Should have zero principals", 0, subject.getPrincipals().size()); + } + + @Test + public void testBadUseridLogin() throws Exception { + LoginContext context = new LoginContext("PropertiesLogin", new CallbackHandler() { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof NameCallback) { + ((NameCallback) callbacks[i]).setName("BAD"); + } + else if (callbacks[i] instanceof PasswordCallback) { + ((PasswordCallback) callbacks[i]).setPassword("secret".toCharArray()); + } + else { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + }); + try { + context.login(); + fail("Should have thrown a FailedLoginException"); + } + catch (FailedLoginException doNothing) { + } + + } + + @Test + public void testBadPWLogin() throws Exception { + LoginContext context = new LoginContext("PropertiesLogin", new CallbackHandler() { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof NameCallback) { + ((NameCallback) callbacks[i]).setName("first"); + } + else if (callbacks[i] instanceof PasswordCallback) { + ((PasswordCallback) callbacks[i]).setPassword("BAD".toCharArray()); + } + else { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + }); + try { + context.login(); + fail("Should have thrown a FailedLoginException"); + } + catch (FailedLoginException doNothing) { + } + + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/RolePrincipalTest.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/RolePrincipalTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/RolePrincipalTest.java new file mode 100644 index 0000000..e23fe6a --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/RolePrincipalTest.java @@ -0,0 +1,61 @@ +/* + * 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.activemq.artemis.core.security.jaas; + +import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal; +import org.junit.Assert; +import org.junit.Test; + +public class RolePrincipalTest extends Assert { + + @Test + public void testArguments() { + RolePrincipal principal = new RolePrincipal("FOO"); + + assertEquals("FOO", principal.getName()); + + try { + new RolePrincipal(null); + fail("Should have thrown IllegalArgumentException"); + } + catch (IllegalArgumentException ingore) { + + } + } + + @Test + public void testHash() { + RolePrincipal p1 = new RolePrincipal("FOO"); + RolePrincipal p2 = new RolePrincipal("FOO"); + + assertEquals(p1.hashCode(), p1.hashCode()); + assertEquals(p1.hashCode(), p2.hashCode()); + } + + @Test + public void testEquals() { + RolePrincipal p1 = new RolePrincipal("FOO"); + RolePrincipal p2 = new RolePrincipal("FOO"); + RolePrincipal p3 = new RolePrincipal("BAR"); + + assertTrue(p1.equals(p1)); + assertTrue(p1.equals(p2)); + assertFalse(p1.equals(null)); + assertFalse(p1.equals("FOO")); + assertFalse(p1.equals(p3)); + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/StubCertificateLoginModule.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/StubCertificateLoginModule.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/StubCertificateLoginModule.java new file mode 100644 index 0000000..6c7bf24 --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/StubCertificateLoginModule.java @@ -0,0 +1,47 @@ +/* + * 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.activemq.artemis.core.security.jaas; + +import javax.security.auth.login.LoginException; +import java.security.cert.X509Certificate; +import java.util.Set; + +import org.apache.activemq.artemis.spi.core.security.jaas.CertificateLoginModule; + +public class StubCertificateLoginModule extends CertificateLoginModule { + + final String userName; + final Set groupNames; + + String lastUserName; + X509Certificate[] lastCertChain; + + public StubCertificateLoginModule(String userName, Set groupNames) { + this.userName = userName; + this.groupNames = groupNames; + } + + protected String getUserNameForCertificates(X509Certificate[] certs) throws LoginException { + lastCertChain = certs; + return userName; + } + + protected Set getUserGroups(String username) throws LoginException { + lastUserName = username; + return this.groupNames; + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/UserPrincipalTest.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/UserPrincipalTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/UserPrincipalTest.java new file mode 100644 index 0000000..9a08141 --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/UserPrincipalTest.java @@ -0,0 +1,61 @@ +/* + * 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.activemq.artemis.core.security.jaas; + +import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal; +import org.junit.Assert; +import org.junit.Test; + +public class UserPrincipalTest extends Assert { + + @Test + public void testArguments() { + UserPrincipal principal = new UserPrincipal("FOO"); + + assertEquals("FOO", principal.getName()); + + try { + new UserPrincipal(null); + fail("Should have thrown IllegalArgumentException"); + } + catch (IllegalArgumentException ingore) { + + } + } + + @Test + public void testHash() { + UserPrincipal p1 = new UserPrincipal("FOO"); + UserPrincipal p2 = new UserPrincipal("FOO"); + + assertEquals(p1.hashCode(), p1.hashCode()); + assertEquals(p1.hashCode(), p2.hashCode()); + } + + @Test + public void testEquals() { + UserPrincipal p1 = new UserPrincipal("FOO"); + UserPrincipal p2 = new UserPrincipal("FOO"); + UserPrincipal p3 = new UserPrincipal("BAR"); + + assertTrue(p1.equals(p1)); + assertTrue(p1.equals(p2)); + assertFalse(p1.equals(null)); + assertFalse(p1.equals("FOO")); + assertFalse(p1.equals(p3)); + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java b/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java index 715094c..7cbe2f0 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java @@ -793,20 +793,24 @@ public abstract class ActiveMQTestBase extends Assert { } protected final void clearDataRecreateServerDirs() { - clearDataRecreateServerDirs(getTestDir()); + clearDataRecreateServerDirs(0, false); } - protected void clearDataRecreateServerDirs(final String testDir1) { + protected final void clearDataRecreateServerDirs(int index, boolean backup) { + clearDataRecreateServerDirs(getTestDir(), index, backup); + } + + protected void clearDataRecreateServerDirs(final String testDir1, int index, boolean backup) { // Need to delete the root File file = new File(testDir1); deleteDirectory(file); file.mkdirs(); - recreateDirectory(getJournalDir(testDir1)); - recreateDirectory(getBindingsDir(testDir1)); - recreateDirectory(getPageDir(testDir1)); - recreateDirectory(getLargeMessagesDir(testDir1)); + recreateDirectory(getJournalDir(testDir1, index, backup)); + recreateDirectory(getBindingsDir(testDir1, index, backup)); + recreateDirectory(getPageDir(testDir1, index, backup)); + recreateDirectory(getLargeMessagesDir(testDir1, index, backup)); recreateDirectory(getClientLargeMessagesDir(testDir1)); recreateDirectory(getTemporaryDir(testDir1)); } @@ -815,7 +819,7 @@ public abstract class ActiveMQTestBase extends Assert { * @return the journalDir */ public String getJournalDir() { - return getJournalDir(getTestDir()); + return getJournalDir(0, false); } protected static String getJournalDir(final String testDir1) { @@ -834,7 +838,7 @@ public abstract class ActiveMQTestBase extends Assert { * @return the bindingsDir */ protected String getBindingsDir() { - return getBindingsDir(getTestDir()); + return getBindingsDir(0, false); } /** @@ -859,7 +863,7 @@ public abstract class ActiveMQTestBase extends Assert { * @return the pageDir */ protected String getPageDir() { - return getPageDir(getTestDir()); + return getPageDir(0, false); } protected File getPageDirFile() { @@ -886,7 +890,7 @@ public abstract class ActiveMQTestBase extends Assert { * @return the largeMessagesDir */ protected String getLargeMessagesDir() { - return getLargeMessagesDir(getTestDir()); + return getLargeMessagesDir(0, false); } /** http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/resources/login.config ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/resources/login.config b/artemis-server/src/test/resources/login.config new file mode 100644 index 0000000..9b1e1c0 --- /dev/null +++ b/artemis-server/src/test/resources/login.config @@ -0,0 +1,118 @@ +/* + * 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. + */ +PropertiesLogin { + org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule required + debug=true + org.apache.activemq.jaas.properties.user="users.properties" + org.apache.activemq.jaas.properties.role="roles.properties"; +}; + +LDAPLogin { + org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required + debug=true + initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory + connectionURL="ldap://localhost:1024" + connectionUsername="uid=admin,ou=system" + connectionPassword=secret + connectionProtocol=s + authentication=simple + userBase="ou=system" + userSearchMatching="(uid={0})" + userSearchSubtree=false + roleBase="ou=system" + roleName=cn + roleSearchMatching="(member=uid={1},ou=system)" + roleSearchSubtree=false + ; +}; + +UnAuthenticatedLDAPLogin { + org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required + debug=true + initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory + connectionURL="ldap://localhost:1024" + connectionUsername="uid=admin,ou=system" + connectionPassword="" + connectionProtocol=s + authentication=simple + userBase="ou=system" + userSearchMatching="(uid={0})" + userSearchSubtree=false + roleBase="ou=system" + roleName=dummyRoleName + roleSearchMatching="(uid={1})" + roleSearchSubtree=false + ; +}; + +ExpandedLDAPLogin { + org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required + debug=true + initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory + connectionURL="ldap://localhost:1024" + connectionUsername="uid=admin,ou=system" + connectionPassword=secret + connectionProtocol=s + authentication=simple + userBase="ou=system" + userSearchMatching="(uid={0})" + userSearchSubtree=false + roleBase="ou=system" + roleName=cn + roleSearchMatching="(uid={1})" + roleSearchSubtree=false + expandRoles=true + expandRolesMatching="(member={0})" + ; +}; + +GuestLogin { + org.apache.activemq.artemis.spi.core.security.jaas.GuestLoginModule required + debug=true + org.apache.activemq.jaas.guest.user="foo" + org.apache.activemq.jaas.guest.role="bar"; + +}; + +GuestLoginWithDefaults { + org.apache.activemq.artemis.spi.core.security.jaas.GuestLoginModule required + debug=true; +}; + +OpenLdapConfiguration { + org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required + debug=true + initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory + connectionURL="ldap://localhost:389" + connectionUsername="cn=mqbroker,ou=Services,ou=system,dc=fusesource,dc=com" + connectionPassword="sunflower" + connectionProtocol="s" + topicSearchMatchingFormat="cn={0},ou=Topic,ou=Destination,ou=ActiveMQ,ou=system,dc=fusesource,dc=com" + topicSearchSubtreeBool=true + authentication=simple + userBase="ou=User,ou=ActiveMQ,ou=system,dc=fusesource,dc=com" + userSearchMatching="(uid={0})" + userSearchSubtree=false + roleSearchMatching="(uid={1})" + queueSearchMatchingFormat="cn={0},ou=Queue,ou=Destination,ou=ActiveMQ,ou=system,dc=fusesource,dc=com" + queueSearchSubtreeBool=true + roleBase="ou=Group,ou=ActiveMQ,ou=system,dc=fusesource,dc=com" + roleName=cn + roleSearchMatching="(member:=uid={1})" + roleSearchSubtree=true + ; +}; http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/resources/roles.properties ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/resources/roles.properties b/artemis-server/src/test/resources/roles.properties new file mode 100644 index 0000000..de332d3 --- /dev/null +++ b/artemis-server/src/test/resources/roles.properties @@ -0,0 +1,20 @@ +# +# 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. +# + +programmers=first +accounting=second +employees=first,second http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/resources/test.ldif ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/resources/test.ldif b/artemis-server/src/test/resources/test.ldif new file mode 100644 index 0000000..6d6bd58 --- /dev/null +++ b/artemis-server/src/test/resources/test.ldif @@ -0,0 +1,39 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +dn: uid=first,ou=system +uid: first +userPassword: secret +objectClass: account +objectClass: simpleSecurityObject +objectClass: top + +################### +## Define groups ## +################### + +dn: cn=admins,ou=system +cn: admins +member: uid=first,ou=system +objectClass: groupOfNames +objectClass: top + +dn: cn=users,ou=system +cn: users +member: cn=admins,ou=system +objectClass: groupOfNames +objectClass: top \ No newline at end of file http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/test/resources/users.properties ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/resources/users.properties b/artemis-server/src/test/resources/users.properties new file mode 100644 index 0000000..1087b0b --- /dev/null +++ b/artemis-server/src/test/resources/users.properties @@ -0,0 +1,19 @@ +# +# 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. +# + +first=secret +second=password
