/*
* JBoss, Home of Professional Open Source.
* Copyright 2013, 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.
*/
package org.jboss.as.test.integration.mgmt.access;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ACCESS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.AUDIT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.AUDIT_LOG;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.AUTHORIZATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CORE_SERVICE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ENABLED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.LOGGER;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MANAGEMENT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MAP_GROUPS_TO_ROLES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROBLEM;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROVIDER;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_ATTRIBUTE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RECURSIVE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESPONSE_HEADERS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ROLLED_BACK;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SECURITY_REALM;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VALID;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VALIDATE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VALUE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOVE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ALLOW_RESOURCE_SERVICE_RESTART;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_HEADERS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VAULT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VAULT_OPTIONS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.client.OperationBuilder;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.controller.operations.common.ValidateAddressOperationHandler;
import org.jboss.as.test.integration.management.rbac.RbacUtil;
import org.jboss.as.test.integration.mgmt.access.extension.ExtensionSetup;
import org.jboss.dmr.ModelNode;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.wildfly.core.testrunner.ManagementClient;
import org.wildfly.core.testrunner.ServerSetup;
import org.wildfly.core.testrunner.ServerSetupTask;
import org.wildfly.core.testrunner.WildflyTestRunner;
import org.wildfly.test.security.VaultHandler;
/**
* @author Ladislav Thon <lthon@redhat.com>
*/
@RunWith(WildflyTestRunner.class)
@ServerSetup({BasicExtensionSetupTask.class, ValidateAddressOrOperationTestCase.VaultSetupTask.class, StandardUsersSetupTask.class})
public class ValidateAddressOrOperationTestCase extends AbstractRbacTestCase {
private static String[] LEGAL_ADDRESS_RESP_FIELDS;
private static String[] LEGAL_OPERATION_RESPONSE_FIELDS;
static class VaultSetupTask implements ServerSetupTask {
private VaultHandler vaultHandler;
private static final String RESOURCE_LOCATION = ValidateAddressOrOperationTestCase.class.getProtectionDomain().getCodeSource().getLocation().getFile()
+ "vault-validate/";
private static String DATA_SOURCE = "MaskedDS";
@Override
public void setup(ManagementClient managementClient) throws Exception {
ExtensionSetup.addResources(managementClient);
VaultHandler.cleanFilesystem(RESOURCE_LOCATION, true);
ModelNode op;
// create new vault
vaultHandler = new VaultHandler(RESOURCE_LOCATION);
// create security attributes
String vaultBlock = "ds_ExampleDS";
String attributeName = "password";
String vaultPasswordString = vaultHandler.addSecuredAttribute(vaultBlock, attributeName,
"sa".toCharArray());
String vaultPassword = "${" + vaultPasswordString + "}";
// create new vault setting in standalone
op = new ModelNode();
op.get(OP).set(ADD);
op.get(OP_ADDR).add(CORE_SERVICE, VAULT);
ModelNode vaultOption = op.get(VAULT_OPTIONS);
vaultOption.get("KEYSTORE_URL").set(vaultHandler.getKeyStore());
vaultOption.get("KEYSTORE_PASSWORD").set(vaultHandler.getMaskedKeyStorePassword());
vaultOption.get("KEYSTORE_ALIAS").set(vaultHandler.getAlias());
vaultOption.get("SALT").set(vaultHandler.getSalt());
vaultOption.get("ITERATION_COUNT").set(vaultHandler.getIterationCountAsString());
vaultOption.get("ENC_FILE_DIR").set(vaultHandler.getEncodedVaultFileDirectory());
vaultOption.get(OPERATION_HEADERS).get(ALLOW_RESOURCE_SERVICE_RESTART).set(true);
managementClient.getControllerClient().execute(new OperationBuilder(op).build());
// create new datasource with right password
ModelNode address = new ModelNode();
address.add(SUBSYSTEM, "rbac");
address.add("rbac-constrained", DATA_SOURCE);
address.protect();
op = new ModelNode();
op.get(OP).set(ADD);
op.get(OP_ADDR).set(address);
op.get("jndi-name").set("java:jboss/datasources/" + DATA_SOURCE);
op.get("user-name").set("sa");
op.get("password").set(vaultPassword);
op.get(OPERATION_HEADERS).get(ALLOW_RESOURCE_SERVICE_RESTART).set(true);
managementClient.getControllerClient().execute(new OperationBuilder(op).build());
}
@Override
public void tearDown(ManagementClient managementClient) throws Exception {
ModelNode op;
// remove created datasources
op = new ModelNode();
op.get(OP).set(REMOVE);
op.get(OP_ADDR).add(SUBSYSTEM, "rbac");
op.get(OP_ADDR).add("rbac-constrained", DATA_SOURCE);
managementClient.getControllerClient().execute(new OperationBuilder(op).build());
// remove created vault
op = new ModelNode();
op.get(OP).set(REMOVE);
op.get(OP_ADDR).add(CORE_SERVICE, VAULT);
managementClient.getControllerClient().execute(new OperationBuilder(op).build());
// remove temporary files
vaultHandler.cleanUp();
ExtensionSetup.removeResources(managementClient);
}
}
@Before
public void setupLegalFields() throws Exception {
if (LEGAL_ADDRESS_RESP_FIELDS == null) {
// See if the server is already sending response headers; if so ignore them when validating responses
ModelNode operation = Util.createOperation(READ_RESOURCE_OPERATION, PathAddress.EMPTY_ADDRESS);
ModelNode result = getManagementClient().getControllerClient().execute(operation);
if (result.has(RESPONSE_HEADERS)) {
LEGAL_ADDRESS_RESP_FIELDS = new String[]{OUTCOME, RESULT, RESPONSE_HEADERS};
LEGAL_OPERATION_RESPONSE_FIELDS = new String[]{OUTCOME, FAILURE_DESCRIPTION, ROLLED_BACK, RESPONSE_HEADERS};
} else {
LEGAL_ADDRESS_RESP_FIELDS = new String[]{OUTCOME, RESULT};
LEGAL_OPERATION_RESPONSE_FIELDS = new String[]{OUTCOME, FAILURE_DESCRIPTION, ROLLED_BACK};
}
}
}
@Test
public void testMonitor() throws Exception {
test(RbacUtil.MONITOR_USER, false, true, false, true, true);
}
@Test
public void testOperator() throws Exception {
test(RbacUtil.OPERATOR_USER, false, true, false, true, true);
}
@Test
public void testMaintainer() throws Exception {
test(RbacUtil.MAINTAINER_USER, false, true, false, true, true);
}
@Test
public void testDeployer() throws Exception {
test(RbacUtil.DEPLOYER_USER, false, true, false, true, true);
}
@Test
public void testAdministrator() throws Exception {
test(RbacUtil.ADMINISTRATOR_USER, true, true, true, true, true);
}
@Test
public void testAuditor() throws Exception {
test(RbacUtil.AUDITOR_USER, true, true, true, true, true);
}
@Test
public void testSuperUser() throws Exception {
test(RbacUtil.SUPERUSER_USER, true, true, true, true, true);
}
private void test(String userName, boolean mgmtAuthorizationExpectation, boolean auditLogExpectation,
boolean securityRealmExpectation, boolean datasourceWithPlainPasswordExpectation,
boolean datasourceWithMaskedPasswordExpectation) throws Exception {
testValidateAddress(userName, mgmtAuthorizationExpectation, auditLogExpectation, securityRealmExpectation,
datasourceWithPlainPasswordExpectation, datasourceWithMaskedPasswordExpectation);
testValidateOperation(userName, mgmtAuthorizationExpectation, auditLogExpectation, securityRealmExpectation,
datasourceWithPlainPasswordExpectation, datasourceWithMaskedPasswordExpectation);
}
private void testValidateAddress(String userName, boolean mgmtAuthorizationExpectation, boolean auditLogExpectation,
boolean securityRealmExpectation, boolean datasourceWithPlainPasswordExpectation,
boolean datasourceWithMaskedPasswordExpectation) throws Exception {
ModelControllerClient client = getClientForUser(userName);
ModelNode address = new ModelNode();
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUTHORIZATION);
validateAddress(client, address, mgmtAuthorizationExpectation);
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUDIT);
validateAddress(client, address, auditLogExpectation);
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUDIT).add(LOGGER, AUDIT_LOG);
validateAddress(client, address, auditLogExpectation);
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(SECURITY_REALM, "ManagementRealm");
validateAddress(client, address, securityRealmExpectation);
address.setEmptyList().add(SUBSYSTEM, "rbac").add("rbac-constrained", "default");
validateAddress(client, address, datasourceWithPlainPasswordExpectation);
address.setEmptyList().add(SUBSYSTEM, "rbac").add("rbac-constrained", VaultSetupTask.DATA_SOURCE);
validateAddress(client, address, datasourceWithMaskedPasswordExpectation);
}
private void testValidateOperation(String userName, boolean mgmtAuthorizationExpectation, boolean auditLogExpectation,
boolean securityRealmExpectation, boolean datasourceWithPlainPasswordExpectation,
boolean datasourceWithMaskedPasswordExpectation) throws Exception {
ModelControllerClient client = getClientForUser(userName);
ModelNode address = new ModelNode();
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUTHORIZATION);
validateOperation(client, readResource(address), mgmtAuthorizationExpectation);
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUTHORIZATION);
validateOperation(client, readAttribute(address, PROVIDER), mgmtAuthorizationExpectation);
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUTHORIZATION);
validateOperation(client, writeAttribute(address, PROVIDER, new ModelNode("simple")), mgmtAuthorizationExpectation);
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUDIT);
validateOperation(client, readResource(address), auditLogExpectation);
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUDIT).add(LOGGER, AUDIT_LOG);
validateOperation(client, readResource(address), auditLogExpectation);
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUDIT).add(LOGGER, AUDIT_LOG);
validateOperation(client, readAttribute(address, ENABLED), auditLogExpectation);
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUDIT).add(LOGGER, AUDIT_LOG);
validateOperation(client, writeAttribute(address, ENABLED, new ModelNode(true)), auditLogExpectation);
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(SECURITY_REALM, "ManagementRealm");
validateOperation(client, readResource(address), securityRealmExpectation);
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(SECURITY_REALM, "ManagementRealm");
validateOperation(client, readAttribute(address, MAP_GROUPS_TO_ROLES), securityRealmExpectation);
address.setEmptyList().add(CORE_SERVICE, MANAGEMENT).add(SECURITY_REALM, "ManagementRealm");
validateOperation(client, writeAttribute(address, MAP_GROUPS_TO_ROLES, new ModelNode(true)), securityRealmExpectation);
address.setEmptyList().add(SUBSYSTEM, "rbac").add("rbac-constrained", "default");
validateOperation(client, readResource(address), datasourceWithPlainPasswordExpectation);
address.setEmptyList().add(SUBSYSTEM, "rbac").add("rbac-constrained", "default");
validateOperation(client, readAttribute(address, "password"), datasourceWithPlainPasswordExpectation);
address.setEmptyList().add(SUBSYSTEM, "rbac").add("rbac-constrained", "default");
validateOperation(client, writeAttribute(address, "password", new ModelNode("new-password")), datasourceWithPlainPasswordExpectation);
address.setEmptyList().add(SUBSYSTEM, "rbac").add("rbac-constrained", "MaskedDS");
validateOperation(client, readResource(address), datasourceWithMaskedPasswordExpectation);
address.setEmptyList().add(SUBSYSTEM, "rbac").add("rbac-constrained", "MaskedDS");
validateOperation(client, readAttribute(address, "password"), datasourceWithMaskedPasswordExpectation);
address.setEmptyList().add(SUBSYSTEM, "rbac").add("rbac-constrained", "MaskedDS");
validateOperation(client, writeAttribute(address, "password", new ModelNode("new-password")), datasourceWithMaskedPasswordExpectation);
}
// test utils
private static ModelNode readResource(ModelNode address) {
ModelNode readResource = new ModelNode();
readResource.get(OP).set(READ_RESOURCE_OPERATION);
readResource.get(OP_ADDR).set(address);
readResource.get(RECURSIVE).set(true);
return readResource;
}
private static ModelNode readAttribute(ModelNode address, String attribute) {
ModelNode readAttribute = new ModelNode();
readAttribute.get(OP).set(READ_ATTRIBUTE_OPERATION);
readAttribute.get(OP_ADDR).set(address);
readAttribute.get(NAME).set(attribute);
return readAttribute;
}
private static ModelNode writeAttribute(ModelNode address, String attribute, ModelNode value) {
ModelNode readAttribute = new ModelNode();
readAttribute.get(OP).set(WRITE_ATTRIBUTE_OPERATION);
readAttribute.get(OP_ADDR).set(address);
readAttribute.get(NAME).set(attribute);
readAttribute.get(VALUE).set(value);
return readAttribute;
}
private static void validateAddress(ModelControllerClient client, ModelNode address, boolean expectedOutcome) throws IOException {
ModelNode operation = Util.createOperation(ValidateAddressOperationHandler.OPERATION_NAME, PathAddress.EMPTY_ADDRESS);
operation.get(VALUE).set(address);
ModelNode result = client.execute(operation);
assertModelNodeOnlyContainsKeys(result, LEGAL_ADDRESS_RESP_FIELDS);
assertModelNodeOnlyContainsKeys(result.get(RESULT), VALID, PROBLEM);
assertEquals(result.toString(), expectedOutcome, result.get(RESULT, VALID).asBoolean());
assertEquals(result.toString(), !expectedOutcome, result.get(RESULT).hasDefined(PROBLEM));
if (!expectedOutcome) {
assertTrue(result.get(RESULT, PROBLEM).asString().contains("WFLYCTL0335"));
}
}
private static void validateOperation(ModelControllerClient client, ModelNode validatedOperation, boolean expectedOutcome) throws IOException {
ModelNode operation = Util.createOperation(VALIDATE_OPERATION, PathAddress.EMPTY_ADDRESS);
operation.get(VALUE).set(validatedOperation);
ModelNode result = client.execute(operation);
assertModelNodeOnlyContainsKeys(result, LEGAL_OPERATION_RESPONSE_FIELDS);
if (expectedOutcome) {
assertEquals(result.toString(), SUCCESS, result.get(OUTCOME).asString());
} else {
assertEquals(FAILED, result.get(OUTCOME).asString());
assertTrue(result.toString(), result.get(FAILURE_DESCRIPTION).asString().contains("WFLYCTL0335"));
}
}
private static void assertModelNodeOnlyContainsKeys(ModelNode modelNode, String... keys) {
Collection<String> expectedKeys = Arrays.asList(keys);
Set<String> actualKeys = new HashSet<String>(modelNode.keys()); // need copy for modifications
actualKeys.removeAll(expectedKeys);
if (!actualKeys.isEmpty()) {
fail("ModelNode contained additional keys: " + actualKeys + " " + modelNode);
}
}
}