/*
* Copyright © 2016 Cask Data, 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 co.cask.cdap.cli;
import co.cask.cdap.StandaloneTester;
import co.cask.cdap.common.conf.Constants;
import co.cask.cdap.gateway.handlers.InMemoryAuthorizer;
import co.cask.cdap.internal.test.AppJarHelper;
import co.cask.cdap.proto.id.NamespaceId;
import co.cask.cdap.proto.security.Action;
import co.cask.cdap.proto.security.Principal;
import co.cask.cdap.proto.security.Role;
import co.cask.cdap.security.server.BasicAuthenticationHandler;
import co.cask.common.cli.CLI;
import org.apache.twill.filesystem.LocalLocationFactory;
import org.apache.twill.filesystem.Location;
import org.apache.twill.filesystem.LocationFactory;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
/**
* Tests authorization CLI commands. These tests are in their own class because they need authorization enabled.
*/
public class AuthorizationCLITest extends CLITestBase {
/**
* An {@link ExternalResource} that wraps a {@link TemporaryFolder} and {@link StandaloneTester} to execute them in
* a chain.
*/
private static final class StandaloneTesterWithAuthorization extends ExternalResource {
private final TemporaryFolder tmpFolder = new TemporaryFolder();
private StandaloneTester standaloneTester;
@Override
public Statement apply(final Statement base, final Description description) {
// Apply the TemporaryFolder on a Statement that creates a StandaloneTester and applies on base
return tmpFolder.apply(new Statement() {
@Override
public void evaluate() throws Throwable {
standaloneTester = new StandaloneTester(getAuthConfigs(tmpFolder.newFolder()));
standaloneTester.apply(base, description).evaluate();
}
}, description);
}
/**
* Return the base URI of Standalone for use in tests.
*/
public URI getBaseURI() {
return standaloneTester.getBaseURI();
}
private static String[] getAuthConfigs(File tmpDir) throws IOException {
LocationFactory locationFactory = new LocalLocationFactory(tmpDir);
Location authExtensionJar = AppJarHelper.createDeploymentJar(locationFactory, InMemoryAuthorizer.class);
return new String[] {
// We want to enable security, but bypass it for only testing authorization commands
Constants.Security.ENABLED, "true",
Constants.Security.AUTH_HANDLER_CLASS, BasicAuthenticationHandler.class.getName(),
Constants.Security.Router.BYPASS_AUTHENTICATION_REGEX, ".*",
Constants.Security.Authorization.ENABLED, "true",
Constants.Security.Authorization.EXTENSION_JAR_PATH, authExtensionJar.toURI().getPath(),
// Bypass authorization enforcement for grant/revoke operations in this test. Authorization enforcement for
// grant/revoke is tested in AuthorizationHandlerTest
Constants.Security.Authorization.EXTENSION_CONFIG_PREFIX + "superusers", "*"
};
}
}
@ClassRule
public static final StandaloneTesterWithAuthorization AUTH_STANDALONE = new StandaloneTesterWithAuthorization();
private static CLI cli;
@BeforeClass
public static void setup() throws Exception {
CLIConfig cliConfig = createCLIConfig(AUTH_STANDALONE.getBaseURI());
LaunchOptions launchOptions = new LaunchOptions(LaunchOptions.DEFAULT.getUri(), true, true, false);
CLIMain cliMain = new CLIMain(launchOptions, cliConfig);
cli = cliMain.getCLI();
testCommandOutputContains(cli, "connect " + AUTH_STANDALONE.getBaseURI(), "Successfully connected");
}
@Test
public void testAuthorizationCLI() throws Exception {
Role role = new Role("admins");
Principal principal = new Principal("spiderman", Principal.PrincipalType.USER);
NamespaceId namespaceId = new NamespaceId("ns1");
testCommandOutputContains(cli, String.format("create namespace %s", namespaceId.getNamespace()),
String.format("Namespace '%s' created successfully", namespaceId.getNamespace()));
// test creating role
testCommandOutputContains(cli, "create role " + role.getName(), String.format("Successfully created role '%s'",
role.getName()));
// test add role to principal
testCommandOutputContains(cli, String.format("add role %s to %s %s", role.getName(), principal.getType(),
principal.getName()),
String.format("Successfully added role '%s' to '%s' '%s'", role.getName(),
principal.getType(), principal.getName()));
// test listing all roles
String output = getCommandOutput(cli, "list roles");
List<String> lines = Arrays.asList(output.split("\\r?\\n"));
Assert.assertEquals(2, lines.size());
Assert.assertEquals(role.getName(), lines.get(1)); // 0 is just the table headers
// test listing roles for a principal
output = getCommandOutput(cli, String.format("list roles for %s %s", principal.getType(), principal.getName()));
lines = Arrays.asList(output.split("\\r?\\n"));
Assert.assertEquals(2, lines.size());
Assert.assertEquals(role.getName(), lines.get(1));
// test grant action. also tests case insensitivity of Action and Principal.PrincipalType
testCommandOutputContains(cli, String.format("grant actions %s on entity %s to %s %s",
Action.READ.name().toLowerCase(), namespaceId.toString(),
principal.getType().name().toLowerCase(), principal.getName()),
String.format("Successfully granted action(s) '%s' on entity '%s' to %s '%s'",
Action.READ, namespaceId.toString(), principal.getType(),
principal.getName()));
// test listing privilege
output = getCommandOutput(cli, String.format("list privileges for %s %s", principal.getType(),
principal.getName()));
lines = Arrays.asList(output.split("\\r?\\n"));
Assert.assertEquals(2, lines.size());
Assert.assertArrayEquals(new String[]{namespaceId.toString(), Action.READ.name()}, lines.get(1).split(","));
// test revoke actions
testCommandOutputContains(cli, String.format("revoke actions %s on entity %s from %s %s", Action.READ,
namespaceId.toString(), principal.getType(), principal.getName()),
String.format("Successfully revoked action(s) '%s' on entity '%s' for %s '%s'",
Action.READ, namespaceId.toString(), principal.getType(),
principal.getName()));
// grant and perform revoke on the entity
testCommandOutputContains(cli, String.format("grant actions %s on entity %s to %s %s", Action.READ,
namespaceId.toString(), principal.getType(), principal.getName()),
String.format("Successfully granted action(s) '%s' on entity '%s' to %s '%s'",
Action.READ, namespaceId.toString(), principal.getType(),
principal.getName()));
testCommandOutputContains(cli, String.format("revoke all on entity %s ", namespaceId.toString()),
String.format("Successfully revoked all actions on entity '%s' for all principals",
namespaceId.toString()));
// test remove role from principal
testCommandOutputContains(cli, String.format("remove role %s from %s %s", role.getName(), principal.getType(),
principal.getName()),
String.format("Successfully removed role '%s' from %s '%s'", role.getName(),
principal.getType(), principal.getName()));
}
}