/*
* 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.domain.rbac;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ACCESS_CONTROL;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.AUTO_START;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.BLOCKING;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.BYTES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CHILD_TYPE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CONTENT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DESCRIBE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ENABLED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.GROUP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATIONS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PASSWORD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PATH;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROFILE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_CHILDREN_NAMES_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_CONFIG_AS_XML_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_DESCRIPTION_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOVE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESTART;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS;
import static org.jboss.as.test.integration.management.util.ModelUtil.createOpNode;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.client.ModelControllerClientConfiguration;
import org.jboss.as.controller.client.helpers.domain.DomainClient;
import org.jboss.as.test.integration.domain.management.util.DomainTestSupport;
import org.jboss.as.test.integration.domain.management.util.WildFlyManagedConfiguration;
import org.jboss.as.test.integration.management.rbac.Outcome;
import org.jboss.as.test.integration.management.rbac.RbacAdminCallbackHandler;
import org.jboss.as.test.integration.management.rbac.RbacUtil;
import org.jboss.dmr.ModelNode;
import org.junit.AfterClass;
/**
* Base class for RBAC tests.
*
* @author Brian Stansberry (c) 2013 Red Hat Inc.
*/
public abstract class AbstractRbacTestCase {
protected static final String AUTHORIZATION = "core-service=management/access=authorization";
protected static final String DEPLOYMENT_1 = "deployment=war-example.war";
protected static final String DEPLOYMENT_2 = "deployment=rbac.txt";
protected static final byte[] DEPLOYMENT_2_CONTENT = "CONTENT".getBytes(Charset.defaultCharset());
protected static final String TEST_PATH = "path=rbac.test";
protected static final String MASTER = "master";
protected static final String SLAVE = "slave";
protected static final String SERVER_GROUP_A = "server-group-a";
protected static final String SERVER_GROUP_B = "server-group-b";
protected static final String MASTER_A = "master-a";
protected static final String SLAVE_B = "slave-b";
protected static final String SMALL_JVM = "jvm=small";
protected static final String SCOPED_ROLE_SERVER = "server-config=scoped-role-server";
private static final Map<String, String> SASL_OPTIONS = Collections.singletonMap("SASL_DISALLOWED_MECHANISMS", "JBOSS-LOCAL-USER");
private static final String TEST = "test.war";
private static final String REPLACEMENT = "test.war.v2";
private static final String SECURITY_DOMAIN = "subsystem=1/rbac-sensitive=other";
private static final String HTTP_BINDING = "socket-binding-group=sockets-a/socket-binding=http";
private static final String MEMORY_MBEAN = "core-service=platform-mbean/type=memory";
private static final String PROFILE_A = "profile=profile-a";
private static final String EXAMPLE_CONSTRAINED = "subsystem=1/rbac-constrained=default";
private static final String GENERIC_SERVER_CONFIG_ADDRESS = "host=master/server-config=*";
private static final Map<String, ModelControllerClient> nonLocalAuthclients = new HashMap<String, ModelControllerClient>();
private static final Map<String, ModelControllerClient> localAuthClients = new HashMap<String, ModelControllerClient>();
protected static DomainTestSupport testSupport;
protected static WildFlyManagedConfiguration masterClientConfig;
@AfterClass
public static void cleanUpClients() {
try {
cleanUpClients(nonLocalAuthclients);
} finally {
cleanUpClients(localAuthClients);
}
}
private static void cleanUpClients(Map<String, ModelControllerClient> clients) {
for (ModelControllerClient client : clients.values()) {
try {
client.close();
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
clients.clear();
}
protected static void deployDeployment1(DomainClient domainClient) throws IOException {
ModelNode op = createOpNode(DEPLOYMENT_1, ADD);
op.get(ENABLED).set(false);
ModelNode content = op.get(CONTENT).add();
content.get(BYTES).set(DEPLOYMENT_2_CONTENT);
RbacUtil.executeOperation(domainClient, op, Outcome.SUCCESS);
}
protected static void removeDeployment1(DomainClient domainClient) throws IOException {
ModelNode op = createOpNode(DEPLOYMENT_1, REMOVE);
RbacUtil.executeOperation(domainClient, op, Outcome.SUCCESS);
}
public ModelControllerClient getClientForUser(String userName, boolean allowLocalAuth,
WildFlyManagedConfiguration clientConfig) throws UnknownHostException {
Map<String, ModelControllerClient> clients = allowLocalAuth ? localAuthClients : nonLocalAuthclients;
ModelControllerClient result = clients.get(userName);
if (result == null) {
result = createClient(userName, allowLocalAuth, clientConfig);
clients.put(userName, result);
}
return result;
}
private ModelControllerClient createClient(String userName, boolean allowLocalAuth,
WildFlyManagedConfiguration clientConfig) throws UnknownHostException {
ModelControllerClientConfiguration config = new ModelControllerClientConfiguration.Builder()
.setProtocol(clientConfig.getHostControllerManagementProtocol())
.setHostName(clientConfig.getHostControllerManagementAddress())
.setPort(clientConfig.getHostControllerManagementPort())
.setHandler(new RbacAdminCallbackHandler(userName))
.setSaslOptions(allowLocalAuth ? Collections.<String, String>emptyMap() : SASL_OPTIONS)
.build();
return ModelControllerClient.Factory.create(config);
}
public static void removeClientForUser(String userName, boolean allowLocalAuth) throws IOException {
Map<String, ModelControllerClient> clients = allowLocalAuth ? localAuthClients : nonLocalAuthclients;
ModelControllerClient client = clients.remove(userName);
if (client != null) {
client.close();
}
}
protected abstract void configureRoles(ModelNode op, String[] roles);
boolean readOnly = false; // used by RbacSoakTest
/**
* @param expectedOutcome for standard and host-scoped roles tests, this is the expected outcome of all operations;
* for server-group-scoped roles tests, this is the expected outcome for the profile of the server group the user
* is member of, as for the other profiles and for read-config-as-xml, the outcome is well known
*/
protected void readWholeConfig(ModelControllerClient client, Outcome expectedOutcome, String... roles) throws IOException {
Outcome expectedOutcomeForReadConfigAsXml = expectedOutcome;
if (this instanceof AbstractServerGroupScopedRolesTestCase) {
expectedOutcomeForReadConfigAsXml = Outcome.UNAUTHORIZED;
}
ModelNode op = createOpNode(null, READ_CONFIG_AS_XML_OPERATION);
configureRoles(op, roles);
RbacUtil.executeOperation(client, op, expectedOutcomeForReadConfigAsXml);
// the code below calls the non-published operation 'describe'; see WFLY-2379 for more info
ModelControllerClient domainClient = testSupport.getDomainMasterLifecycleUtil().getDomainClient();
op = createOpNode(null, READ_CHILDREN_NAMES_OPERATION);
op.get(CHILD_TYPE).set(PROFILE);
ModelNode profiles = RbacUtil.executeOperation(domainClient, op, Outcome.SUCCESS);
for (ModelNode profile : profiles.get(RESULT).asList()) {
Outcome expectedOutcomeForProfile = expectedOutcome;
if (this instanceof AbstractServerGroupScopedRolesTestCase) {
expectedOutcomeForProfile = "profile-a".equals(profile.asString()) ? expectedOutcome : Outcome.HIDDEN;
}
op = createOpNode("profile=" + profile.asString(), DESCRIBE);
configureRoles(op, roles);
ModelNode result = RbacUtil.executeOperation(client, op, expectedOutcomeForProfile);
assertEquals(expectedOutcomeForProfile == Outcome.SUCCESS, result.hasDefined(RESULT));
op = createOpNode("profile=" + profile.asString(), READ_CHILDREN_NAMES_OPERATION);
op.get(CHILD_TYPE).set(SUBSYSTEM);
ModelNode subsystems = RbacUtil.executeOperation(domainClient, op, Outcome.SUCCESS);
for (ModelNode subsystem : subsystems.get(RESULT).asList()) {
op = createOpNode("profile=" + profile.asString() + "/subsystem=" + subsystem.asString(), DESCRIBE);
configureRoles(op, roles);
result = RbacUtil.executeOperation(client, op, expectedOutcomeForProfile);
assertEquals(expectedOutcomeForProfile == Outcome.SUCCESS, result.hasDefined(RESULT));
}
}
}
protected void checkStandardReads(ModelControllerClient client, String host, String server, String... roles) throws IOException {
readResource(client, DEPLOYMENT_1, host, server, Outcome.SUCCESS, roles);
readResource(client, HTTP_BINDING, host, server, Outcome.SUCCESS, roles);
}
protected ModelNode readResource(ModelControllerClient client, String address, String host, String server, Outcome expectedOutcome,
String... roles) throws IOException {
String serverPart = server == null ? "" : "/server=" + server;
String fullAddress = host == null ? address : "host=" + host + serverPart + "/" + address;
ModelNode op = createOpNode(fullAddress, READ_RESOURCE_OPERATION);
configureRoles(op, roles);
return RbacUtil.executeOperation(client, op, expectedOutcome);
}
protected void checkRootRead(ModelControllerClient client, String host, String server, Outcome expectedOutcome, String... roles) throws IOException {
String suffix = host == null ? null : "";
readResource(client, suffix, host, server, expectedOutcome, roles);
}
protected void checkSecurityDomainRead(ModelControllerClient client, String host, String server, Outcome expectedOutcome, String... roles) throws IOException {
String sdAddress = host == null ? PROFILE_A + "/" + SECURITY_DOMAIN : SECURITY_DOMAIN;
readResource(client, sdAddress, host, server, expectedOutcome, roles);
}
protected void checkSensitiveAttribute(ModelControllerClient client, String host, String server, boolean expectSuccess, String... roles) throws IOException {
String dsAddress = host == null ? PROFILE_A + "/" + EXAMPLE_CONSTRAINED
: "host=" + host + "server=" + server + "/" + EXAMPLE_CONSTRAINED;
ModelNode attrValue = readResource(client, dsAddress, host, server, Outcome.SUCCESS, roles).get(RESULT, PASSWORD);
ModelNode correct = new ModelNode();
if (expectSuccess) {
correct.set("sa");
}
assertEquals(correct, attrValue);
}
protected void runGC(ModelControllerClient client, String host, String server, Outcome expectedOutcome, String... roles) throws IOException {
String serverAddress = server == null ? "" : "/server=" + server;
String fullAddress = "host=" + host + serverAddress + "/" + MEMORY_MBEAN;
ModelNode op = createOpNode(fullAddress, "gc");
configureRoles(op, roles);
RbacUtil.executeOperation(client, op, expectedOutcome);
}
protected void addDeployment2(ModelControllerClient client, Outcome expectedOutcome, String... roles) throws IOException {
ModelNode op = createOpNode(DEPLOYMENT_2, ADD);
op.get(ENABLED).set(false);
ModelNode content = op.get(CONTENT).add();
content.get(BYTES).set(DEPLOYMENT_2_CONTENT);
configureRoles(op, roles);
RbacUtil.executeOperation(client, op, expectedOutcome);
}
protected void addPath(ModelControllerClient client, Outcome expectedOutcome, String... roles) throws IOException {
ModelNode op = createOpNode(TEST_PATH, ADD);
op.get(PATH).set("/");
configureRoles(op, roles);
RbacUtil.executeOperation(client, op, expectedOutcome);
}
protected static String getPrefixedAddress(String prefixKey, String prefixValue, String address) {
return prefixKey + "=" + prefixValue + "/" + address;
}
protected void addJvm(ModelControllerClient client, String prefixKey, String prefixValue,
Outcome expectedOutcome, String... roles) throws IOException {
String fullAddress = getPrefixedAddress(prefixKey, prefixValue, SMALL_JVM);
ModelNode op = createOpNode(fullAddress, ADD);
configureRoles(op, roles);
RbacUtil.executeOperation(client, op, expectedOutcome);
}
protected void addServerConfig(ModelControllerClient client, String host, String serverGroup,
Outcome expectedOutcome, String... roles) throws IOException {
String fullAddress = getPrefixedAddress(HOST, host, SCOPED_ROLE_SERVER);
ModelNode op = createOpNode(fullAddress, ADD);
op.get(GROUP).set(serverGroup);
op.get(AUTO_START).set(false);
configureRoles(op, roles);
RbacUtil.executeOperation(client, op, expectedOutcome);
}
protected void restartServer(ModelControllerClient client, String host, String server,
Outcome expectedOutcome, String... roles) throws IOException {
String fullAddress = String.format("host=%s/server-config=%s", host, server);
ModelNode op = createOpNode(fullAddress, RESTART);
op.get(BLOCKING).set(true);
configureRoles(op, roles);
RbacUtil.executeOperation(client, op, expectedOutcome);
}
protected ModelNode getServerConfigAccessControl(ModelControllerClient client, String... roles) throws IOException {
ModelNode op = createOpNode(GENERIC_SERVER_CONFIG_ADDRESS, READ_RESOURCE_DESCRIPTION_OPERATION);
op.get(ACCESS_CONTROL).set("trim-descriptions");
op.get(OPERATIONS).set(true);
configureRoles(op, roles);
return RbacUtil.executeOperation(client, op, Outcome.SUCCESS).get(RESULT).get(0).get(RESULT, ACCESS_CONTROL);
}
protected void removeResource(String address) throws IOException {
ModelNode op = createOpNode(address, READ_RESOURCE_OPERATION);
DomainClient domainClient = testSupport.getDomainMasterLifecycleUtil().getDomainClient();
ModelNode result = domainClient.execute(op);
if (SUCCESS.equals(result.get(OUTCOME).asString())) {
op = createOpNode(address, REMOVE);
result = domainClient.execute(op);
assertEquals(result.asString(), SUCCESS, result.get(OUTCOME).asString());
}
}
}