/* * Copyright 2017-2020 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 * * https://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.cloud.dataflow.server.single.security; import org.junit.ClassRule; import org.junit.Test; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; import org.springframework.cloud.dataflow.server.single.LocalDataflowResource; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; import org.springframework.security.oauth2.common.OAuth2AccessToken; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertTrue; import static org.springframework.cloud.dataflow.server.single.security.SecurityTestUtils.basicAuthorizationHeader; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Gunnar Hillert */ public class LocalServerSecurityWithOAuth2Tests { private final static OAuth2ServerResource oAuth2ServerResource = new OAuth2ServerResource(); private final static LocalDataflowResource localDataflowResource = new LocalDataflowResource( "classpath:org/springframework/cloud/dataflow/server/single/security/oauthConfig.yml"); @ClassRule public static TestRule springDataflowAndOAuth2Server = RuleChain.outerRule(oAuth2ServerResource) .around(localDataflowResource); @Test public void testAccessRootUrlWithoutCredentials() throws Exception { localDataflowResource.getMockMvc().perform(get("/")).andDo(print()).andExpect(status().isUnauthorized()); } @Test public void testAccessRootUrlWithBasicAuthCredentials() throws Exception { localDataflowResource.getMockMvc() .perform(get("/").header("Authorization", basicAuthorizationHeader("user", "secret10"))).andDo(print()) .andExpect(status().isOk()); } @Test public void testAccessRootUrlWithBasicAuthCredentialsTwice() throws Exception { localDataflowResource.getMockMvc() .perform(get("/").header("Authorization", basicAuthorizationHeader("user", "secret10"))).andDo(print()) .andExpect(status().isOk()); localDataflowResource.getMockMvc() .perform(get("/").header("Authorization", basicAuthorizationHeader("user", "secret10"))).andDo(print()) .andExpect(status().isOk()); } @Test public void testAccessRootUrlAndCheckAllLinksWithBasicAuthCredentials() throws Exception { localDataflowResource.getMockMvc() .perform(get("/").header("Authorization", basicAuthorizationHeader("user", "secret10"))).andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.dashboard.href", is("http://localhost/dashboard"))) .andExpect(jsonPath("$._links.streams/definitions.href", is("http://localhost/streams/definitions"))) .andExpect(jsonPath("$._links.streams/definitions/definition.href", is("http://localhost/streams/definitions/{name}"))) .andExpect(jsonPath("$._links.streams/deployments.href", is("http://localhost/streams/deployments"))) .andExpect(jsonPath("$._links.streams/deployments/deployment.href", is("http://localhost/streams/deployments/{name}"))) .andExpect(jsonPath("$._links.runtime/apps.href", is("http://localhost/runtime/apps"))) .andExpect(jsonPath("$._links.runtime/apps/{appId}.href", is("http://localhost/runtime/apps/{appId}"))) .andExpect(jsonPath("$._links.runtime/apps/{appId}/instances.href", is("http://localhost/runtime/apps/{appId}/instances"))) .andExpect(jsonPath("$._links.runtime/apps/{appId}/instances/{instanceId}.href", is("http://localhost/runtime/apps/{appId}/instances/{instanceId}"))) .andExpect(jsonPath("$._links.runtime/streams.href", is("http://localhost/runtime/streams{?names}"))) .andExpect(jsonPath("$._links.runtime/streams/{streamNames}.href", is("http://localhost/runtime/streams/{streamNames}"))) .andExpect(jsonPath("$._links.tasks/definitions.href", is("http://localhost/tasks/definitions"))) .andExpect(jsonPath("$._links.tasks/definitions/definition.href", is("http://localhost/tasks/definitions/{name}"))) .andExpect(jsonPath("$._links.tasks/executions.href", is("http://localhost/tasks/executions"))) .andExpect(jsonPath("$._links.tasks/executions/name.href", is("http://localhost/tasks/executions{?name}"))) .andExpect(jsonPath("$._links.tasks/executions/execution.href", is("http://localhost/tasks/executions/{id}"))) .andExpect(jsonPath("$._links.jobs/executions.href", is("http://localhost/jobs/executions"))) .andExpect(jsonPath("$._links.jobs/executions/name.href", is("http://localhost/jobs/executions{?name}"))) .andExpect(jsonPath("$._links.jobs/executions/status.href", is("http://localhost/jobs/executions{?status}"))) .andExpect(jsonPath("$._links.jobs/executions/execution.href", is("http://localhost/jobs/executions/{id}"))) .andExpect(jsonPath("$._links.jobs/executions/execution/steps.href", is("http://localhost/jobs/executions/{jobExecutionId}/steps"))) .andExpect(jsonPath("$._links.jobs/executions/execution/steps/step.href", is("http://localhost/jobs/executions/{jobExecutionId}/steps/{stepId}"))) .andExpect(jsonPath("$._links.jobs/executions/execution/steps/step/progress.href", is("http://localhost/jobs/executions/{jobExecutionId}/steps/{stepId}/progress"))) .andExpect(jsonPath("$._links.jobs/instances/name.href", is("http://localhost/jobs/instances{?name}"))) .andExpect(jsonPath("$._links.jobs/instances/instance.href", is("http://localhost/jobs/instances/{id}"))) .andExpect(jsonPath("$._links.tools/parseTaskTextToGraph.href", is("http://localhost/tools"))) .andExpect(jsonPath("$._links.tools/convertTaskGraphToText.href", is("http://localhost/tools"))) .andExpect(jsonPath("$._links.apps.href", is("http://localhost/apps"))) .andExpect(jsonPath("$._links.about.href", is("http://localhost/about"))) .andExpect(jsonPath("$._links.completions/stream.href", is("http://localhost/completions/stream{?start,detailLevel}"))) .andExpect(jsonPath("$._links.completions/task.href", is("http://localhost/completions/task{?start,detailLevel}"))); } @Test public void testAccessRootUrlWithBasicAuthCredentialsWrongPassword() throws Exception { localDataflowResource.getMockMvc() .perform(get("/").header("Authorization", basicAuthorizationHeader("user", "wrong-password"))) .andDo(print()).andExpect(status().isUnauthorized()); } @Test public void testThatAccessToActuatorEndpointPromptsSecurity() throws Exception { localDataflowResource.getMockMvc().perform(get("/management/env")).andDo(print()) .andExpect(status().isUnauthorized()); } @Test public void testAccessToActuatorEndpointWithBasicAuthCredentialsWrongPassword() throws Exception { localDataflowResource.getMockMvc() .perform(get("/management/env").header("Authorization", basicAuthorizationHeader("user", "wrong-password"))) .andDo(print()).andExpect(status().isUnauthorized()); } @Test public void testThatAccessToActuatorEndpointWithBasicAuthCredentialsSucceeds() throws Exception { localDataflowResource.getMockMvc() .perform(get("/management/env").header("Authorization", basicAuthorizationHeader("user", "secret10"))) .andDo(print()).andExpect(status().isOk()); } @Test public void testAccessRootUrlWithOAuth2AccessToken() throws Exception { final ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails(); resourceDetails.setClientId("myclient"); resourceDetails.setClientSecret("mysecret"); resourceDetails.setGrantType("client_credentials"); resourceDetails .setAccessTokenUri("http://localhost:" + oAuth2ServerResource.getOauth2ServerPort() + "/oauth/token"); final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resourceDetails); final OAuth2AccessToken accessToken = oAuth2RestTemplate.getAccessToken(); final String accessTokenAsString = accessToken.getValue(); localDataflowResource.getMockMvc().perform(get("/").header("Authorization", "bearer " + accessTokenAsString)) .andDo(print()).andExpect(status().isOk()); } @Test public void testAccessSecurityInfoUrlWithOAuth2AccessToken() throws Exception { final ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails(); resourceDetails.setClientId("myclient"); resourceDetails.setClientSecret("mysecret"); resourceDetails.setGrantType("client_credentials"); resourceDetails .setAccessTokenUri("http://localhost:" + oAuth2ServerResource.getOauth2ServerPort() + "/oauth/token"); final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resourceDetails); final OAuth2AccessToken accessToken = oAuth2RestTemplate.getAccessToken(); final String accessTokenAsString = accessToken.getValue(); localDataflowResource.getMockMvc() .perform(get("/security/info").header("Authorization", "bearer " + accessTokenAsString)).andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.authenticated", is(Boolean.TRUE))) .andExpect(jsonPath("$.authenticationEnabled", is(Boolean.TRUE))) .andExpect(jsonPath("$.roles", hasSize(7))); } @Test public void testAccessSecurityInfoUrlWithOAuth2AccessToken2Times() throws Exception { final ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails(); resourceDetails.setClientId("myclient"); resourceDetails.setClientSecret("mysecret"); resourceDetails .setAccessTokenUri("http://localhost:" + oAuth2ServerResource.getOauth2ServerPort() + "/oauth/token"); final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resourceDetails); final OAuth2AccessToken accessToken = oAuth2RestTemplate.getAccessToken(); final String accessTokenAsString = accessToken.getValue(); localDataflowResource.getMockMvc() .perform(get("/security/info").header("Authorization", "bearer " + accessTokenAsString)).andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.authenticated", is(Boolean.TRUE))) .andExpect(jsonPath("$.authenticationEnabled", is(Boolean.TRUE))) .andExpect(jsonPath("$.roles", hasSize(7))); localDataflowResource.getMockMvc() .perform(get("/security/info").header("Authorization", "bearer " + accessTokenAsString)).andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.authenticated", is(Boolean.TRUE))) .andExpect(jsonPath("$.authenticationEnabled", is(Boolean.TRUE))) .andExpect(jsonPath("$.roles", hasSize(7))); } @Test public void testAccessSecurityInfoUrlWithOAuth2AccessTokenPasswordGrant2Times() throws Exception { final ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails(); resourceDetails.setClientId("myclient"); resourceDetails.setClientSecret("mysecret"); resourceDetails.setUsername("user"); resourceDetails.setPassword("secret10"); resourceDetails .setAccessTokenUri("http://localhost:" + oAuth2ServerResource.getOauth2ServerPort() + "/oauth/token"); final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resourceDetails); final OAuth2AccessToken accessToken = oAuth2RestTemplate.getAccessToken(); final String accessTokenAsString = accessToken.getValue(); localDataflowResource.getMockMvc() .perform(get("/security/info").header("Authorization", "bearer " + accessTokenAsString)).andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.authenticated", is(Boolean.TRUE))) .andExpect(jsonPath("$.authenticationEnabled", is(Boolean.TRUE))) .andExpect(jsonPath("$.roles", hasSize(7))); localDataflowResource.getMockMvc() .perform(get("/security/info").header("Authorization", "bearer " + accessTokenAsString)).andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.authenticated", is(Boolean.TRUE))) .andExpect(jsonPath("$.authenticationEnabled", is(Boolean.TRUE))) .andExpect(jsonPath("$.roles", hasSize(7))); } @Test public void testAccessSecurityInfoUrlWithOAuth2AccessToken2TimesAndLogout() throws Exception { final ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails(); resourceDetails.setClientId("myclient"); resourceDetails.setClientSecret("mysecret"); resourceDetails.setGrantType("client_credentials"); resourceDetails .setAccessTokenUri("http://localhost:" + oAuth2ServerResource.getOauth2ServerPort() + "/oauth/token"); final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resourceDetails); final OAuth2AccessToken accessToken = oAuth2RestTemplate.getAccessToken(); final String accessTokenAsString = accessToken.getValue(); localDataflowResource.getMockMvc() .perform(get("/security/info").header("Authorization", "bearer " + accessTokenAsString)).andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.authenticated", is(Boolean.TRUE))) .andExpect(jsonPath("$.authenticationEnabled", is(Boolean.TRUE))) .andExpect(jsonPath("$.roles", hasSize(7))); boolean oAuthServerResponse = oAuth2RestTemplate.getForObject("http://localhost:" + oAuth2ServerResource.getOauth2ServerPort() + "/revoke_token", Boolean.class); assertTrue(Boolean.valueOf(oAuthServerResponse)); localDataflowResource.getMockMvc() .perform(get("/security/info").header("Authorization", "bearer " + accessTokenAsString)).andDo(print()) .andExpect(status().isUnauthorized()); localDataflowResource.getMockMvc() .perform(get("/logout").header("Authorization", "bearer " + accessTokenAsString)).andDo(print()) .andExpect(status().isFound()); localDataflowResource.getMockMvc() .perform(get("/security/info").header("Authorization", "bearer " + accessTokenAsString)).andDo(print()) .andExpect(status().isUnauthorized()); } @Test public void testAccessRootUrlWithWrongOAuth2AccessToken() throws Exception { localDataflowResource.getMockMvc().perform(get("/").header("Authorization", "bearer 123456")).andDo(print()) .andExpect(status().isUnauthorized()); } @Test public void testXMLHttpRequestReturnsNoWwwAuthenticateHeader() throws Exception { localDataflowResource.getMockMvc().perform(get("/").header("X-Requested-With", "XMLHttpRequest")).andDo(print()) .andExpect(status().isUnauthorized()) .andExpect(header().doesNotExist("WWW-Authenticate")); } }