/*
*
* * Copyright 2011-2015 the original author or authors.
* *
* * 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.springframework.xd.dirt.security;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.xd.dirt.security.SecurityTestUtils.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.util.CollectionUtils;
import org.springframework.xd.dirt.security.support.UserCredentials;
/**
* Tests for security configuration backed by a file-based user list.
*
* @author Eric Bottard
* @author Gunnar Hillert
*/
@RunWith(Parameterized.class)
public class SingleNodeApplicationWithUsersFileTest {
private static UserCredentials viewOnlyUser = new UserCredentials("bob", "bobspassword");
private static UserCredentials adminOnlyUser = new UserCredentials("alice", "alicepwd");
private static UserCredentials createOnlyUser = new UserCredentials("cartman", "cartmanpwd");
private static UserCredentials wrongUsername = new UserCredentials("joe", "joespassword");
private static UserCredentials wrongPassword = new UserCredentials("bob", "bobpassword999");
private static UserCredentials bootUsernameWithRandomPassword = new UserCredentials("admin", "whosThere");
private final static SpringXdResource springXdResource = new SpringXdResource(
"classpath:org/springframework/xd/dirt/security/fileBasedUsers.yml");
private final static Logger logger = LoggerFactory.getLogger(SingleNodeApplicationWithUsersFileTest.class);
@ClassRule
public static TestRule springXdAndLdapServer = springXdResource;
@Parameters(name = "Authentication Test {index} - {0} {2} - Returns: {1}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ HttpMethod.GET, HttpStatus.OK, "/", adminOnlyUser, null },
{ HttpMethod.GET, HttpStatus.UNAUTHORIZED, "/", null, null },
{ HttpMethod.GET, HttpStatus.UNAUTHORIZED, "/management/metrics", null, null },
{ HttpMethod.GET, HttpStatus.OK, "/management/metrics", adminOnlyUser, null },
{ HttpMethod.GET, HttpStatus.UNAUTHORIZED, "/modules", null, null }, //Unauthenticated
{ HttpMethod.GET, HttpStatus.OK, "/modules", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.UNAUTHORIZED, "/modules", wrongUsername, null },
{ HttpMethod.GET, HttpStatus.UNAUTHORIZED, "/modules", bootUsernameWithRandomPassword, null },
{ HttpMethod.GET, HttpStatus.UNAUTHORIZED, "/modules", wrongPassword, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/streams/definitions", createOnlyUser, null }, //AuthenticatedButUnauthorized
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/streams/definitions.json", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/streams/definitions", adminOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/streams/definitions.json", adminOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/streams/definitions", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/streams/definitions.json", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/streams/definitions.json", viewOnlyUser,
ImmutableMap.of("page", "0", "size", "10") },
{ HttpMethod.DELETE, HttpStatus.FORBIDDEN, "/streams/definitions", viewOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.FORBIDDEN, "/streams/definitions.json", viewOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.FORBIDDEN, "/streams/definitions", adminOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.FORBIDDEN, "/streams/definitions.json", adminOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.OK, "/streams/definitions", createOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.OK, "/streams/definitions.json", createOnlyUser, null },
{ HttpMethod.POST, HttpStatus.FORBIDDEN, "/streams/deployments/abcd", viewOnlyUser, null },
{ HttpMethod.POST, HttpStatus.FORBIDDEN, "/streams/deployments/abcd", adminOnlyUser, null },
{ HttpMethod.POST, HttpStatus.NOT_FOUND, "/streams/deployments/abcd", createOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.FORBIDDEN, "/streams/deployments/abcd", viewOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.FORBIDDEN, "/streams/deployments/abcd", adminOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.NOT_FOUND, "/streams/deployments/abcd", createOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.FORBIDDEN, "/streams/definitions/abcd", viewOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.FORBIDDEN, "/streams/definitions/abcd", adminOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.NOT_FOUND, "/streams/definitions/abcd", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/definitions", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/definitions.json", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/jobs/definitions", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/jobs/definitions.json", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/definitions.json", createOnlyUser,
ImmutableMap.of("page", "0", "size", "10") },
{ HttpMethod.GET, HttpStatus.OK, "/jobs/definitions.json", viewOnlyUser,
ImmutableMap.of("page", "0", "size", "10") },
{ HttpMethod.POST, HttpStatus.FORBIDDEN, "/jobs/definitions", viewOnlyUser,
ImmutableMap.of("definition", "timestampfile", "deploy", "true", "name", "abcdef") },
{ HttpMethod.POST, HttpStatus.CREATED, "/jobs/definitions", createOnlyUser,
ImmutableMap.of("definition", "timestampfile", "deploy", "true", "name", "abcdef") },
{ HttpMethod.POST, HttpStatus.FORBIDDEN, "/jobs/executions", viewOnlyUser,
ImmutableMap.of("jobname", "abcdef") },
{ HttpMethod.PUT, HttpStatus.FORBIDDEN, "/jobs/executions", viewOnlyUser,
ImmutableMap.of("stop", "true") },
{ HttpMethod.PUT, HttpStatus.FORBIDDEN, "/jobs/executions", adminOnlyUser,
ImmutableMap.of("stop", "true") },
{ HttpMethod.PUT, HttpStatus.OK, "/jobs/executions", createOnlyUser,
ImmutableMap.of("stop", "true") },
{ HttpMethod.POST, HttpStatus.CREATED, "/jobs/executions", createOnlyUser,
ImmutableMap.of("jobname", "abcdef") },
{ HttpMethod.DELETE, HttpStatus.OK, "/jobs/definitions/abcdef", createOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.FORBIDDEN, "/jobs/definitions/abcdef", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/configurations", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/jobs/configurations", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/configurations.json", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/jobs/configurations.json", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/configurations.json", createOnlyUser,
ImmutableMap.of("page", "0", "size", "10") },
{ HttpMethod.GET, HttpStatus.OK, "/jobs/configurations.json", viewOnlyUser,
ImmutableMap.of("page", "0", "size", "10") },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/executions", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/executions.json", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.UNAUTHORIZED, "/jobs/executions/333/steps/123/progress", null, null },
{ HttpMethod.GET, HttpStatus.NOT_FOUND, "/jobs/executions/333/steps/123/progress", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/executions/333/steps/123/progress", adminOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/executions/333/steps/123/progress", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.UNAUTHORIZED, "/jobs/executions/333/steps/123/progress.json", null, null },
{ HttpMethod.GET, HttpStatus.NOT_FOUND, "/jobs/executions/333/steps/123/progress.json", viewOnlyUser,
null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/executions/333/steps/123/progress.json", adminOnlyUser,
null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/executions/333/steps/123/progress.json", createOnlyUser,
null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/instances", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/jobs/instances.json", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.BAD_REQUEST, "/jobs/instances", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.NOT_FOUND, "/jobs/instances", viewOnlyUser,
ImmutableMap.of("jobname", "testjobname") },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/modules", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/modules.json", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/modules", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/modules.json", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/runtime/modules", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/runtime/modules.json", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/runtime/modules", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/runtime/modules.json", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/runtime/containers", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/runtime/containers.json", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/runtime/containers", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/runtime/containers.json", viewOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.FORBIDDEN, "/runtime/containers", viewOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.BAD_REQUEST, "/runtime/containers", createOnlyUser, null },
{ HttpMethod.DELETE, HttpStatus.NOT_FOUND, "/runtime/containers", createOnlyUser,
ImmutableMap.of("containerId", "123456789") },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/metrics/counters", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/metrics/counters.json", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/metrics/counters", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/metrics/counters.json", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/metrics/field-value-counters", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/metrics/field-value-counters.json", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/metrics/field-value-counters", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/metrics/field-value-counters.json", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/metrics/aggregate-counters", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/metrics/aggregate-counters.json", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/metrics/aggregate-counters", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/metrics/aggregate-counters.json", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/metrics/gauges", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/metrics/gauges.json", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/metrics/gauges", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/metrics/gauges.json", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/metrics/rich-gauges", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.FORBIDDEN, "/metrics/rich-gauges.json", createOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/metrics/rich-gauges", viewOnlyUser, null },
{ HttpMethod.GET, HttpStatus.OK, "/metrics/rich-gauges.json", viewOnlyUser, null }
});
}
@Parameter(value = 0)
public HttpMethod httpMethod;
@Parameter(value = 1)
public HttpStatus expectedHttpStatus;
@Parameter(value = 2)
public String url;
@Parameter(value = 3)
public UserCredentials userCredentials;
@Parameter(value = 4)
public Map<String, String> urlParameters;
@Test
public void testEndpointAuthentication() throws Exception {
logger.info(String.format("Using parameters - httpMethod: %s, "
+ "URL: %s, URL parameters: %s, user credentials: %s", this.httpMethod,
this.url, this.urlParameters, userCredentials));
final MockHttpServletRequestBuilder rb;
switch (httpMethod) {
case GET:
rb = get(url);
break;
case POST:
rb = post(url);
break;
case PUT:
rb = put(url);
break;
case DELETE:
rb = delete(url);
break;
default:
throw new IllegalArgumentException("Unsupported Method: " + httpMethod);
}
if (this.userCredentials != null) {
rb.header("Authorization",
basicAuthorizationHeader(this.userCredentials.getUsername(), this.userCredentials.getPassword()));
}
if (!CollectionUtils.isEmpty(urlParameters)) {
for (Map.Entry<String, String> mapEntry : urlParameters.entrySet()) {
rb.param(mapEntry.getKey(), mapEntry.getValue());
}
}
final ResultMatcher statusResultMatcher;
switch (expectedHttpStatus) {
case UNAUTHORIZED:
statusResultMatcher = status().isUnauthorized();
break;
case FORBIDDEN:
statusResultMatcher = status().isForbidden();
break;
case NOT_FOUND:
statusResultMatcher = status().isNotFound();
break;
case OK:
statusResultMatcher = status().isOk();
break;
case CREATED:
statusResultMatcher = status().isCreated();
break;
case BAD_REQUEST:
statusResultMatcher = status().isBadRequest();
break;
case INTERNAL_SERVER_ERROR:
statusResultMatcher = status().isInternalServerError();
break;
default:
throw new IllegalArgumentException("Unsupported Status: " + expectedHttpStatus);
}
try {
springXdResource.getMockMvc().perform(rb).andDo(print()).andExpect(statusResultMatcher);
}
catch (AssertionError e) {
throw new AssertionError(
String.format("Assertion failed for parameters - httpMethod: %s, "
+ "URL: %s, URL parameters: %s, user credentials: %s",
this.httpMethod, this.url, this.urlParameters, this.userCredentials),
e);
}
}
}