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 Aug 8, 2023
1 parent 3e12aaa commit 1b975a2
Show file tree
Hide file tree
Showing 9 changed files with 456 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>

<!--
~ JBoss, Home of Professional Open Source.
~ Copyright 2015, Red Hat, Inc., and individual contributors
~ as indicated by the @author tags. See the copyright.txt file in the
~ distribution for a full listing of individual contributors.
~
~ This is free software; you can redistribute it and/or modify it
~ under the terms of the GNU Lesser General Public License as
~ published by the Free Software Foundation; either version 2.1 of
~ the License, or (at your option) any later version.
~
~ This software is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
~ Lesser General Public License for more details.
~
~ You should have received a copy of the GNU Lesser General Public
~ License along with this software; if not, write to the Free
~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-->

<module xmlns="urn:jboss:module:1.9" name="org.wildfly.extension.elytron.jaas-realm">

<!-- This module is deprecated, and you should use the jaas-realm resource instead of this module. -->
<properties>
<property name="jboss.api" value="deprecated"/>
</properties>

<exports>
<exclude path="org/wildfly/extension/elytron/JaasCustomSecurityRealmWrapper"/>
</exports>

<resources>
<artifact name="${org.wildfly.core:wildfly-elytron-integration}"/>
</resources>

<dependencies>
<module name="java.logging"/>
<module name="org.jboss.as.controller"/>
<module name="org.jboss.as.server"/>
<module name="org.jboss.modules"/>
<module name="org.jboss.msc"/>
<module name="org.jboss.logging"/>
<module name="org.jboss.logmanager"/>
<module name="org.wildfly.common"/>
<module name="org.wildfly.security.elytron-private" export="true"/>
</dependencies>
</module>
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
<packages>
<!-- required by default configuration-->
<package name="org.wildfly.openssl"/>
<package name="org.wildfly.extension.elytron.jaas-realm"/>
</packages>
</layer-spec>
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* 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
*
* @deprecated Use a jaas-realm resource instead
*/
@Deprecated
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,91 @@
/*
* 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.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.Assume;
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 {
Assume.assumeTrue("Skip for bootable jar", System.getProperty("ts.bootable") == null);
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=jaasLoginModule --resources=" + jarFile.getAbsolutePath() + " --dependencies=org.wildfly.security.elytron,org.wildfly.extension.elytron");
}

@Test
public void testAddJaasRealmAsCustomRealm() throws Exception {
CLIWrapper cli = new CLIWrapper(true);
cli.sendLine("/subsystem=elytron/custom-realm=customJaasWrapperRealm:add(module=org.wildfly.extension.elytron.jaas-realm, 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=org.wildfly.extension.elytron.jaas-realm,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
}
}

@AfterClass
public static void cleanUp() throws Exception {
if (System.getProperty("ts.bootable") == null) {
CLIWrapper cli = new CLIWrapper(true);
cli.sendLine("module remove --name=jaasLoginModule");
cli.sendLine("reload");
}
}
}
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 1b975a2

Please sign in to comment.