diff --git a/activemq-jaas/src/main/java/org/apache/activemq/jaas/ReloadableProperties.java b/activemq-jaas/src/main/java/org/apache/activemq/jaas/ReloadableProperties.java index 95781cc11c..42427d0a0b 100644 --- a/activemq-jaas/src/main/java/org/apache/activemq/jaas/ReloadableProperties.java +++ b/activemq-jaas/src/main/java/org/apache/activemq/jaas/ReloadableProperties.java @@ -24,6 +24,8 @@ import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +35,7 @@ public class ReloadableProperties { private Properties props = new Properties(); private Map invertedProps; private Map> invertedValueProps; + private Map regexpProps; private long reloadTime = -1; private final PropertiesLoader.FileNameKey key; @@ -51,6 +54,7 @@ public class ReloadableProperties { load(key.file(), props); invertedProps = null; invertedValueProps = null; + regexpProps = null; if (key.isDebug()) { LOG.debug("Load of: " + key); } @@ -69,7 +73,10 @@ public class ReloadableProperties { if (invertedProps == null) { invertedProps = new HashMap<>(props.size()); for (Map.Entry val : props.entrySet()) { - invertedProps.put((String) val.getValue(), (String) val.getKey()); + String str = (String) val.getValue(); + if (!looksLikeRegexp(str)) { + invertedProps.put(str, (String) val.getKey()); + } } } return invertedProps; @@ -93,6 +100,24 @@ public class ReloadableProperties { return invertedValueProps; } + public synchronized Map regexpPropertiesMap() { + if (regexpProps == null) { + regexpProps = new HashMap<>(props.size()); + for (Map.Entry val : props.entrySet()) { + String str = (String) val.getValue(); + if (looksLikeRegexp(str)) { + try { + Pattern p = Pattern.compile(str.substring(1, str.length() - 1)); + regexpProps.put((String) val.getKey(), p); + } catch (PatternSyntaxException e) { + LOG.warn("Ignoring invalid regexp: " + str); + } + } + } + } + return regexpProps; + } + private void load(final File source, Properties props) throws IOException { FileInputStream in = new FileInputStream(source); try { @@ -116,4 +141,9 @@ public class ReloadableProperties { return key.file.lastModified() > reloadTime; } + private boolean looksLikeRegexp(String str) { + int len = str.length(); + return len > 2 && str.charAt(0) == '/' && str.charAt(len - 1) == '/'; + } + } diff --git a/activemq-jaas/src/main/java/org/apache/activemq/jaas/TextFileCertificateLoginModule.java b/activemq-jaas/src/main/java/org/apache/activemq/jaas/TextFileCertificateLoginModule.java index 42f2c9d64a..c316367461 100644 --- a/activemq-jaas/src/main/java/org/apache/activemq/jaas/TextFileCertificateLoginModule.java +++ b/activemq-jaas/src/main/java/org/apache/activemq/jaas/TextFileCertificateLoginModule.java @@ -24,6 +24,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.regex.Pattern; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; @@ -32,13 +33,14 @@ import javax.security.auth.login.LoginException; /** * 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 =. This class also - * uses a group definition file where each line is =,,etc. + * Properties class where each line is either = + * or =//. This class also uses a group definition + * file where each line is =,,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). + * org.apache.activemq.jaas.textfiledn.group 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). * * @author sepandm@gmail.com (Sepand) */ @@ -48,6 +50,7 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule { private static final String GROUP_FILE_PROP_NAME = "org.apache.activemq.jaas.textfiledn.group"; private Map> groupsByUser; + private Map regexpByUser; private Map usersByDn; /** @@ -58,6 +61,7 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule { super.initialize(subject, callbackHandler, sharedState, options); usersByDn = load(USER_FILE_PROP_NAME, "", options).invertedPropertiesMap(); + regexpByUser = load(USER_FILE_PROP_NAME, "", options).regexpPropertiesMap(); groupsByUser = load(GROUP_FILE_PROP_NAME, "", options).invertedPropertiesValuesMap(); } @@ -76,8 +80,8 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule { if (certs == null) { throw new LoginException("Client certificates not found. Cannot authenticate."); } - - return usersByDn.get(getDistinguishedName(certs)); + String dn = getDistinguishedName(certs); + return usersByDn.containsKey(dn) ? usersByDn.get(dn) : getUserByRegexp(dn); } /** @@ -96,4 +100,17 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule { } return userGroups; } + + private synchronized String getUserByRegexp(String dn) { + String name = null; + for (Map.Entry val : regexpByUser.entrySet()) { + if (val.getValue().matcher(dn).matches()) { + name = val.getKey(); + break; + } + } + usersByDn.put(dn, name); + return name; + } + } diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/security/TextFileCertificateLoginModuleTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/security/TextFileCertificateLoginModuleTest.java index 76681c67d6..9ca43c949e 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/security/TextFileCertificateLoginModuleTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/security/TextFileCertificateLoginModuleTest.java @@ -38,6 +38,7 @@ public class TextFileCertificateLoginModuleTest { private static final String CERT_USERS_FILE_SMALL = "cert-users-SMALL.properties"; private static final String CERT_USERS_FILE_LARGE = "cert-users-LARGE.properties"; + private static final String CERT_USERS_FILE_REGEXP = "cert-users-REGEXP.properties"; private static final String CERT_GROUPS_FILE = "cert-groups.properties"; private static final Logger LOG = LoggerFactory.getLogger(TextFileCertificateLoginModuleTest.class); @@ -76,6 +77,11 @@ public class TextFileCertificateLoginModuleTest { loginTest(CERT_USERS_FILE_LARGE, CERT_GROUPS_FILE); } + @Test + public void testLoginWithREGEXPUsersFile() throws Exception { + loginTest(CERT_USERS_FILE_REGEXP, CERT_GROUPS_FILE); + } + private void loginTest(String usersFiles, String groupsFile) throws LoginException { HashMap options = new HashMap(); diff --git a/activemq-unit-tests/src/test/resources/cert-users-REGEXP.properties b/activemq-unit-tests/src/test/resources/cert-users-REGEXP.properties new file mode 100644 index 0000000000..c422738f3b --- /dev/null +++ b/activemq-unit-tests/src/test/resources/cert-users-REGEXP.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. +## --------------------------------------------------------------------------- + +CNODD=/DN=TEST_USER_\\d*[13579]/ +CNEVEN=/DN=TEST_USER_\\d*[02468]/