From 9bd0fd4bea0e6501380d027c2fe4008d59fbe139 Mon Sep 17 00:00:00 2001 From: Diana Krepinska Date: Wed, 31 Aug 2022 19:04:31 +0200 Subject: [PATCH] [ELY-2413] Re-authentication after reboot, even though HttpSession are persisted --- .../impl/JaspiAuthenticationContext.java | 36 ++++++++++++---- .../org/wildfly/security/authz/Roles.java | 26 ++++++++++++ .../security/cache/CachedIdentity.java | 42 +++++++++++++++++++ .../http/impl/AbstractBaseHttpTest.java | 5 +++ 4 files changed, 102 insertions(+), 7 deletions(-) diff --git a/auth/jaspi/src/main/java/org/wildfly/security/auth/jaspi/impl/JaspiAuthenticationContext.java b/auth/jaspi/src/main/java/org/wildfly/security/auth/jaspi/impl/JaspiAuthenticationContext.java index b333632ca7e..99b73cf1c82 100644 --- a/auth/jaspi/src/main/java/org/wildfly/security/auth/jaspi/impl/JaspiAuthenticationContext.java +++ b/auth/jaspi/src/main/java/org/wildfly/security/auth/jaspi/impl/JaspiAuthenticationContext.java @@ -45,6 +45,7 @@ import org.wildfly.security.auth.server.ServerAuthenticationContext; import org.wildfly.security.authz.RoleMapper; import org.wildfly.security.authz.Roles; +import org.wildfly.security.cache.CachedIdentity; import org.wildfly.security.evidence.Evidence; import org.wildfly.security.evidence.PasswordGuessEvidence; import org.wildfly.security.permission.ElytronPermission; @@ -60,6 +61,7 @@ public class JaspiAuthenticationContext { private final SecurityDomain securityDomain; private final boolean integrated; + private CachedIdentity cachedIdentity = null; private volatile SecurityIdentity securityIdentity = null; private final Set roles = new HashSet<>(); @@ -69,6 +71,11 @@ public class JaspiAuthenticationContext { this.integrated = integrated; } + JaspiAuthenticationContext(SecurityDomain securityDomain, boolean integrated, CachedIdentity cachedIdentity) { + this.securityDomain = securityDomain; + this.integrated = integrated; + this.cachedIdentity = cachedIdentity; + } /* * Having a few options makes it feel like we should use a Builder, however that would lead to one more object per request. * @@ -86,6 +93,14 @@ public static JaspiAuthenticationContext newInstance(final SecurityDomain securi return new JaspiAuthenticationContext(checkNotNullParam("securityDomain", securityDomain), integrated); } + public static JaspiAuthenticationContext newInstance(final SecurityDomain securityDomain, final boolean integrated, CachedIdentity cachedIdentity) { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(CREATE_AUTH_CONTEXT); + } + return new JaspiAuthenticationContext(checkNotNullParam("securityDomain", securityDomain), integrated, cachedIdentity); + } + public CallbackHandler createCallbackHandler() { return createCommonCallbackHandler(integrated); } @@ -130,6 +145,7 @@ private void handleOne(Callback[] callbacks, int index) throws IOException, Unsu SecurityIdentity authenticated = securityDomain.authenticate(username, evidence); pvc.setResult(true); securityIdentity = authenticated; // Take a PasswordValidationCallback as always starting authentication again. + cachedIdentity = new CachedIdentity("JASPI", true, authenticated); } catch (Exception e) { log.trace("Authentication failed", e); pvc.setResult(false); @@ -144,20 +160,26 @@ private void handleOne(Callback[] callbacks, int index) throws IOException, Unsu log.tracef("Original Principal = '%s', Caller Name = '%s', Resulting Principal = '%s'", originalPrincipal, callerName, callerPrincipal); SecurityIdentity authorizedIdentity = null; - if (securityIdentity != null) { + SecurityIdentity securityIdentityToImport = null; + if (cachedIdentity != null) { + if (cachedIdentity.getSecurityIdentity() == null) { + securityIdentityToImport = securityDomain.createAdHocIdentity(cachedIdentity.getName()); + } else { + securityIdentityToImport = cachedIdentity.getSecurityIdentity(); + } if (callerPrincipal != null) { - boolean authorizationRequired = (integrated && !securityIdentity.getPrincipal().equals(callerPrincipal)); + boolean authorizationRequired = (integrated && !securityIdentityToImport.getPrincipal().equals(callerPrincipal)); // If we are integrated we want an authorization check. - authorizedIdentity = securityIdentity.createRunAsIdentity(callerPrincipal, authorizationRequired); + authorizedIdentity = cachedIdentity.getSecurityIdentity().createRunAsIdentity(callerPrincipal, authorizationRequired); } else if (integrated) { // Authorize as the authenticated identity. try (final ServerAuthenticationContext sac = securityDomain.createNewAuthenticationContext()) { - sac.importIdentity(securityIdentity); + sac.importIdentity(securityIdentityToImport); sac.authorize(); authorizedIdentity = sac.getAuthorizedIdentity(); } } else { - authorizedIdentity = securityIdentity; + authorizedIdentity = cachedIdentity.getSecurityIdentity(); } } else { if (callerPrincipal == null) { @@ -179,7 +201,7 @@ private void handleOne(Callback[] callbacks, int index) throws IOException, Unsu } if (authorizedIdentity != null) { - securityIdentity = authorizedIdentity; + cachedIdentity = new CachedIdentity("JASPI", true, authorizedIdentity); final Subject subject = cpc.getSubject(); if (subject != null && !subject.isReadOnly()) { subject.getPrincipals().add(authorizedIdentity.getPrincipal()); @@ -213,7 +235,7 @@ private void handleOne(Callback[] callbacks, int index) throws IOException, Unsu * @throws IllegalStateException if the authentication is incomplete */ public SecurityIdentity getAuthorizedIdentity() throws IllegalStateException { - SecurityIdentity securityIdentity = this.securityIdentity; + SecurityIdentity securityIdentity = this.cachedIdentity.getSecurityIdentity(); if (securityIdentity != null && roles.size() > 0) { if (log.isTraceEnabled()) { Iterator rolesIterator = roles.iterator(); diff --git a/auth/server/base/src/main/java/org/wildfly/security/authz/Roles.java b/auth/server/base/src/main/java/org/wildfly/security/authz/Roles.java index 43ba4a7bf8d..0e64039b3fc 100644 --- a/auth/server/base/src/main/java/org/wildfly/security/authz/Roles.java +++ b/auth/server/base/src/main/java/org/wildfly/security/authz/Roles.java @@ -20,11 +20,13 @@ import static org.wildfly.common.Assert.checkNotNullParam; import static org.wildfly.common.Assert.checkNotEmptyParam; + import java.util.Collections; import java.util.Iterator; import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; +import java.util.TreeSet; import java.util.function.Consumer; import org.wildfly.common.Assert; @@ -130,6 +132,30 @@ public boolean isEmpty() { }; } + /** + * Returns a set (immutable) containing roles from a roles collection. + * + * @param roles collection (not {@code null}) + * @return the set of role names (must not be {@code null}) + */ + static Set toSet(Roles roles) { + Assert.checkNotNullParam("roles", roles); + Iterator iterator = roles.iterator(); + if (!iterator.hasNext()) { + return Collections.emptySet(); + } + String role = iterator.next(); + if (!iterator.hasNext()) { + return Collections.singleton(role); + } + Set result = new TreeSet<>(); + result.add(role); + while (iterator.hasNext()) { + result.add(iterator.next()); + } + return Collections.unmodifiableSet(result); + } + /** * Construct a role set consisting of a single role. * diff --git a/auth/server/base/src/main/java/org/wildfly/security/cache/CachedIdentity.java b/auth/server/base/src/main/java/org/wildfly/security/cache/CachedIdentity.java index b142ab0a520..822b91066f4 100644 --- a/auth/server/base/src/main/java/org/wildfly/security/cache/CachedIdentity.java +++ b/auth/server/base/src/main/java/org/wildfly/security/cache/CachedIdentity.java @@ -22,8 +22,11 @@ import java.io.Serializable; import java.security.Principal; +import java.util.Collections; +import java.util.Set; import org.wildfly.security.auth.server.SecurityIdentity; +import org.wildfly.security.authz.Roles; /** * Represents a cached identity, managed by an {@link IdentityCache}. @@ -41,6 +44,7 @@ public final class CachedIdentity implements Serializable { private final boolean programmatic; private final String name; private final transient SecurityIdentity securityIdentity; + private final Set roles; /** * Creates a new instance based on the given mechanismName and securityIdentity. @@ -64,11 +68,36 @@ public CachedIdentity(String mechanismName, boolean programmatic, Principal prin this(mechanismName, programmatic, null, principal); } + /** + * Creates a new instance based on the given mechanismName and principal. + * + * @param mechanismName the name of the authentication mechanism used to authenticate/authorize the identity + * @param programmatic indicates if this identity was created as a result of programmatic authentication + * @param principal the principal of this cached identity + * @param roles the roles assigned to this cached identity + */ + public CachedIdentity(String mechanismName, boolean programmatic, Principal principal, Set roles) { + this(mechanismName, programmatic, null, principal, roles); + } + private CachedIdentity(String mechanismName, boolean programmatic, SecurityIdentity securityIdentity, Principal principal) { this.mechanismName = checkNotNullParam("mechanismName", mechanismName); this.programmatic = programmatic; this.name = checkNotNullParam("name", checkNotNullParam("principal", principal).getName()); this.securityIdentity = securityIdentity; + if (securityIdentity != null && securityIdentity.getPrincipal() != null) { + this.roles = Roles.toSet(securityIdentity.getRoles()); + } else { + this.roles = Collections.emptySet(); + } + } + + private CachedIdentity(String mechanismName, boolean programmatic, SecurityIdentity securityIdentity, Principal principal, Set roles) { + this.mechanismName = checkNotNullParam("mechanismName", mechanismName); + this.programmatic = programmatic; + this.name = checkNotNullParam("name", checkNotNullParam("principal", principal).getName()); + this.securityIdentity = securityIdentity; + this.roles = roles; } /** @@ -107,6 +136,19 @@ public boolean isProgrammatic() { return programmatic; } + /** + * Returns the roles associated with the cached identity. + * + * @return the roles associated with the cached identity. + */ + public Set getRoles() { + if (this.securityIdentity != null) { + return Roles.toSet(this.securityIdentity.getRoles()); + } else { + return this.roles; + } + } + @Override public String toString() { return "CachedIdentity{" + mechanismName + ", '" + name + "', " + securityIdentity + ", " + programmatic + "}"; diff --git a/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java b/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java index 73171b4fb48..52c7bde6181 100644 --- a/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java +++ b/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java @@ -61,6 +61,7 @@ import org.wildfly.security.auth.callback.EvidenceVerifyCallback; import org.wildfly.security.auth.callback.IdentityCredentialCallback; import org.wildfly.security.auth.server.SecurityIdentity; +import org.wildfly.security.authz.Roles; import org.wildfly.security.credential.Credential; import org.wildfly.security.credential.PasswordCredential; import org.wildfly.security.evidence.PasswordGuessEvidence; @@ -112,6 +113,10 @@ protected SecurityIdentity mockSecurityIdentity(Principal p) { public Principal getPrincipal() { return p; } + @Mock + public Roles getRoles() { + return Roles.NONE; + } }.getMockInstance(); }