From c84d020454df58bf17ad88eb7c9b578d40dafe6c Mon Sep 17 00:00:00 2001 From: Diana Krepinska Date: Fri, 4 Aug 2023 11:19:38 +0200 Subject: [PATCH] [ELY-2583] Make requestURI and Source-Address available from RealmSuccessfulAuthenticationEvent and RealmFailedAuthenticationEvent --- .../server/RequestInformationCallback.java | 45 +++++++ .../server/ServerAuthenticationContext.java | 55 +++++++- ...tMechanismInformationMechanismFactory.java | 5 +- .../security/auth/server/RealmEventTest.java | 121 ++++++++++++++++++ .../security/auth/server/TestCustomRealm.java | 112 ++++++++++++++++ 5 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 auth/server/base/src/main/java/org/wildfly/security/auth/server/RequestInformationCallback.java create mode 100644 tests/base/src/test/java/org/wildfly/security/auth/server/RealmEventTest.java create mode 100644 tests/base/src/test/java/org/wildfly/security/auth/server/TestCustomRealm.java diff --git a/auth/server/base/src/main/java/org/wildfly/security/auth/server/RequestInformationCallback.java b/auth/server/base/src/main/java/org/wildfly/security/auth/server/RequestInformationCallback.java new file mode 100644 index 00000000000..406279dfed4 --- /dev/null +++ b/auth/server/base/src/main/java/org/wildfly/security/auth/server/RequestInformationCallback.java @@ -0,0 +1,45 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2023 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.auth.server; + +import org.wildfly.security.auth.callback.ExtendedCallback; + +import java.net.URI; + +/** + * A {@link javax.security.auth.callback.Callback} to inform a server authentication context about current authenticaiton request. + * + */ +public class RequestInformationCallback implements ExtendedCallback { + + /** + * request URI + */ + private final URI requestUri; + + public RequestInformationCallback(URI requestUri) { + this.requestUri = requestUri; + } + + /** + * @return the request URI + */ + public URI getRequestUri() { + return this.requestUri; + } +} diff --git a/auth/server/base/src/main/java/org/wildfly/security/auth/server/ServerAuthenticationContext.java b/auth/server/base/src/main/java/org/wildfly/security/auth/server/ServerAuthenticationContext.java index 21196ad1140..6b98f9382fa 100644 --- a/auth/server/base/src/main/java/org/wildfly/security/auth/server/ServerAuthenticationContext.java +++ b/auth/server/base/src/main/java/org/wildfly/security/auth/server/ServerAuthenticationContext.java @@ -311,6 +311,7 @@ */ public final class ServerAuthenticationContext implements AutoCloseable { + public static final String REQUEST_URI = "Request-URI"; private final AtomicReference stateRef; ServerAuthenticationContext(final SecurityDomain domain, final MechanismConfigurationSelector mechanismConfigurationSelector) { @@ -1162,6 +1163,13 @@ private void handleOne(final Callback[] callbacks, final int idx) throws IOExcep AuthenticationConfigurationCallback authenticationConfigurationCallback = (AuthenticationConfigurationCallback) callback; saslSkipCertificateVerification = authenticationConfigurationCallback.getSaslSkipCertificateVerification(); handleOne(callbacks, idx + 1); + } else if (callback instanceof RequestInformationCallback) { + RequestInformationCallback requestInformationCallback = (RequestInformationCallback) callback; + String requestUri = requestInformationCallback.getRequestUri() != null ? requestInformationCallback.getRequestUri().toString() : null; + Attributes runtimeAttributes = new MapAttributes(); + runtimeAttributes.addFirst(REQUEST_URI, requestUri); + addRuntimeAttributes(runtimeAttributes); + handleOne(callbacks, idx + 1); } else { CallbackUtil.unsupported(callback); @@ -2107,15 +2115,58 @@ void succeed() { void fail(final boolean requireInProgress) { final SecurityIdentity capturedIdentity = getSourceIdentity(); final AtomicReference stateRef = getStateRef(); - if (! stateRef.compareAndSet(this, FAILED)) { + if (!stateRef.compareAndSet(this, FAILED)) { stateRef.get().fail(requireInProgress); return; } - SecurityRealm.safeHandleRealmEvent(getRealmInfo().getSecurityRealm(), new RealmFailedAuthenticationEvent(realmIdentity, null, null)); + SecurityRealm.safeHandleRealmEvent(getRealmInfo().getSecurityRealm(), new RealmFailedAuthenticationEvent(getRealmIdentityWithRuntimeAttributes(), null, null)); SecurityDomain.safeHandleSecurityEvent(capturedIdentity.getSecurityDomain(), new SecurityAuthenticationFailedEvent(capturedIdentity, realmIdentity.getRealmIdentityPrincipal())); realmIdentity.dispose(); } + private RealmIdentity getRealmIdentityWithRuntimeAttributes() { + return new RealmIdentity() { + @Override + public Principal getRealmIdentityPrincipal() { + return realmIdentity.getRealmIdentityPrincipal(); + } + + @Override + public SupportLevel getCredentialAcquireSupport(Class credentialType, String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException { + return realmIdentity.getCredentialAcquireSupport(credentialType, algorithmName, parameterSpec); + } + + @Override + public C getCredential(Class credentialType) throws RealmUnavailableException { + return realmIdentity.getCredential(credentialType); + } + + @Override + public SupportLevel getEvidenceVerifySupport(Class evidenceType, String algorithmName) throws RealmUnavailableException { + return realmIdentity.getEvidenceVerifySupport(evidenceType, algorithmName); + } + + @Override + public boolean verifyEvidence(Evidence evidence) throws RealmUnavailableException { + return realmIdentity.verifyEvidence(evidence); + } + + @Override + public boolean exists() throws RealmUnavailableException { + return realmIdentity.exists(); + } + + @Override + public AuthorizationIdentity getAuthorizationIdentity() throws RealmUnavailableException { + if (realmIdentity.exists()) { + return AuthorizationIdentity.basicIdentity(realmIdentity.getAuthorizationIdentity(), runtimeAttributes); + } else { + return AuthorizationIdentity.basicIdentity(AuthorizationIdentity.EMPTY, runtimeAttributes); + } + } + }; + } + @Override void setPrincipal(final Principal principal, final boolean exclusive) { if (isSamePrincipal(principal)) { diff --git a/http/util/src/main/java/org/wildfly/security/http/util/SetMechanismInformationMechanismFactory.java b/http/util/src/main/java/org/wildfly/security/http/util/SetMechanismInformationMechanismFactory.java index 30478006ce8..5536c61d577 100644 --- a/http/util/src/main/java/org/wildfly/security/http/util/SetMechanismInformationMechanismFactory.java +++ b/http/util/src/main/java/org/wildfly/security/http/util/SetMechanismInformationMechanismFactory.java @@ -20,6 +20,7 @@ import static org.wildfly.security.http.HttpConstants.HOST; import static org.wildfly.security.http.util.ElytronMessages.log; +import java.net.URI; import java.util.Map; import javax.security.auth.callback.Callback; @@ -27,6 +28,7 @@ import org.wildfly.security.auth.callback.MechanismInformationCallback; import org.wildfly.security.auth.server.MechanismInformation; +import org.wildfly.security.auth.server.RequestInformationCallback; import org.wildfly.security.http.HttpAuthenticationException; import org.wildfly.security.http.HttpServerAuthenticationMechanism; import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory; @@ -73,6 +75,7 @@ public String getMechanismName() { public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException { String host = request.getFirstRequestHeaderValue(HOST); String resolvedHostName = null; + URI requestedUri = request.getRequestURI(); if (host != null) { if (host.startsWith("[")) { int close = host.indexOf(']'); @@ -110,7 +113,7 @@ public String getMechanismName() { public String getHostName() { return hostName; } - })}); + }), new RequestInformationCallback(request.getRequestURI())}); } catch (Throwable e) { throw log.unableToLocateMechanismConfiguration(e).toHttpAuthenticationException(); diff --git a/tests/base/src/test/java/org/wildfly/security/auth/server/RealmEventTest.java b/tests/base/src/test/java/org/wildfly/security/auth/server/RealmEventTest.java new file mode 100644 index 00000000000..895fba183ba --- /dev/null +++ b/tests/base/src/test/java/org/wildfly/security/auth/server/RealmEventTest.java @@ -0,0 +1,121 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2023 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.auth.server; + +import org.junit.Test; +import org.wildfly.security.auth.callback.MechanismInformationCallback; +import org.wildfly.security.auth.permission.LoginPermission; +import org.wildfly.security.authz.Attributes; +import org.wildfly.security.authz.MapAttributes; +import org.wildfly.security.credential.PasswordCredential; +import org.wildfly.security.password.PasswordFactory; +import org.wildfly.security.password.WildFlyElytronPasswordProvider; +import org.wildfly.security.password.interfaces.ClearPassword; +import org.wildfly.security.password.spec.ClearPasswordSpec; +import org.wildfly.security.permission.PermissionVerifier; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.security.spec.InvalidKeySpecException; + +import static org.junit.Assert.fail; +import static org.wildfly.security.authz.RoleDecoder.KEY_SOURCE_ADDRESS; + +public class RealmEventTest { + + private static SecurityDomain usersDomain; + private static TestCustomRealm.CustomRealm usersRealm; + private static final Provider provider = WildFlyElytronPasswordProvider.getInstance(); + + private ServerAuthenticationContext setupAndGetServerAuthenticationContext() throws IOException, UnsupportedCallbackException { + Security.addProvider(provider); + + usersRealm = new TestCustomRealm.CustomRealm(); + SecurityDomain.Builder builder = SecurityDomain.builder(); + builder.addRealm("users", usersRealm).build(); + builder.setDefaultRealmName("users"); + builder.setPermissionMapper((permissionMappable, roles) -> PermissionVerifier.from(new LoginPermission())); + usersDomain = builder.build(); + + ServerAuthenticationContext serverAuthenticationContext = usersDomain.createNewAuthenticationContext(); + serverAuthenticationContext.addRuntimeAttributes(createRuntimeAttributesWithSourceAddress()); + + MechanismInformation mechanismInformation = new MechanismInformation() { + @Override + public String getMechanismType() { + return null; + } + + @Override + public String getMechanismName() { + return null; + } + + @Override + public String getHostName() { + return null; + } + + @Override + public String getProtocol() { + return null; + } + + }; + + CallbackHandler callbackHandler = serverAuthenticationContext.createCallbackHandler(); + callbackHandler.handle(new MechanismInformationCallback[]{new MechanismInformationCallback(mechanismInformation)}); + return serverAuthenticationContext; + } + + @Test + public void testRealmSuccessfulAuthenticationEvent() throws IOException, UnsupportedCallbackException { + ServerAuthenticationContext serverAuthenticationContext = setupAndGetServerAuthenticationContext(); + try { + serverAuthenticationContext.setAuthenticationName("myadmin"); + serverAuthenticationContext.addPublicCredential(new PasswordCredential( + PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR).generatePassword( + new ClearPasswordSpec("mypassword".toCharArray())))); + + serverAuthenticationContext.authorize(); + } catch (RealmUnavailableException | InvalidKeySpecException | NoSuchAlgorithmException e) { + fail(); + } + serverAuthenticationContext.succeed(); + } + + @Test + public void testRealmFailedAuthenticationEvent() throws NoSuchAlgorithmException, IOException, UnsupportedCallbackException, InvalidKeySpecException { + ServerAuthenticationContext serverAuthenticationContext = setupAndGetServerAuthenticationContext(); + serverAuthenticationContext.setAuthenticationName("myadmin"); + serverAuthenticationContext.addPublicCredential(new PasswordCredential( + PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR).generatePassword( + new ClearPasswordSpec("wrongPassword".toCharArray())))); + serverAuthenticationContext.fail(); + } + + private Attributes createRuntimeAttributesWithSourceAddress() { + MapAttributes runtimeAttributes = new MapAttributes(); + runtimeAttributes.addFirst(KEY_SOURCE_ADDRESS, "10.12.14.16"); + return runtimeAttributes; + } +} diff --git a/tests/base/src/test/java/org/wildfly/security/auth/server/TestCustomRealm.java b/tests/base/src/test/java/org/wildfly/security/auth/server/TestCustomRealm.java new file mode 100644 index 00000000000..4bf9cccffd2 --- /dev/null +++ b/tests/base/src/test/java/org/wildfly/security/auth/server/TestCustomRealm.java @@ -0,0 +1,112 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2023 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.auth.server; + +import org.wildfly.security.auth.SupportLevel; +import org.wildfly.security.auth.server.event.RealmEvent; +import org.wildfly.security.auth.server.event.RealmFailedAuthenticationEvent; +import org.wildfly.security.auth.server.event.RealmSuccessfulAuthenticationEvent; +import org.wildfly.security.credential.Credential; +import org.wildfly.security.evidence.Evidence; +import org.wildfly.security.evidence.PasswordGuessEvidence; + +import java.security.Principal; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class TestCustomRealm { + + public static class CustomRealm implements SecurityRealm { + + // this realm does not allow acquiring credentials + public SupportLevel getCredentialAcquireSupport(Class credentialType, String algorithmName, + AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException { + return SupportLevel.UNSUPPORTED; + } + + // this realm will be able to verify password evidences only + public SupportLevel getEvidenceVerifySupport(Class evidenceType, String algorithmName) + throws RealmUnavailableException { + return PasswordGuessEvidence.class.isAssignableFrom(evidenceType) ? SupportLevel.POSSIBLY_SUPPORTED : SupportLevel.UNSUPPORTED; + } + + public RealmIdentity getRealmIdentity(final Principal principal) throws RealmUnavailableException { + + if ("myadmin".equals(principal.getName())) { // identity "myadmin" will have password "mypassword" + return new RealmIdentity() { + public Principal getRealmIdentityPrincipal() { + return principal; + } + + public SupportLevel getCredentialAcquireSupport(Class credentialType, + String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException { + return SupportLevel.UNSUPPORTED; + } + + public C getCredential(Class credentialType) throws RealmUnavailableException { + return null; + } + + public SupportLevel getEvidenceVerifySupport(Class evidenceType, String algorithmName) { + return PasswordGuessEvidence.class.isAssignableFrom(evidenceType) ? SupportLevel.SUPPORTED : SupportLevel.UNSUPPORTED; + } + + // evidence will be accepted if it is password "mypassword" + public boolean verifyEvidence(Evidence evidence) { + if (evidence instanceof PasswordGuessEvidence) { + PasswordGuessEvidence guess = (PasswordGuessEvidence) evidence; + try { + return Arrays.equals("mypassword".toCharArray(), guess.getGuess()); + + } finally { + guess.destroy(); + } + } + return false; + } + + public boolean exists() { + return true; + } + }; + } + return RealmIdentity.NON_EXISTENT; + } + + @Override + public void handleRealmEvent(RealmEvent event) { + if (event instanceof RealmSuccessfulAuthenticationEvent) { + assertEquals("10.12.14.16", ((RealmSuccessfulAuthenticationEvent) event).getAuthorizationIdentity().getRuntimeAttributes().get("Source-Address").get(0)); + assertEquals("testURI", ((RealmSuccessfulAuthenticationEvent) event).getAuthorizationIdentity().getRuntimeAttributes().get("Request-URI").get(0)); + assertEquals("myadmin", ((RealmSuccessfulAuthenticationEvent) event).getRealmIdentity().getRealmIdentityPrincipal()); + } + if (event instanceof RealmFailedAuthenticationEvent) { + try { + assertEquals("10.12.14.16", ((RealmFailedAuthenticationEvent) event).getRealmIdentity().getAuthorizationIdentity().getRuntimeAttributes().get("Source-Address").get(0)); + assertEquals("testURI", ((RealmFailedAuthenticationEvent) event).getRealmIdentity().getAuthorizationIdentity().getRuntimeAttributes().get("Request-URI").get(0)); + assertEquals("myadmin", ((RealmSuccessfulAuthenticationEvent) event).getRealmIdentity().getRealmIdentityPrincipal()); + } catch (RealmUnavailableException e) { + fail("RealmFailedAuthenticationEvent should have runtime attributes associated"); + } + } + } + } +}