You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by gt...@apache.org on 2021/03/23 09:52:03 UTC

[activemq-artemis] 01/02: ARTEMIS-3168 - add PrincipalConversionLoginModule feature

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

gtully pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git

commit 06461f146c328c3374a17315d423a4e57b6055ab
Author: gtully <ga...@gmail.com>
AuthorDate: Mon Mar 8 21:54:31 2021 +0000

    ARTEMIS-3168 - add PrincipalConversionLoginModule feature
---
 .../jaas/PrincipalConversionLoginModule.java       | 103 ++++++++++++++++
 .../jaas/PrincipalConversionLoginModuleTest.java   | 135 +++++++++++++++++++++
 docs/user-manual/en/security.md                    |  13 ++
 3 files changed, 251 insertions(+)

diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalConversionLoginModule.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalConversionLoginModule.java
new file mode 100644
index 0000000..272023c
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalConversionLoginModule.java
@@ -0,0 +1,103 @@
+/*
+ * 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.security.Principal;
+import java.util.Map;
+
+import org.jboss.logging.Logger;
+
+/**
+ * populate an empty (no UserPrincipal) subject with UserPrincipal seeded from existing principal
+ * Useful when a third party login module generated principal needs to be accepted as-is by the broker
+ */
+public class PrincipalConversionLoginModule implements AuditLoginModule {
+
+   private static final Logger logger = Logger.getLogger(PrincipalConversionLoginModule.class);
+
+   public static final String PRINCIPAL_CLASS_LIST = "principalClassList";
+   private Subject subject;
+   private String principalClazzList;
+   private Principal principal;
+
+   @Override
+   public void initialize(Subject subject,
+                          CallbackHandler callbackHandler,
+                          Map<String, ?> sharedState,
+                          Map<String, ?> options) {
+      this.subject = subject;
+      this.principalClazzList = (String) options.get(PRINCIPAL_CLASS_LIST);
+   }
+
+   @Override
+   public boolean login() throws LoginException {
+      logger.debug("login ok!");
+      return true;
+   }
+
+   @Override
+   public boolean commit() throws LoginException {
+
+      if (subject == null || !subject.getPrincipals(UserPrincipal.class).isEmpty()) {
+         return false;
+      }
+
+      if (principalClazzList != null) {
+         String[] principalClasses = principalClazzList.split(",");
+         for (String principalClass : principalClasses) {
+            String trimmedCandidateClassName = principalClass.trim();
+            for (Principal candidatePrincipal : subject.getPrincipals()) {
+               logger.debug("Checking principal class name:" + candidatePrincipal.getClass().getName() + ", " + candidatePrincipal);
+               if (candidatePrincipal.getClass().getName().equals(trimmedCandidateClassName)) {
+                  logger.debug("converting: " + candidatePrincipal);
+                  principal = new UserPrincipal(candidatePrincipal.getName());
+                  subject.getPrincipals().add(principal);
+                  break;
+               }
+            }
+         }
+      }
+      logger.debug("commit, result: " + (principal != null));
+      return principal != null;
+   }
+
+   @Override
+   public boolean abort() throws LoginException {
+      registerFailureForAudit(principal != null ? principal.getName() : null);
+      clear();
+      logger.debug("abort");
+      return true;
+   }
+
+   private void clear() {
+      principal = null;
+   }
+
+   @Override
+   public boolean logout() throws LoginException {
+      if (subject != null) {
+         subject.getPrincipals().remove(principal);
+      }
+      clear();
+      logger.debug("logout");
+      return true;
+   }
+
+}
\ No newline at end of file
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalConversionLoginModuleTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalConversionLoginModuleTest.java
new file mode 100644
index 0000000..fecb576
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalConversionLoginModuleTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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 java.security.Principal;
+import java.util.HashMap;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class PrincipalConversionLoginModuleTest {
+
+   @Test
+   public void loginOk() throws Exception {
+      PrincipalConversionLoginModule underTest = new PrincipalConversionLoginModule();
+
+      final Subject subject = new Subject();
+      underTest.initialize(subject,null, null, new HashMap<>());
+
+      assertTrue(underTest.login());
+      assertFalse(underTest.commit());
+      assertTrue(subject.getPrincipals().isEmpty());
+   }
+
+   @Test
+   public void loginOkOnNullSubject() throws Exception {
+      PrincipalConversionLoginModule underTest = new PrincipalConversionLoginModule();
+
+      underTest.initialize(null,null, null, new HashMap<>());
+
+      assertTrue(underTest.login());
+      assertFalse(underTest.commit());
+      assertTrue(underTest.logout());
+
+   }
+
+   @Test
+   public void loginConvert() throws Exception {
+      PrincipalConversionLoginModule underTest = new PrincipalConversionLoginModule();
+
+      final Subject subject = new Subject();
+      final HashMap<String, String> options = new HashMap<>();
+      options.put(PrincipalConversionLoginModule.PRINCIPAL_CLASS_LIST, RolePrincipal.class.getCanonicalName());
+      subject.getPrincipals().add(new RolePrincipal("BLA"));
+      underTest.initialize(subject, null, null, options);
+
+      assertTrue(underTest.login());
+      assertTrue(underTest.commit());
+      assertEquals(1, subject.getPrincipals(UserPrincipal.class).size());
+      assertEquals("BLA", ((Principal)subject.getPrincipals(UserPrincipal.class).iterator().next()).getName());
+
+      underTest.logout();
+      assertEquals(0, subject.getPrincipals(UserPrincipal.class).size());
+   }
+
+
+   @Test
+   public void loginNoOverwriteExistingUserPrincipal() throws Exception {
+      PrincipalConversionLoginModule underTest = new PrincipalConversionLoginModule();
+
+      final Subject subject = new Subject();
+      final HashMap<String, String> options = new HashMap<>();
+      options.put(PrincipalConversionLoginModule.PRINCIPAL_CLASS_LIST, RolePrincipal.class.getCanonicalName());
+      subject.getPrincipals().add(new RolePrincipal("BLA"));
+      subject.getPrincipals().add(new UserPrincipal("Important"));
+
+      underTest.initialize(subject, null, null, options);
+
+      assertTrue(underTest.login());
+      assertFalse(underTest.commit());
+      assertEquals(1, subject.getPrincipals(UserPrincipal.class).size());
+      assertEquals("Important", ((Principal)subject.getPrincipals(UserPrincipal.class).iterator().next()).getName());
+   }
+
+
+
+   static final class TestPrincipal implements Principal {
+
+      @Override
+      public boolean equals(Object another) {
+         return this == another;
+      }
+
+      @Override
+      public String toString() {
+         return null;
+      }
+
+      @Override
+      public int hashCode() {
+         return 0;
+      }
+
+      @Override
+      public String getName() {
+         return "TestPrincipal";
+      }
+   }
+
+   @Test
+   public void loginConvertList() throws Exception {
+      PrincipalConversionLoginModule underTest = new PrincipalConversionLoginModule();
+
+      final Subject subject = new Subject();
+      final HashMap<String, String> options = new HashMap<>();
+      String multiOptionList = TestPrincipal.class.getTypeName() + ", " + RolePrincipal.class.getTypeName();
+      options.put(PrincipalConversionLoginModule.PRINCIPAL_CLASS_LIST, multiOptionList);
+      subject.getPrincipals().add(new TestPrincipal());
+      underTest.initialize(subject, null, null, options);
+
+      assertTrue(underTest.login());
+      assertTrue(underTest.commit());
+      assertEquals(1, subject.getPrincipals(UserPrincipal.class).size());
+      assertEquals("TestPrincipal", ((Principal)subject.getPrincipals(UserPrincipal.class).iterator().next()).getName());
+   }
+
+}
diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md
index d94c19c..2c78558 100644
--- a/docs/user-manual/en/security.md
+++ b/docs/user-manual/en/security.md
@@ -1025,6 +1025,19 @@ do role mapping for the TLS client certificate.
 org.apache.activemq.artemis.spi.core.security.jaas.ExternalCertificateLoginModule required
     ;
 ```    
+
+#### PrincipalConversionLoginModule
+
+The principal conversion login module is used to convert an existing validated Principal 
+into a JAAS UserPrincipal. The module is configured with a list of class names used to
+match existing Principals. If no UserPrincipal exists, the first matching Principal
+will be added as a UserPrincipal of the same Name.
+
+```
+org.apache.activemq.artemis.spi.core.security.jaas.PrincipalConversionLoginModule required
+     principalClassList=org.apache.x.Principal,org.apache.y.Principal
+    ;
+```    
  
 
 The simplest way to make the login configuration available to JAAS is to add