Skip to content

Commit

Permalink
[WFCORE-6411] Make it possible to use JaasSecurityRealm via a custom-…
Browse files Browse the repository at this point in the history
…realm resource
  • Loading branch information
Skyllarr committed Jul 25, 2023
1 parent e26bec0 commit 20e5f0e
Show file tree
Hide file tree
Showing 6 changed files with 402 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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.extension.elytron;

import org.jboss.as.controller.services.path.PathManager;
import org.jboss.msc.service.StartException;
import org.jboss.msc.value.InjectedValue;
import org.wildfly.security.auth.SupportLevel;
import org.wildfly.security.auth.realm.JaasSecurityRealm;
import org.wildfly.security.auth.server.RealmIdentity;
import org.wildfly.security.auth.server.RealmUnavailableException;
import org.wildfly.security.auth.server.SecurityRealm;
import org.wildfly.security.auth.server.event.RealmEvent;
import org.wildfly.security.credential.Credential;
import org.wildfly.security.evidence.Evidence;

import javax.security.auth.callback.CallbackHandler;
import java.io.File;
import java.security.Principal;
import java.security.PrivilegedExceptionAction;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Map;
import java.util.function.Function;

import static org.wildfly.extension.elytron.ClassLoadingAttributeDefinitions.resolveClassLoader;
import static org.wildfly.extension.elytron.FileAttributeDefinitions.pathResolver;
import static org.wildfly.extension.elytron.SecurityActions.doPrivileged;
import static org.wildfly.extension.elytron._private.ElytronSubsystemMessages.ROOT_LOGGER;

/**
* Wrapper for JAAS REALM so it can be defined as a custom realm resource
*/
public class JaasCustomSecurityRealmWrapper implements SecurityRealm {
private JaasSecurityRealm jaasSecurityRealm;

// receiving configuration from subsystem
public void initialize(Map<String, String> configuration) throws StartException {

String entry = configuration.get("entry");
if (entry == null || entry.isEmpty()) {
throw ROOT_LOGGER.jaasEntryNotDefined();
}
String pathParam = configuration.get("path");
String relativeToParam = configuration.get("relative-to");
String moduleNameParam = configuration.get("module");
String callbackHandlerName = configuration.get("callbackHandlerName");

String rootPath = null;
FileAttributeDefinitions.PathResolver pathResolver;
InjectedValue<PathManager> pathManagerInjector = new InjectedValue<>();
if (pathParam != null) {
pathResolver = pathResolver();
File jaasConfigFile = pathResolver.path(pathParam).relativeTo(relativeToParam, pathManagerInjector.getOptionalValue()).resolve();
if (!jaasConfigFile.exists()) {
throw ROOT_LOGGER.jaasFileDoesNotExist(jaasConfigFile.getPath());
}
rootPath = jaasConfigFile.getPath();
}

CallbackHandler callbackhandler = null;
ClassLoader classLoader;
try {
classLoader = doPrivileged((PrivilegedExceptionAction<ClassLoader>) () -> resolveClassLoader(moduleNameParam));
if (callbackHandlerName != null) {
Class<?> typeClazz = classLoader.loadClass(callbackHandlerName);
callbackhandler = (CallbackHandler) typeClazz.getDeclaredConstructor().newInstance();
}
} catch (Exception e) {
throw ROOT_LOGGER.failedToLoadCallbackhandlerFromProvidedModule();
}
this.jaasSecurityRealm = new JaasSecurityRealm(entry, rootPath, classLoader, callbackhandler);
}

@Override
public RealmIdentity getRealmIdentity(Principal principal) throws RealmUnavailableException {
return jaasSecurityRealm.getRealmIdentity(principal);
}

@Override
public RealmIdentity getRealmIdentity(Evidence evidence) throws RealmUnavailableException {
return jaasSecurityRealm.getRealmIdentity(evidence);
}

@Override
public RealmIdentity getRealmIdentity(Evidence evidence, Function<Principal, Principal> principalTransformer) throws RealmUnavailableException {
return jaasSecurityRealm.getRealmIdentity(evidence, principalTransformer);
}

@Override
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
return jaasSecurityRealm.getCredentialAcquireSupport(credentialType, algorithmName);
}

@Override
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException {
return jaasSecurityRealm.getCredentialAcquireSupport(credentialType, algorithmName, parameterSpec);
}

@Override
public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException {
return jaasSecurityRealm.getEvidenceVerifySupport(evidenceType, algorithmName);
}

@Override
public void handleRealmEvent(RealmEvent event) {
jaasSecurityRealm.handleRealmEvent(event);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ public interface ElytronSubsystemMessages extends BasicLogger {
@Message(id = 48, value = "A string representation of an X.500 distinguished name is required: %s")
IllegalArgumentException representationOfX500IsRequired(String causeMessage);

@Message(id = 49, value = "Entry is not defined.")
StartException jaasEntryNotDefined();

/*
* Credential Store Section.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2023 Red Hat, Inc.
*
* 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.test.security.common.elytron;

import org.jboss.as.test.integration.management.util.CLIWrapper;
import org.jboss.as.test.shared.TestSuiteEnvironment;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.wildfly.core.testrunner.WildflyTestRunner;

import java.io.File;

/**
* Tests testing JaasSecurityRealm via custom realm resource
*/
@RunWith(WildflyTestRunner.class)
public class JaasCustomRealmWrapperTest {

@ClassRule
public static TemporaryFolder tmpDir = new TemporaryFolder();

@BeforeClass
public static void setup() throws Exception {
JavaArchive jar = ShrinkWrap.create(JavaArchive.class, "testJaasCustomRealm.jar").addAsResource(new StringAsset("Dependencies: org.wildfly.security"), "META-INF/MANIFEST.MF").addClasses(TestLoginModule.class, TestCallbackHandler.class);
File jarFile = new File(tmpDir.getRoot(), "testJaasCustomRealm.jar");
jar.as(ZipExporter.class).exportTo(jarFile, true);
CLIWrapper cli = new CLIWrapper(true);
cli.sendLine("module add --name=" + "jaasWrapperModule " + " --resources=" + TestSuiteEnvironment.getSystemProperty("jboss.dist", null) + "/modules/system/layers/base/org/wildfly/extension/elytron " + " --dependencies=org.wildfly.extension.elytron,org.wildfly.security.elytron");
cli.sendLine("module add --name=" + "jaasLoginModule " + " --resources=" + jarFile.getAbsolutePath() + " --dependencies=org.wildfly.security.elytron");
}

@AfterClass
public static void cleanUp() throws Exception {
CLIWrapper cli = new CLIWrapper(true);
cli.sendLine("module remove --name=" + "jaasWrapperModule");
cli.sendLine("module remove --name=" + "jaasLoginModule");
cli.sendLine("reload");
}

@Test
public void testAddJaasRealmAsCustomRealm() throws Exception {
CLIWrapper cli = new CLIWrapper(true);
cli.sendLine("/subsystem=elytron/custom-realm=customJaasWrapperRealm:add(" + "module=jaasWrapperModule," + "class-name=org.wildfly.extension.elytron.JaasCustomSecurityRealmWrapper," +
"configuration={entry=Entry1,module=jaasLoginModule,callback-handler=org.wildfly.test.integration.elytron.realm.TestCallbackHandler," + "path=" + JaasCustomRealmWrapperTest.class.getResource("jaas-login.config").getFile() + "})");
Assert.assertTrue(cli.readAllAsOpResult().isIsOutcomeSuccess());
cli.sendLine("/subsystem=elytron/security-domain=jaasTestDomain:add(realms=[{realm=customJaasWrapperRealm}]," + "default-realm=customJaasWrapperRealm, permission-mapper=default-permission-mapper)");
Assert.assertTrue(cli.readAllAsOpResult().isIsOutcomeSuccess());
cli.sendLine("/subsystem=elytron/security-domain=jaasTestDomain:remove");
cli.sendLine("/subsystem=elytron/custom-realm=customJaasWrapperRealm:remove");
}

@Test
public void testJaasRealmHasToContainEntry() {
CLIWrapper cli = null;
try {
cli = new CLIWrapper(true);
} catch (Exception e) {
throw new RuntimeException(e);
}
try {
cli.sendLine("/subsystem=elytron/custom-realm=customJaasWrapperRealm:add(" + "module=jaasWrapperModule," + "class-name=org.wildfly.extension.elytron.JaasCustomSecurityRealmWrapper," +
"configuration={module=jaasLoginModule,callback-handler=org.wildfly.test.integration.elytron.realm.TestCallbackHandler," + "path=" + JaasCustomRealmWrapperTest.class.getResource("jaas-login.config").getFile() + "})");
Assert.fail();
} catch (AssertionError e) {
// ignore
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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.test.security.common.elytron;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.security.Principal;

import org.wildfly.security.evidence.Evidence;
import org.wildfly.security.evidence.PasswordGuessEvidence;

/**
* A custom {@link javax.security.auth.callback.CallbackHandler} used in the JAAS security realm tests. It implements the
* {@code setSecurityInfo} method that has been historically used to populate custom handlers. Also, its {@code handle}
* implementation will handle any kind of credential by calling {@code toString} and then {@code toCharArray} on the opaque
* object.
*
* @author <a href="mailto:[email protected]">Stefan Guilhen</a>
*/
public class TestCallbackHandler implements CallbackHandler {

private Principal principal;
private Evidence evidence;

public TestCallbackHandler() {
}

/**
* Sets this handler's state.
*
* @param principal the principal being authenticated.
* @param evidence the evidence being verified.
*/
public void setSecurityInfo(final Principal principal, final Object evidence) {
this.principal = principal;
this.evidence = (Evidence) evidence;
}

@Override
public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
if (callbacks == null)
throw new IllegalArgumentException("The callbacks argument cannot be null");

for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
NameCallback nameCallback = (NameCallback) callback;
if (principal != null)
nameCallback.setName(this.principal.getName());
}
else if (callback instanceof PasswordCallback) {
PasswordCallback passwordCallback = (PasswordCallback) callback;
if (this.evidence instanceof PasswordGuessEvidence) {
passwordCallback.setPassword(((PasswordGuessEvidence) this.evidence).getGuess());
}
}
else {
throw new UnsupportedCallbackException(callback, "Unsupported callback");
}
}
}
}
Loading

0 comments on commit 20e5f0e

Please sign in to comment.