You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2019/08/01 14:34:49 UTC

[isis] branch v2 updated: ISIS-2156 adds smoketest for Secman using delegated LDAP authentication

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

ahuber pushed a commit to branch v2
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/v2 by this push:
     new d89166c  ISIS-2156 adds smoketest for Secman using delegated LDAP authentication
d89166c is described below

commit d89166c1e5e304ca555a389f1f859b33fa715468
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Aug 1 16:34:40 2019 +0200

    ISIS-2156 adds smoketest for Secman using delegated LDAP authentication
    
    - testing for proper user auto-creation (initial state disabled)
    - testing proper login/logout for existing user
---
 .../isis/testdomain/ldap/LdapEmbeddedServer.java   |  4 +-
 .../testdomain/ldap/LdapEmbeddedServerTest.java    |  2 +-
 .../isis/testdomain/shiro/ShiroLdapTest.java       |  6 +-
 ...ShiroLdapTest.java => ShiroSecmanLdapTest.java} | 70 +++++++++++++++++-----
 .../smoketest/src/test/resources/ldap-users.ldif   | 26 +++++---
 .../smoketest/src/test/resources/shiro-ldap.ini    |  9 +--
 .../{shiro-ldap.ini => shiro-secman-ldap.ini}      | 29 +++------
 ...oduleSecurityRegularUserRoleAndPermissions.java |  2 +-
 ...ticationStrategyForIsisModuleSecurityRealm.java | 29 +++++----
 ...inglePrincipalForApplicationUserInAnyRealm.java |  3 +-
 10 files changed, 112 insertions(+), 68 deletions(-)

diff --git a/examples/smoketest/src/test/java/org/apache/isis/testdomain/ldap/LdapEmbeddedServer.java b/examples/smoketest/src/test/java/org/apache/isis/testdomain/ldap/LdapEmbeddedServer.java
index 9d1739b..4c8ef42 100644
--- a/examples/smoketest/src/test/java/org/apache/isis/testdomain/ldap/LdapEmbeddedServer.java
+++ b/examples/smoketest/src/test/java/org/apache/isis/testdomain/ldap/LdapEmbeddedServer.java
@@ -38,7 +38,7 @@ import lombok.val;
 //@RunWith(FrameworkRunner.class) //when picked up as a regular JUnit Test just act as a no-op.
 @CreateDS(name = "myDS",
     partitions = {
-        @CreatePartition(name = "test", suffix = "dc=myorg,dc=com")
+        @CreatePartition(name = "mojo", suffix = "o=mojo")
     })
 @CreateLdapServer(transports = { 
 		@CreateTransport(protocol = "LDAP", address = "localhost", port = LdapEmbeddedServer.PORT)})
@@ -47,6 +47,8 @@ public class LdapEmbeddedServer extends AbstractLdapTestUnit {
 	
 	/** IP port for the LDAP server to listen on */
     public static final int PORT = 10389;
+	public static final String SVEN_PRINCIPAL = "cn=Sven Mojo,o=mojo";
+	public static final String OLAF_PRINCIPAL = "cn=Olaf Mojo,o=mojo";
 
 	@Test
     public void authenticateAgainstLdap() {
diff --git a/examples/smoketest/src/test/java/org/apache/isis/testdomain/ldap/LdapEmbeddedServerTest.java b/examples/smoketest/src/test/java/org/apache/isis/testdomain/ldap/LdapEmbeddedServerTest.java
index 2d658d8..aa4c98a 100644
--- a/examples/smoketest/src/test/java/org/apache/isis/testdomain/ldap/LdapEmbeddedServerTest.java
+++ b/examples/smoketest/src/test/java/org/apache/isis/testdomain/ldap/LdapEmbeddedServerTest.java
@@ -51,7 +51,7 @@ class LdapEmbeddedServerTest {
         env.put(Context.PROVIDER_URL, "ldap://localhost:" + LdapEmbeddedServer.PORT);
 
         env.put(Context.SECURITY_AUTHENTICATION, "simple");
-        env.put(Context.SECURITY_PRINCIPAL, "cn=Sven Tester,ou=Users,dc=myorg,dc=com");
+        env.put(Context.SECURITY_PRINCIPAL, LdapEmbeddedServer.SVEN_PRINCIPAL);
         env.put(Context.SECURITY_CREDENTIALS, "pass");
         
         try {
diff --git a/examples/smoketest/src/test/java/org/apache/isis/testdomain/shiro/ShiroLdapTest.java b/examples/smoketest/src/test/java/org/apache/isis/testdomain/shiro/ShiroLdapTest.java
index 9622069..673b469 100644
--- a/examples/smoketest/src/test/java/org/apache/isis/testdomain/shiro/ShiroLdapTest.java
+++ b/examples/smoketest/src/test/java/org/apache/isis/testdomain/shiro/ShiroLdapTest.java
@@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import javax.inject.Inject;
 
 import org.apache.isis.testdomain.jdo.JdoTestDomainModule_withShiro;
+import org.apache.isis.testdomain.ldap.LdapEmbeddedServer;
 import org.apache.isis.testdomain.ldap.LdapServerService;
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authc.AuthenticationToken;
@@ -80,11 +81,10 @@ class ShiroLdapTest extends AbstractShiroTest {
 
 		val subject = SecurityUtils.getSubject(); 
 		assertNotNull(subject);
-		
 		assertFalse(subject.isAuthenticated());
 
 		val token = (AuthenticationToken) new UsernamePasswordToken(
-				"cn=Sven Tester,ou=Users,dc=myorg,dc=com",
+				LdapEmbeddedServer.SVEN_PRINCIPAL,
 				"pass");
 
 		subject.login(token);
@@ -106,7 +106,7 @@ class ShiroLdapTest extends AbstractShiroTest {
 		assertFalse(subject.isAuthenticated());
 
 		val token = (AuthenticationToken) new UsernamePasswordToken(
-				"cn=Sven Tester,ou=Users,dc=myorg,dc=com",
+				LdapEmbeddedServer.SVEN_PRINCIPAL,
 				"invalid-pass");
 		
 		assertThrows(Exception.class, ()->{
diff --git a/examples/smoketest/src/test/java/org/apache/isis/testdomain/shiro/ShiroLdapTest.java b/examples/smoketest/src/test/java/org/apache/isis/testdomain/shiro/ShiroSecmanLdapTest.java
similarity index 59%
copy from examples/smoketest/src/test/java/org/apache/isis/testdomain/shiro/ShiroLdapTest.java
copy to examples/smoketest/src/test/java/org/apache/isis/testdomain/shiro/ShiroSecmanLdapTest.java
index 9622069..22d27c2 100644
--- a/examples/smoketest/src/test/java/org/apache/isis/testdomain/shiro/ShiroLdapTest.java
+++ b/examples/smoketest/src/test/java/org/apache/isis/testdomain/shiro/ShiroSecmanLdapTest.java
@@ -25,10 +25,19 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import javax.inject.Inject;
 
+import org.apache.isis.extensions.secman.api.SecurityModuleConfig;
+import org.apache.isis.extensions.secman.api.role.ApplicationRoleRepository;
+import org.apache.isis.extensions.secman.api.user.ApplicationUserRepository;
+import org.apache.isis.extensions.secman.encryption.jbcrypt.IsisBootSecmanEncryptionJbcrypt;
+import org.apache.isis.extensions.secman.jdo.IsisBootSecmanPersistenceJdo;
+import org.apache.isis.extensions.secman.model.IsisBootSecmanModel;
+import org.apache.isis.extensions.secman.shiro.IsisBootSecmanRealmShiro;
 import org.apache.isis.testdomain.jdo.JdoTestDomainModule_withShiro;
+import org.apache.isis.testdomain.ldap.LdapEmbeddedServer;
 import org.apache.isis.testdomain.ldap.LdapServerService;
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.DisabledAccountException;
 import org.apache.shiro.authc.UsernamePasswordToken;
 import org.apache.shiro.config.IniSecurityManagerFactory;
 import org.junit.jupiter.api.AfterAll;
@@ -38,7 +47,6 @@ import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.context.annotation.Import;
 
 import lombok.val;
-import lombok.extern.log4j.Log4j2;
 
 @SpringBootTest(
 		classes = { 
@@ -49,19 +57,28 @@ import lombok.extern.log4j.Log4j2;
 				"smoketest.withShiro=true", // enable shiro specific config to be picked up by Spring 
 		})
 @Import({
+	
 	LdapServerService.class,
-})
-@Log4j2
-class ShiroLdapTest extends AbstractShiroTest {
 	
-	@Inject LdapServerService ldapServerService;
+    // Security Manager Extension (secman)
+    IsisBootSecmanModel.class,
+    IsisBootSecmanRealmShiro.class,
+    IsisBootSecmanPersistenceJdo.class,
+    IsisBootSecmanEncryptionJbcrypt.class,
+})
+class ShiroSecmanLdapTest extends AbstractShiroTest {
 
+	@Inject LdapServerService ldapServerService;
+	@Inject ApplicationUserRepository applicationUserRepository;
+	@Inject ApplicationRoleRepository applicationRoleRepository;
+	@Inject SecurityModuleConfig securityModuleConfig;
+	
 	@BeforeAll
 	static void beforeClass() {
-		// Build and set the SecurityManager used to build Subject instances used in your tests
-		// This typically only needs to be done once per class if your shiro.ini doesn't change,
-		// otherwise, you'll need to do this logic in each test that is different
-		val factory = new IniSecurityManagerFactory("classpath:shiro-ldap.ini");
+		//    Build and set the SecurityManager used to build Subject instances used in your tests
+		//    This typically only needs to be done once per class if your shiro.ini doesn't change,
+		//    otherwise, you'll need to do this logic in each test that is different
+		val factory = new IniSecurityManagerFactory("classpath:shiro-secman-ldap.ini");
 		setSecurityManager(factory.getInstance());
 	}
 
@@ -73,18 +90,22 @@ class ShiroLdapTest extends AbstractShiroTest {
 	@Test
 	void loginLogoutRoundtrip() {
 		
-		log.info("starting login/logout roundtrip");
-
+		// setup sven account in DB
+		val regularUserRoleName = securityModuleConfig.getRegularUserRoleName();
+		val regularUserRole = applicationRoleRepository.findByName(regularUserRoleName);
+		val enabled = true;
+		applicationUserRepository.newDelegateUser(LdapEmbeddedServer.SVEN_PRINCIPAL, regularUserRole, enabled);
+		//
+		
 		val secMan = SecurityUtils.getSecurityManager();
 		assertNotNull(secMan);
 
 		val subject = SecurityUtils.getSubject(); 
 		assertNotNull(subject);
-		
 		assertFalse(subject.isAuthenticated());
 
 		val token = (AuthenticationToken) new UsernamePasswordToken(
-				"cn=Sven Tester,ou=Users,dc=myorg,dc=com",
+				LdapEmbeddedServer.SVEN_PRINCIPAL,
 				"pass");
 
 		subject.login(token);
@@ -94,6 +115,27 @@ class ShiroLdapTest extends AbstractShiroTest {
 		assertFalse(subject.isAuthenticated());
 
 	}
+	
+	@Test
+	void login_withAccount_thatOnlyExistsWithLdap() {
+
+		val secMan = getSecurityManager();
+		assertNotNull(secMan);
+		
+		val subject = SecurityUtils.getSubject(); 
+		assertNotNull(subject);
+		assertFalse(subject.isAuthenticated());
+
+		val token = (AuthenticationToken) new UsernamePasswordToken(
+				LdapEmbeddedServer.OLAF_PRINCIPAL,
+				"pass");
+
+		// default behavior is to create the account within the DB but leave it disabled 
+		assertThrows(DisabledAccountException.class, ()->{
+			subject.login(token);
+		});
+		
+	}
 
 	@Test
 	void login_withInvalidPassword() {
@@ -106,7 +148,7 @@ class ShiroLdapTest extends AbstractShiroTest {
 		assertFalse(subject.isAuthenticated());
 
 		val token = (AuthenticationToken) new UsernamePasswordToken(
-				"cn=Sven Tester,ou=Users,dc=myorg,dc=com",
+				LdapEmbeddedServer.SVEN_PRINCIPAL,
 				"invalid-pass");
 		
 		assertThrows(Exception.class, ()->{
diff --git a/examples/smoketest/src/test/resources/ldap-users.ldif b/examples/smoketest/src/test/resources/ldap-users.ldif
index 61e3a9b..cb36e94 100644
--- a/examples/smoketest/src/test/resources/ldap-users.ldif
+++ b/examples/smoketest/src/test/resources/ldap-users.ldif
@@ -18,27 +18,37 @@
 #
 
 version: 1
-dn: dc=myorg,dc=com
+dn: o=mojo
 objectClass: domain
 objectClass: top
 dc: myorg
 
-dn: ou=Users,dc=myorg,dc=com
+dn: ou=users,o=mojo
 objectClass: organizationalUnit
 objectClass: top
-ou: Users
+ou: users
 
-dn: ou=Groups,dc=myorg,dc=com
+dn: ou=groups,o=mojo
 objectClass: organizationalUnit
 objectClass: top
-ou: Groups
+ou: groups
 
-dn: cn=Sven Tester,ou=Users,dc=myorg,dc=com
+dn: cn=Sven Mojo,o=mojo
 objectClass: inetOrgPerson
 objectClass: organizationalPerson
 objectClass: person
 objectClass: top
-cn: Sven Tester
-sn: Ldap
+cn: Sven Mojo
+sn: Mojo
 uid: sven
+userPassword: pass
+
+dn: cn=Olaf Mojo,o=mojo
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: Olaf Mojo
+sn: Mojo
+uid: olaf
 userPassword: pass
\ No newline at end of file
diff --git a/examples/smoketest/src/test/resources/shiro-ldap.ini b/examples/smoketest/src/test/resources/shiro-ldap.ini
index 2340e4b..fef9ace 100644
--- a/examples/smoketest/src/test/resources/shiro-ldap.ini
+++ b/examples/smoketest/src/test/resources/shiro-ldap.ini
@@ -50,14 +50,11 @@ ldapRealm.permissionsByRole=\
 
 securityManager.realms = $ldapRealm
 
-#authenticationStrategy=org.apache.isis.extensions.secman.shiro.AuthenticationStrategyForIsisModuleSecurityRealm
-#isisModuleSecurityRealm=org.apache.isis.extensions.secman.shiro.IsisModuleSecurityRealm
-
-#securityManager.authenticator.authenticationStrategy = $authenticationStrategy
-#securityManager.realms = $isisModuleSecurityRealm
-
 [users]
+# unused
+
 [roles]
+# unused
 
 
 
diff --git a/examples/smoketest/src/test/resources/shiro-ldap.ini b/examples/smoketest/src/test/resources/shiro-secman-ldap.ini
similarity index 64%
copy from examples/smoketest/src/test/resources/shiro-ldap.ini
copy to examples/smoketest/src/test/resources/shiro-secman-ldap.ini
index 2340e4b..1429f38 100644
--- a/examples/smoketest/src/test/resources/shiro-ldap.ini
+++ b/examples/smoketest/src/test/resources/shiro-secman-ldap.ini
@@ -28,36 +28,23 @@ contextFactory.systemPassword = secret
 
 ldapRealm = org.apache.isis.security.shiro.IsisLdapRealm   
 ldapRealm.contextFactory = $contextFactory
-
 ldapRealm.searchBase = ou=groups,o=mojo                    
 ldapRealm.groupObjectClass = groupOfUniqueNames            
 ldapRealm.uniqueMemberAttribute = uniqueMember             
 ldapRealm.uniqueMemberAttributeValueTemplate = uid={0}
 
-# optional mapping from physical groups to logical application roles
-ldapRealm.rolesByGroup = \                                 
-    LDN_USERS: user_role,\
-    NYK_USERS: user_role,\
-    HKG_USERS: user_role,\
-    GLOBAL_ADMIN: admin_role,\
-    DEMOS: self-install_role
-
-ldapRealm.permissionsByRole=\
-   user_role = *:InventoryManager:*:*,\
-               *:Product:*:*; \
-   self-install_role = *:FixtureScriptsDefault:*:* ; \
-   admin_role = *
-
-securityManager.realms = $ldapRealm
+authenticationStrategy=org.apache.isis.extensions.secman.shiro.AuthenticationStrategyForIsisModuleSecurityRealm
+isisModuleSecurityRealm=org.apache.isis.extensions.secman.shiro.IsisModuleSecurityRealm
+isisModuleSecurityRealm.delegateAuthenticationRealm=$ldapRealm
 
-#authenticationStrategy=org.apache.isis.extensions.secman.shiro.AuthenticationStrategyForIsisModuleSecurityRealm
-#isisModuleSecurityRealm=org.apache.isis.extensions.secman.shiro.IsisModuleSecurityRealm
-
-#securityManager.authenticator.authenticationStrategy = $authenticationStrategy
-#securityManager.realms = $isisModuleSecurityRealm
+securityManager.authenticator.authenticationStrategy = $authenticationStrategy
+securityManager.realms = $isisModuleSecurityRealm
 
 [users]
+# unused
+
 [roles]
+# unused
 
 
 
diff --git a/extensions/secman/persistence-jdo/src/main/java/org/apache/isis/extensions/secman/jdo/seed/scripts/IsisModuleSecurityRegularUserRoleAndPermissions.java b/extensions/secman/persistence-jdo/src/main/java/org/apache/isis/extensions/secman/jdo/seed/scripts/IsisModuleSecurityRegularUserRoleAndPermissions.java
index 01eaa2b..0ec2fd6 100644
--- a/extensions/secman/persistence-jdo/src/main/java/org/apache/isis/extensions/secman/jdo/seed/scripts/IsisModuleSecurityRegularUserRoleAndPermissions.java
+++ b/extensions/secman/persistence-jdo/src/main/java/org/apache/isis/extensions/secman/jdo/seed/scripts/IsisModuleSecurityRegularUserRoleAndPermissions.java
@@ -31,7 +31,7 @@ import org.apache.isis.extensions.secman.model.app.user.MeService;
  */
 public class IsisModuleSecurityRegularUserRoleAndPermissions extends AbstractRoleAndPermissionsFixtureScript {
 
-    //public static final String ROLE_NAME = "isis-module-security-regular-user";
+    //public static final String ROLE_NAME = "isis-module-security-regular-user"; .. moved to SecurityModuleConfig
 
     public IsisModuleSecurityRegularUserRoleAndPermissions(SecurityModuleConfig configBean) {
         super(configBean.getRegularUserRoleName(), "Regular user of the security module");
diff --git a/extensions/secman/realm-shiro/src/main/java/org/apache/isis/extensions/secman/shiro/AuthenticationStrategyForIsisModuleSecurityRealm.java b/extensions/secman/realm-shiro/src/main/java/org/apache/isis/extensions/secman/shiro/AuthenticationStrategyForIsisModuleSecurityRealm.java
index dab0357..3f702d6 100644
--- a/extensions/secman/realm-shiro/src/main/java/org/apache/isis/extensions/secman/shiro/AuthenticationStrategyForIsisModuleSecurityRealm.java
+++ b/extensions/secman/realm-shiro/src/main/java/org/apache/isis/extensions/secman/shiro/AuthenticationStrategyForIsisModuleSecurityRealm.java
@@ -27,26 +27,31 @@ import org.apache.shiro.authc.SimpleAuthenticationInfo;
 import org.apache.shiro.authc.pam.AllSuccessfulStrategy;
 import org.apache.shiro.realm.Realm;
 
+import lombok.val;
+
 public class AuthenticationStrategyForIsisModuleSecurityRealm extends AllSuccessfulStrategy {
 
     /**
-     * Reconfigures the SimpleAuthenticationInfo to use a implementation for storing its PrincipalCollections.
+     * Reconfigures the SimpleAuthenticationInfo to use an implementation for storing its PrincipalCollections.
      *
      * <p>
-     *    The default implementation uses a {@link org.apache.shiro.subject.SimplePrincipalCollection}, however this
-     *    doesn't play well with the Isis Addons' security module which ends up chaining together multiple instances of
-     *    {@link org.apache.isis.extensions.secman.shiro.PrincipalForApplicationUser} for each login.  This is probably
-     *    because of it doing double duty with holding authorization information.  There may be a better design here,
-     *    but for now the solution I've chosen is to use a different implementation of
-     *    {@link org.apache.shiro.subject.PrincipalCollection} that will only ever store one instance of
-     *    {@link org.apache.isis.extensions.secman.shiro.PrincipalForApplicationUser} as a principal.
+     *    The default implementation uses a {@link org.apache.shiro.subject.SimplePrincipalCollection}, 
+     *    however this doesn't play well with the Isis security module which ends up chaining 
+     *    together multiple instances of {@link PrincipalForApplicationUser} for each login.  
+     *    This is probably because of it doing double duty with holding authorization information.  
+     *    There may be a better design here, but for now the solution I've chosen is to use a different 
+     *    implementation of {@link org.apache.shiro.subject.PrincipalCollection} that will only ever store 
+     *    one instance of {@link PrincipalForApplicationUser} as a principal.
      * </p>
      */
     @Override
-    public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException {
-        final SimpleAuthenticationInfo authenticationInfo = (SimpleAuthenticationInfo) super.beforeAllAttempts(realms, token);
-
-        authenticationInfo.setPrincipals(new PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm());
+    public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) 
+    		throws AuthenticationException {
+    	
+        val authenticationInfo = (SimpleAuthenticationInfo) super.beforeAllAttempts(realms, token);
+        val principalCollection = new PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm();
+        
+        authenticationInfo.setPrincipals(principalCollection);
         return authenticationInfo;
     }
 
diff --git a/extensions/secman/realm-shiro/src/main/java/org/apache/isis/extensions/secman/shiro/PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm.java b/extensions/secman/realm-shiro/src/main/java/org/apache/isis/extensions/secman/shiro/PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm.java
index 3576c22..9550069 100644
--- a/extensions/secman/realm-shiro/src/main/java/org/apache/isis/extensions/secman/shiro/PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm.java
+++ b/extensions/secman/realm-shiro/src/main/java/org/apache/isis/extensions/secman/shiro/PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm.java
@@ -23,7 +23,8 @@ import java.util.Collection;
 import org.apache.shiro.subject.SimplePrincipalCollection;
 
 @SuppressWarnings("rawtypes")
-public class PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm extends SimplePrincipalCollection {
+public class PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm 
+extends SimplePrincipalCollection {
 
     private static final long serialVersionUID = 1L;