/* * JBoss, Home of Professional Open Source. * Copyright 2016 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * Licensed 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.wildfly.security.ldap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.wildfly.common.Assert.assertNotNull; import static org.wildfly.common.Assert.assertTrue; import javax.naming.NamingException; import javax.naming.directory.BasicAttribute; import javax.naming.directory.DirContext; import javax.naming.directory.ModificationItem; import java.security.Principal; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import org.junit.Test; import org.wildfly.common.function.ExceptionSupplier; import org.wildfly.security.auth.SupportLevel; import org.wildfly.security.auth.permission.LoginPermission; import org.wildfly.security.auth.realm.CacheableSecurityRealm; import org.wildfly.security.auth.realm.CachingModifiableSecurityRealm; import org.wildfly.security.auth.realm.ldap.LdapSecurityRealmBuilder; import org.wildfly.security.auth.server.CloseableIterator; import org.wildfly.security.auth.server.ModifiableRealmIdentity; import org.wildfly.security.auth.server.ModifiableSecurityRealm; import org.wildfly.security.auth.server.RealmIdentity; import org.wildfly.security.auth.server.RealmUnavailableException; import org.wildfly.security.auth.server.SecurityDomain; import org.wildfly.security.auth.server.SecurityIdentity; import org.wildfly.security.auth.server.ServerAuthenticationContext; import org.wildfly.security.authz.Attributes; import org.wildfly.security.authz.AuthorizationIdentity; import org.wildfly.security.cache.RealmIdentityCache; import org.wildfly.security.credential.Credential; import org.wildfly.security.credential.PasswordCredential; import org.wildfly.security.evidence.Evidence; import org.wildfly.security.evidence.PasswordGuessEvidence; import org.wildfly.security.password.interfaces.ClearPassword; /** * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> */ public class LdapSecurityRealmIdentityCacheSuiteChild { private CountDownLatch waitServerNotification = new CountDownLatch(1); private AtomicInteger realmHitCount = new AtomicInteger(); private AtomicInteger credentialHitCount = new AtomicInteger(); private AtomicInteger attributesHitCount = new AtomicInteger(); private AtomicInteger evidencesHitCount = new AtomicInteger(); @Test public void testCacheUpdateAfterChangeNotificationFromServer() throws Exception { SecurityDomain securityDomain = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", createSecurityRealm(true, false)).build() .setPermissionMapper((permissionMappable, roles) -> LoginPermission.getInstance()) .build(); for (int i = 0; i < 10; i++) { assertAuthenticationAndAuthorization("plainUser", securityDomain); assertEquals(1, realmHitCount.get()); assertEquals(1, credentialHitCount.get()); assertEquals(1, attributesHitCount.get()); assertEquals(0, evidencesHitCount.get()); } ExceptionSupplier<DirContext, NamingException> supplier = LdapTestSuite.dirContextFactory.create(); DirContext dirContext = supplier.get(); ModificationItem[] mods = new ModificationItem[1]; mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("sn", "Changed SN")); dirContext.modifyAttributes("uid=plainUser,dc=elytron,dc=wildfly,dc=org", mods); waitServerNotification.await(5, TimeUnit.SECONDS); assertAuthenticationAndAuthorization("plainUser", securityDomain); assertEquals(2, realmHitCount.get()); assertEquals(2, credentialHitCount.get()); assertEquals(2, attributesHitCount.get()); assertEquals(0, evidencesHitCount.get()); dirContext.close(); } @Test public void testCacheUpdateAfterRemoveNotificationFromServer() throws Exception { SecurityDomain securityDomain = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", createSecurityRealm(true, false)).build() .setPermissionMapper((permissionMappable, roles) -> LoginPermission.getInstance()) .build(); assertAuthenticationAndAuthorization("userToRemove", securityDomain); assertAuthenticationAndAuthorization("userToRemove", securityDomain); assertEquals(1, realmHitCount.get()); ExceptionSupplier<DirContext, NamingException> supplier = LdapTestSuite.dirContextFactory.create(); DirContext dirContext = supplier.get(); dirContext.destroySubcontext("uid=userToRemove,dc=elytron,dc=wildfly,dc=org"); waitServerNotification.await(5, TimeUnit.SECONDS); ServerAuthenticationContext sac = createServerAuthenticationContext("userToRemove", securityDomain); assertFalse(sac.exists()); dirContext.close(); } @Test public void testDirectVerificationCaching() throws Exception { SecurityDomain securityDomain = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", createSecurityRealm(false, true)).build() .setPermissionMapper((permissionMappable, roles) -> LoginPermission.getInstance()) .build(); for (int i = 0; i < 5; i++) { ServerAuthenticationContext sac = createServerAuthenticationContext("plainUser", securityDomain); assertTrue(sac.verifyEvidence(new PasswordGuessEvidence("plainPassword".toCharArray()))); assertEquals(1, realmHitCount.get()); assertEquals(1, credentialHitCount.get()); assertEquals(0, attributesHitCount.get()); assertEquals(1, evidencesHitCount.get()); } } private void assertAuthenticationAndAuthorization(String username, SecurityDomain securityDomain) throws RealmUnavailableException { ServerAuthenticationContext sac = createServerAuthenticationContext(username, securityDomain); assertTrue(sac.verifyEvidence(new PasswordGuessEvidence("plainPassword".toCharArray()))); assertEquals(SupportLevel.SUPPORTED, sac.getCredentialAcquireSupport(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR)); assertEquals("plainPassword", new String(sac.getCredential(PasswordCredential.class).getPassword().castAs(ClearPassword.class, ClearPassword.ALGORITHM_CLEAR).getPassword())); assertTrue(sac.verifyEvidence(new PasswordGuessEvidence("plainPassword".toCharArray()))); assertTrue(sac.authorize(username)); SecurityIdentity securityIdentity = sac.getAuthorizedIdentity(); assertNotNull(securityIdentity); assertNotNull(securityIdentity.getAttributes()); assertEquals(username, securityIdentity.getPrincipal().getName()); } private ServerAuthenticationContext createServerAuthenticationContext(String username, SecurityDomain securityDomain) throws RealmUnavailableException { ServerAuthenticationContext sac = securityDomain.createNewAuthenticationContext(); sac.setAuthenticationName(username); return sac; } private ModifiableSecurityRealm createSecurityRealm(boolean credentialLoader, boolean directVerification) { LdapSecurityRealmBuilder builder = LdapSecurityRealmBuilder.builder() .setDirContextSupplier(LdapTestSuite.dirContextFactory.create()) .identityMapping() .setSearchDn("dc=elytron,dc=wildfly,dc=org") .setRdnIdentifier("uid") .build(); if (credentialLoader) builder.userPasswordCredentialLoader().build(); if (directVerification) builder.addDirectEvidenceVerification(); return new CachingModifiableSecurityRealm(new MockCacheableModifiableSecurityRealm(builder.build()), createRealmIdentitySimpleJavaMapCache()); } private class MockCacheableModifiableSecurityRealm implements ModifiableSecurityRealm, CacheableSecurityRealm { private final ModifiableSecurityRealm realm; public MockCacheableModifiableSecurityRealm(ModifiableSecurityRealm realm) { this.realm = realm; } @Override public void registerIdentityChangeListener(Consumer<Principal> listener) { ((CacheableSecurityRealm) realm).registerIdentityChangeListener(principal -> { listener.accept(principal); waitServerNotification.countDown(); }); } @Override public RealmIdentity getRealmIdentity(Principal principal) throws RealmUnavailableException { realmHitCount.incrementAndGet(); return new RealmIdentity() { RealmIdentity identity = realm.getRealmIdentity(principal); @Override public Principal getRealmIdentityPrincipal() { return identity.getRealmIdentityPrincipal(); } @Override public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException { credentialHitCount.incrementAndGet(); return identity.getCredentialAcquireSupport(credentialType, algorithmName); } @Override public <C extends Credential> C getCredential(Class<C> credentialType) throws RealmUnavailableException { credentialHitCount.incrementAndGet(); return identity.getCredential(credentialType); } @Override public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException { evidencesHitCount.incrementAndGet(); return identity.getEvidenceVerifySupport(evidenceType, algorithmName); } @Override public boolean verifyEvidence(Evidence evidence) throws RealmUnavailableException { evidencesHitCount.incrementAndGet(); return identity.verifyEvidence(evidence); } @Override public boolean exists() throws RealmUnavailableException { return identity.exists(); } @Override public AuthorizationIdentity getAuthorizationIdentity() throws RealmUnavailableException { attributesHitCount.incrementAndGet(); return identity.getAuthorizationIdentity(); } @Override public Attributes getAttributes() throws RealmUnavailableException { attributesHitCount.incrementAndGet(); return identity.getAttributes(); } }; } @Override public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException { return realm.getCredentialAcquireSupport(credentialType, algorithmName); } @Override public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException { return realm.getEvidenceVerifySupport(evidenceType, algorithmName); } @Override public ModifiableRealmIdentity getRealmIdentityForUpdate(Principal principal) throws RealmUnavailableException { return realm.getRealmIdentityForUpdate(principal); } @Override public CloseableIterator<ModifiableRealmIdentity> getRealmIdentityIterator() throws RealmUnavailableException { return realm.getRealmIdentityIterator(); } } private RealmIdentityCache createRealmIdentitySimpleJavaMapCache() { return new RealmIdentityCache() { private Map<Principal, RealmIdentity> cache = new HashMap<>(); @Override public void put(Principal principal, RealmIdentity realmIdentity) { cache.put(principal, realmIdentity); } @Override public RealmIdentity get(Principal principal) { return cache.get(principal); } @Override public void remove(Principal principal) { cache.remove(principal); } @Override public void clear() { cache.clear(); } }; } }