/** * Copyright 2015 StreamSets Inc. * * Licensed under the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 com.streamsets.datacollector.restapi; import com.google.common.collect.ImmutableSet; import com.streamsets.datacollector.http.WebServerTask; import com.streamsets.datacollector.log.LogUtils; import com.streamsets.datacollector.main.MainStandalonePipelineManagerModule; import com.streamsets.datacollector.main.RuntimeInfo; import com.streamsets.datacollector.main.RuntimeModule; import com.streamsets.datacollector.task.Task; import com.streamsets.datacollector.task.TaskWrapper; import com.streamsets.datacollector.util.AuthzRole; import com.streamsets.datacollector.util.Configuration; import com.streamsets.pipeline.api.impl.Utils; import com.streamsets.testing.NetworkUtils; import dagger.ObjectGraph; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.glassfish.jersey.client.filter.CsrfProtectionFilter; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.FileWriter; import java.io.InputStreamReader; import java.io.Writer; import java.net.HttpURLConnection; import java.net.URL; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; public class TestRestApiAuthorization { private static final String CONVERSION_PATTERN = "%d{ISO8601} [user:%X{s-user}] [pipeline:%X{s-entity}] [thread:%t] %-5p %c{1} - %m%n"; private String createTestDir() { File dir = new File("target", UUID.randomUUID().toString()); Assert.assertTrue(dir.mkdirs()); return dir.getAbsolutePath(); } private String log4jConf; private String baseDir; private Task server; @Before public void setup() throws Exception { server = null; baseDir = createTestDir(); log4jConf = new File(baseDir, "log4j.properties").getAbsolutePath(); File logFile = new File(baseDir, "x.log"); Writer writer = new FileWriter(log4jConf); writer.write(LogUtils.LOG4J_APPENDER_STREAMSETS_FILE_PROPERTY + "=" + logFile.getAbsolutePath() + "\n"); writer.write(LogUtils.LOG4J_APPENDER_STREAMSETS_LAYOUT_CONVERSION_PATTERN + "=" + CONVERSION_PATTERN); writer.close(); Assert.assertTrue(new File(baseDir, "etc").mkdir()); Assert.assertTrue(new File(baseDir, "data").mkdir()); Assert.assertTrue(new File(baseDir, "log").mkdir()); Assert.assertTrue(new File(baseDir, "web").mkdir()); System.setProperty(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.CONFIG_DIR, baseDir + "/etc"); System.setProperty(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.DATA_DIR, baseDir + "/data"); System.setProperty(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.LOG_DIR, baseDir + "/log"); System.setProperty(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.STATIC_WEB_DIR, baseDir + "/web"); } @After public void cleanup() { System.getProperties().remove(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.CONFIG_DIR); System.getProperties().remove(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.DATA_DIR); System.getProperties().remove(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.LOG_DIR); System.getProperties().remove(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.STATIC_WEB_DIR); } private String startServer(boolean authzEnabled) throws Exception { int port = NetworkUtils.getRandomPort(); Configuration conf = new Configuration(); conf.set(WebServerTask.HTTP_PORT_KEY, port); conf.set(WebServerTask.AUTHENTICATION_KEY, (authzEnabled) ? "basic" : "none"); Writer writer = new FileWriter(new File(System.getProperty(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.CONFIG_DIR), "sdc.properties")); conf.save(writer); writer.close(); File realmFile = new File(System.getProperty(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.CONFIG_DIR), "basic-realm.properties"); writer = new FileWriter(realmFile); IOUtils.copy(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("basic-realm.properties")), writer); writer.close(); Files.setPosixFilePermissions(realmFile.toPath(), WebServerTask.OWNER_PERMISSIONS); ObjectGraph dagger = ObjectGraph.create(MainStandalonePipelineManagerModule.class); RuntimeInfo runtimeInfo = dagger.get(RuntimeInfo.class); runtimeInfo.setAttribute(RuntimeInfo.LOG4J_CONFIGURATION_URL_ATTR, new URL("file://" + baseDir + "/log4j.properties")); server = dagger.get(TaskWrapper.class); server.init(); server.run(); return "http://127.0.0.1:" + port; } private void stopServer() { if (server != null) { server.stop(); } } enum Method { GET, POST, PUT, DELETE }; private static final Set<String> ALL_ROLES = ImmutableSet .of(AuthzRole.ADMIN, AuthzRole.CREATOR, AuthzRole.MANAGER, AuthzRole.GUEST); static class RestApi { final String uriPath; final Method method; final Set<String> roles; public RestApi(String uriPath, Method method, String... roles) throws Exception { this.uriPath = uriPath; this.method = method; Set<String> set = new HashSet<>(); if (roles != null) { for (String role : roles) { if (role != null) { set.add(role); } } } this.roles = Collections.unmodifiableSet(set); } } private void test(List<RestApi> apis, boolean authzEnabled) throws Exception { String baseUrl = startServer(authzEnabled); try { for (RestApi api : apis) { Set<String> has = api.roles; for (String user : ALL_ROLES) { user = "guest"; URL url = new URL(baseUrl + api.uriPath); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestProperty(CsrfProtectionFilter.HEADER_NAME, "CSRF"); if (authzEnabled) { conn.setRequestProperty("Authorization", "Basic " + Base64.encodeBase64URLSafeString((user + ":" + user).getBytes())); } conn.setRequestMethod(api.method.name()); conn.setDefaultUseCaches(false); if (authzEnabled) { if (has.contains(user)) { Assert.assertNotEquals( Utils.format("Authz '{}' User '{}' METHOD '{}' API '{}'", authzEnabled, user, api.method, api.uriPath), HttpURLConnection.HTTP_FORBIDDEN, conn.getResponseCode()); } else { Assert.assertEquals( Utils .format("Authz '{}' User '{}' METHOD '{}' API '{}'", authzEnabled, user, api.method, api.uriPath), HttpURLConnection.HTTP_FORBIDDEN, conn.getResponseCode()); } } else { Assert.assertNotEquals( Utils.format("Authz '{}' User '{}' METHOD '{}' API '{}'", authzEnabled, user, api.method, api.uriPath), HttpURLConnection.HTTP_FORBIDDEN, conn.getResponseCode()); } } } } finally { stopServer(); } } private List<RestApi> createRestApis() throws Exception { List<RestApi> list = new ArrayList<>(); list.add(new RestApi("/rest/ping", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/system/shutdown", Method.POST, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/system/threads", Method.GET, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/system/enableDPM", Method.POST, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/system/disableDPM", Method.POST, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/system/configuration/ui", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/system/configuration", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/definitions/helpref", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/system/info", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/system/info/currentUser", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/system/users", Method.GET, AuthzRole.ADMIN, AuthzRole.CREATOR)); list.add(new RestApi("/rest/v1/system/groups", Method.GET, AuthzRole.ADMIN, AuthzRole.CREATOR)); list.add(new RestApi("/rest/v1/logout", Method.POST, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/pipelines/status", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/pipeline/foo/status", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/pipeline/foo/start", Method.POST, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipelines/start", Method.POST, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/stop", Method.POST, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipelines/stop", Method.POST, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/forceStop", Method.POST, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipelines/forceStop", Method.POST, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/committedOffsets", Method.GET, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/resetOffset", Method.POST, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipelines/resetOffsets", Method.POST, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipelines/addLabels", Method.POST, AuthzRole.CREATOR, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/snapshot/foo", Method.PUT, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipelines/snapshots", Method.GET, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/snapshots", Method.GET, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/snapshot/foo/status", Method.GET, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/snapshot/foo", Method.GET, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/snapshot/foo", Method.DELETE, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/metrics", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/pipeline/foo/history", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/pipeline/foo/history", Method.DELETE, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/errorRecords", Method.GET, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/errorMessages", Method.GET, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/sampledRecords", Method.GET, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipelines/alerts", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/pipeline/foo/alerts", Method.DELETE, AuthzRole.MANAGER, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipelines", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/pipelines/delete", Method.POST, AuthzRole.CREATOR, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipelines/deleteByFiltering", Method.POST, AuthzRole.CREATOR, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/pipeline/foo", Method.PUT, AuthzRole.CREATOR, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo", Method.DELETE, AuthzRole.CREATOR, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo", Method.POST, AuthzRole.CREATOR, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/rules", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/pipeline/foo/rules", Method.POST,AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.MANAGER)); list.add(new RestApi("/rest/v1/pipeline/foo/preview", Method.POST, AuthzRole.CREATOR, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/preview/uuid/status", Method.GET, AuthzRole.CREATOR, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/preview/uuid", Method.GET, AuthzRole.CREATOR, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/preview/uuid", Method.DELETE, AuthzRole.CREATOR, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/rawSourcePreview", Method.GET, AuthzRole.CREATOR, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/pipeline/foo/validate", Method.GET, AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.MANAGER)); list.add(new RestApi("/rest/v1/definitions", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/definitions/stage/foo/foo/icons", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/stageLibraries/list", Method.GET, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/stageLibraries/install", Method.POST, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/stageLibraries/uninstall", Method.POST, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/stageLibraries/extras/list", Method.GET, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/stageLibraries/extras/foo/upload", Method.POST, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/stageLibraries/extras/delete", Method.POST, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/system/logs", Method.GET, AuthzRole.ADMIN, AuthzRole.CREATOR, AuthzRole.MANAGER)); list.add(new RestApi("/rest/v1/system/logs/files", Method.GET, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/system/logs/files/foo", Method.GET, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/system/log/config", Method.GET, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/system/log/config", Method.POST, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/acl/foo", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/acl/foo", Method.POST, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/acl/foo/permissions", Method.GET, AuthzRole.ALL_ROLES)); list.add(new RestApi("/rest/v1/acl/pipelines/subjects", Method.GET, AuthzRole.ADMIN)); list.add(new RestApi("/rest/v1/acl/pipelines/subjects", Method.POST, AuthzRole.ADMIN)); return list; } @Test public void testAuthorizationEnabled() throws Exception { test(createRestApis(), true); } @Test public void testAuthorizationDisabled() throws Exception { test(createRestApis(), false); } }