/* * Copyright 2012-2017 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.boot.actuate.cloudfoundry; import java.util.Map; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason; import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.client.match.MockRestRequestMatchers.header; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError; import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import static org.springframework.test.web.client.response.MockRestResponseCreators.withUnauthorizedRequest; /** * Tests for {@link CloudFoundrySecurityService}. * * @author Madhura Bhave */ public class CloudFoundrySecurityServiceTests { @Rule public ExpectedException thrown = ExpectedException.none(); private static final String CLOUD_CONTROLLER = "http://my-cloud-controller.com"; private static final String CLOUD_CONTROLLER_PERMISSIONS = CLOUD_CONTROLLER + "/v2/apps/my-app-id/permissions"; private static final String UAA_URL = "http://my-uaa.com"; private CloudFoundrySecurityService securityService; private MockRestServiceServer server; @Before public void setup() throws Exception { MockServerRestTemplateCustomizer mockServerCustomizer = new MockServerRestTemplateCustomizer(); RestTemplateBuilder builder = new RestTemplateBuilder(mockServerCustomizer); this.securityService = new CloudFoundrySecurityService(builder, CLOUD_CONTROLLER, false); this.server = mockServerCustomizer.getServer(); } @Test public void skipSslValidationWhenTrue() throws Exception { RestTemplateBuilder builder = new RestTemplateBuilder(); this.securityService = new CloudFoundrySecurityService(builder, CLOUD_CONTROLLER, true); RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils .getField(this.securityService, "restTemplate"); assertThat(restTemplate.getRequestFactory()) .isInstanceOf(SkipSslVerificationHttpRequestFactory.class); } @Test public void doNotskipSslValidationWhenFalse() throws Exception { RestTemplateBuilder builder = new RestTemplateBuilder(); this.securityService = new CloudFoundrySecurityService(builder, CLOUD_CONTROLLER, false); RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils .getField(this.securityService, "restTemplate"); assertThat(restTemplate.getRequestFactory()) .isNotInstanceOf(SkipSslVerificationHttpRequestFactory.class); } @Test public void getAccessLevelWhenSpaceDeveloperShouldReturnFull() throws Exception { String responseBody = "{\"read_sensitive_data\": true,\"read_basic_data\": true}"; this.server.expect(requestTo(CLOUD_CONTROLLER_PERMISSIONS)) .andExpect(header("Authorization", "bearer my-access-token")) .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); AccessLevel accessLevel = this.securityService.getAccessLevel("my-access-token", "my-app-id"); this.server.verify(); assertThat(accessLevel).isEqualTo(AccessLevel.FULL); } @Test public void getAccessLevelWhenNotSpaceDeveloperShouldReturnRestricted() throws Exception { String responseBody = "{\"read_sensitive_data\": false,\"read_basic_data\": true}"; this.server.expect(requestTo(CLOUD_CONTROLLER_PERMISSIONS)) .andExpect(header("Authorization", "bearer my-access-token")) .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); AccessLevel accessLevel = this.securityService.getAccessLevel("my-access-token", "my-app-id"); this.server.verify(); assertThat(accessLevel).isEqualTo(AccessLevel.RESTRICTED); } @Test public void getAccessLevelWhenTokenIsNotValidShouldThrowException() throws Exception { this.server.expect(requestTo(CLOUD_CONTROLLER_PERMISSIONS)) .andExpect(header("Authorization", "bearer my-access-token")) .andRespond(withUnauthorizedRequest()); this.thrown .expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN)); this.securityService.getAccessLevel("my-access-token", "my-app-id"); } @Test public void getAccessLevelWhenForbiddenShouldThrowException() throws Exception { this.server.expect(requestTo(CLOUD_CONTROLLER_PERMISSIONS)) .andExpect(header("Authorization", "bearer my-access-token")) .andRespond(withStatus(HttpStatus.FORBIDDEN)); this.thrown .expect(AuthorizationExceptionMatcher.withReason(Reason.ACCESS_DENIED)); this.securityService.getAccessLevel("my-access-token", "my-app-id"); } @Test public void getAccessLevelWhenCloudControllerIsNotReachableThrowsException() throws Exception { this.server.expect(requestTo(CLOUD_CONTROLLER_PERMISSIONS)) .andExpect(header("Authorization", "bearer my-access-token")) .andRespond(withServerError()); this.thrown.expect( AuthorizationExceptionMatcher.withReason(Reason.SERVICE_UNAVAILABLE)); this.securityService.getAccessLevel("my-access-token", "my-app-id"); } @Test public void fetchTokenKeysWhenSuccessfulShouldReturnListOfKeysFromUAA() throws Exception { this.server.expect(requestTo(CLOUD_CONTROLLER + "/info")) .andRespond(withSuccess("{\"token_endpoint\":\"http://my-uaa.com\"}", MediaType.APPLICATION_JSON)); String tokenKeyValue = "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0m59l2u9iDnMbrXHfqkO\n" + "rn2dVQ3vfBJqcDuFUK03d+1PZGbVlNCqnkpIJ8syFppW8ljnWweP7+LiWpRoz0I7\n" + "fYb3d8TjhV86Y997Fl4DBrxgM6KTJOuE/uxnoDhZQ14LgOU2ckXjOzOdTsnGMKQB\n" + "LCl0vpcXBtFLMaSbpv1ozi8h7DJyVZ6EnFQZUWGdgTMhDrmqevfx95U/16c5WBDO\n" + "kqwIn7Glry9n9Suxygbf8g5AzpWcusZgDLIIZ7JTUldBb8qU2a0Dl4mvLZOn4wPo\n" + "jfj9Cw2QICsc5+Pwf21fP+hzf+1WSRHbnYv8uanRO0gZ8ekGaghM/2H6gqJbo2nI\n" + "JwIDAQAB\n-----END PUBLIC KEY-----"; String responseBody = "{\"keys\" : [ {\"kid\":\"test-key\",\"value\" : \"" + tokenKeyValue.replace("\n", "\\n") + "\"} ]}"; this.server.expect(requestTo(UAA_URL + "/token_keys")) .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); Map<String, String> tokenKeys = this.securityService.fetchTokenKeys(); this.server.verify(); assertThat(tokenKeys.get("test-key")).isEqualTo(tokenKeyValue); } @Test public void fetchTokenKeysWhenNoKeysReturnedFromUAA() throws Exception { this.server.expect(requestTo(CLOUD_CONTROLLER + "/info")).andRespond(withSuccess( "{\"token_endpoint\":\"" + UAA_URL + "\"}", MediaType.APPLICATION_JSON)); String responseBody = "{\"keys\": []}"; this.server.expect(requestTo(UAA_URL + "/token_keys")) .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); Map<String, String> tokenKeys = this.securityService.fetchTokenKeys(); this.server.verify(); assertThat(tokenKeys).hasSize(0); } @Test public void fetchTokenKeysWhenUnsuccessfulShouldThrowException() throws Exception { this.server.expect(requestTo(CLOUD_CONTROLLER + "/info")).andRespond(withSuccess( "{\"token_endpoint\":\"" + UAA_URL + "\"}", MediaType.APPLICATION_JSON)); this.server.expect(requestTo(UAA_URL + "/token_keys")) .andRespond(withServerError()); this.thrown.expect( AuthorizationExceptionMatcher.withReason(Reason.SERVICE_UNAVAILABLE)); this.securityService.fetchTokenKeys(); } @Test public void getUaaUrlShouldCallCloudControllerInfoOnlyOnce() throws Exception { this.server.expect(requestTo(CLOUD_CONTROLLER + "/info")).andRespond(withSuccess( "{\"token_endpoint\":\"" + UAA_URL + "\"}", MediaType.APPLICATION_JSON)); String uaaUrl = this.securityService.getUaaUrl(); this.server.verify(); assertThat(uaaUrl).isEqualTo(UAA_URL); // Second call should not need to hit server uaaUrl = this.securityService.getUaaUrl(); assertThat(uaaUrl).isEqualTo(UAA_URL); } @Test public void getUaaUrlWhenCloudControllerUrlIsNotReachableShouldThrowException() throws Exception { this.server.expect(requestTo(CLOUD_CONTROLLER + "/info")) .andRespond(withServerError()); this.thrown.expect( AuthorizationExceptionMatcher.withReason(Reason.SERVICE_UNAVAILABLE)); this.securityService.getUaaUrl(); } }