Pave the way for reworking the Actuator's CloudFoundry support

See gh-9996
pull/10000/merge
Andy Wilkinson 7 years ago
parent ee16332745
commit b49c1f6925

@ -1,69 +0,0 @@
/*
* 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.autoconfigure.cloudfoundry;
import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.IgnoredRequestCustomizer;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
/**
* {@link EnableAutoConfiguration Auto-configuration} to expose actuator endpoints for
* cloud foundry to use.
*
* @author Madhura Bhave
* @since 2.0.0
*/
@Configuration
@ConditionalOnProperty(prefix = "management.cloudfoundry", name = "enabled", matchIfMissing = true)
@AutoConfigureAfter(ServletEndpointAutoConfiguration.class)
@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
public class CloudFoundryActuatorAutoConfiguration {
/**
* Nested configuration for ignored requests if Spring Security is present.
*/
@ConditionalOnClass(WebSecurity.class)
static class CloudFoundryIgnoredRequestConfiguration {
@Bean
public IgnoredRequestCustomizer cloudFoundryIgnoredRequestCustomizer() {
return new CloudFoundryIgnoredRequestCustomizer();
}
private static class CloudFoundryIgnoredRequestCustomizer
implements IgnoredRequestCustomizer {
@Override
public void customize(WebSecurity.IgnoredRequestConfigurer configurer) {
configurer.requestMatchers(
new AntPathRequestMatcher("/cloudfoundryapplication/**"));
}
}
}
}

@ -1,20 +0,0 @@
/*
* 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.
*/
/**
* Auto-configuration for Actuator's CloudFoundry integration.
*/
package org.springframework.boot.actuate.autoconfigure.cloudfoundry;

@ -1,67 +0,0 @@
/*
* Copyright 2012-2016 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.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
/**
* The specific access level granted to the cloud foundry user that's calling the
* endpoints.
*
* @author Madhura Bhave
*/
enum AccessLevel {
/**
* Restricted access to a limited set of endpoints.
*/
RESTRICTED("", "/health", "/info"),
/**
* Full access to all endpoints.
*/
FULL;
private static final String REQUEST_ATTRIBUTE = "cloudFoundryAccessLevel";
private final List<String> endpointPaths;
AccessLevel(String... endpointPaths) {
this.endpointPaths = Arrays.asList(endpointPaths);
}
/**
* Returns if the access level should allow access to the specified endpoint path.
* @param endpointPath the endpoint path
* @return {@code true} if access is allowed
*/
public boolean isAccessAllowed(String endpointPath) {
return this.endpointPaths.isEmpty() || this.endpointPaths.contains(endpointPath);
}
public void put(HttpServletRequest request) {
request.setAttribute(REQUEST_ATTRIBUTE, this);
}
public static AccessLevel get(HttpServletRequest request) {
return (AccessLevel) request.getAttribute(REQUEST_ATTRIBUTE);
}
}

@ -1,92 +0,0 @@
/*
* 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 org.springframework.http.HttpStatus;
/**
* Authorization exceptions thrown to limit access to the endpoints.
*
* @author Madhura Bhave
*/
class CloudFoundryAuthorizationException extends RuntimeException {
private final Reason reason;
CloudFoundryAuthorizationException(Reason reason, String message) {
this(reason, message, null);
}
CloudFoundryAuthorizationException(Reason reason, String message, Throwable cause) {
super(message);
this.reason = reason;
}
/**
* Return the status code that should be returned to the client.
* @return the HTTP status code
*/
public HttpStatus getStatusCode() {
return getReason().getStatus();
}
/**
* Return the reason why the authorization exception was thrown.
* @return the reason
*/
public Reason getReason() {
return this.reason;
}
/**
* Reasons why the exception can be thrown.
*/
enum Reason {
ACCESS_DENIED(HttpStatus.FORBIDDEN),
INVALID_AUDIENCE(HttpStatus.UNAUTHORIZED),
INVALID_ISSUER(HttpStatus.UNAUTHORIZED),
INVALID_KEY_ID(HttpStatus.UNAUTHORIZED),
INVALID_SIGNATURE(HttpStatus.UNAUTHORIZED),
INVALID_TOKEN(HttpStatus.UNAUTHORIZED),
MISSING_AUTHORIZATION(HttpStatus.UNAUTHORIZED),
TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED),
UNSUPPORTED_TOKEN_SIGNING_ALGORITHM(HttpStatus.UNAUTHORIZED),
SERVICE_UNAVAILABLE(HttpStatus.SERVICE_UNAVAILABLE);
private final HttpStatus status;
Reason(HttpStatus status) {
this.status = status;
}
public HttpStatus getStatus() {
return this.status;
}
}
}

@ -1,77 +0,0 @@
/*
* 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;
/**
* {@link MvcEndpoint} to expose HAL-formatted JSON for Cloud Foundry specific actuator
* endpoints.
*
* @author Madhura Bhave
*/
class CloudFoundryDiscoveryMvcEndpoint {
// TODO Port to new infrastructure
// private final Set<NamedMvcEndpoint> endpoints;
//
// CloudFoundryDiscoveryMvcEndpoint(Set<NamedMvcEndpoint> endpoints) {
// this.endpoints = endpoints;
// }
//
// @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
// @ResponseBody
// public Map<String, Map<String, Link>> links(HttpServletRequest request) {
// Map<String, Link> links = new LinkedHashMap<>();
// String url = request.getRequestURL().toString();
// if (url.endsWith("/")) {
// url = url.substring(0, url.length() - 1);
// }
// links.put("self", Link.withHref(url));
// AccessLevel accessLevel = AccessLevel.get(request);
// for (NamedMvcEndpoint endpoint : this.endpoints) {
// if (accessLevel != null && accessLevel.isAccessAllowed(endpoint.getPath())) {
// links.put(endpoint.getName(),
// Link.withHref(url + "/" + endpoint.getName()));
// }
// }
// return Collections.singletonMap("_links", links);
// }
//
// /**
// * Details for a link in the HAL response.
// */
// static class Link {
//
// private String href;
//
// public String getHref() {
// return this.href;
// }
//
// public void setHref(String href) {
// this.href = href;
// }
//
// static Link withHref(Object href) {
// Link link = new Link();
// link.setHref(href.toString());
// return link;
// }
//
// }
}

@ -1,44 +0,0 @@
/*
* 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 org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension;
/**
* Extension of {@link HealthWebEndpointExtension} for Cloud Foundry. Since security for
* Cloud Foundry actuators is already handled by the
* {@link CloudFoundrySecurityInterceptor}, this endpoint skips the additional security
* checks done by the regular {@link HealthWebEndpointExtension}.
*
* @author Madhura Bhave
*/
class CloudFoundryHealthMvcEndpoint extends HealthWebEndpointExtension {
// TODO Port to new infrastructure
CloudFoundryHealthMvcEndpoint(HealthEndpoint delegate) {
super(delegate);
}
//
// @Override
// protected boolean exposeHealthDetails(HttpServletRequest request,
// Principal principal) {
// return true;
// }
}

@ -1,114 +0,0 @@
/*
* 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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/**
* {@link HandlerInterceptor} to check the cloud foundry token.
*
* @author Madhura Bhave
* @since 2.0.0
*/
public class CloudFoundrySecurityInterceptor extends HandlerInterceptorAdapter {
private static final Log logger = LogFactory
.getLog(CloudFoundrySecurityInterceptor.class);
private final TokenValidator tokenValidator;
private final CloudFoundrySecurityService cloudFoundrySecurityService;
private final String applicationId;
public CloudFoundrySecurityInterceptor(TokenValidator tokenValidator,
CloudFoundrySecurityService cloudFoundrySecurityService,
String applicationId) {
this.tokenValidator = tokenValidator;
this.cloudFoundrySecurityService = cloudFoundrySecurityService;
this.applicationId = applicationId;
}
// TODO Port to new infrastructure
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
// if (CorsUtils.isPreFlightRequest(request)) {
// return true;
// }
// try {
// if (!StringUtils.hasText(this.applicationId)) {
// throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE,
// "Application id is not available");
// }
// if (this.cloudFoundrySecurityService == null) {
// throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE,
// "Cloud controller URL is not available");
// }
// HandlerMethod handlerMethod = (HandlerMethod) handler;
// if (HttpMethod.OPTIONS.matches(request.getMethod())
// && !(handlerMethod.getBean() instanceof MvcEndpoint)) {
// return true;
// }
// MvcEndpoint mvcEndpoint = (MvcEndpoint) handlerMethod.getBean();
// check(request, mvcEndpoint);
// }
// catch (CloudFoundryAuthorizationException ex) {
// logger.error(ex);
// response.setContentType(MediaType.APPLICATION_JSON.toString());
// response.getWriter()
// .write("{\"security_error\":\"" + ex.getMessage() + "\"}");
// response.setStatus(ex.getStatusCode().value());
// return false;
// }
return true;
}
// private void check(HttpServletRequest request, MvcEndpoint mvcEndpoint)
// throws Exception {
// Token token = getToken(request);
// this.tokenValidator.validate(token);
// AccessLevel accessLevel = this.cloudFoundrySecurityService
// .getAccessLevel(token.toString(), this.applicationId);
// if (!accessLevel.isAccessAllowed(mvcEndpoint.getPath())) {
// throw new CloudFoundryAuthorizationException(Reason.ACCESS_DENIED,
// "Access denied");
// }
// accessLevel.put(request);
// }
private Token getToken(HttpServletRequest request) {
String authorization = request.getHeader("Authorization");
String bearerPrefix = "bearer ";
if (authorization == null
|| !authorization.toLowerCase().startsWith(bearerPrefix)) {
throw new CloudFoundryAuthorizationException(Reason.MISSING_AUTHORIZATION,
"Authorization header is missing or invalid");
}
return new Token(authorization.substring(bearerPrefix.length()));
}
}

@ -1,147 +0,0 @@
/*
* 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.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.util.Assert;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
/**
* Cloud Foundry security service to handle REST calls to the cloud controller and UAA.
*
* @author Madhura Bhave
* @since 2.0.0
*/
public class CloudFoundrySecurityService {
private final RestTemplate restTemplate;
private final String cloudControllerUrl;
private String uaaUrl;
public CloudFoundrySecurityService(RestTemplateBuilder restTemplateBuilder,
String cloudControllerUrl, boolean skipSslValidation) {
Assert.notNull(restTemplateBuilder, "RestTemplateBuilder must not be null");
Assert.notNull(cloudControllerUrl, "CloudControllerUrl must not be null");
if (skipSslValidation) {
restTemplateBuilder = restTemplateBuilder
.requestFactory(SkipSslVerificationHttpRequestFactory.class);
}
this.restTemplate = restTemplateBuilder.build();
this.cloudControllerUrl = cloudControllerUrl;
}
/**
* Return the access level that should be granted to the given token.
* @param token the token
* @param applicationId the cloud foundry application ID
* @return the access level that should be granted
* @throws CloudFoundryAuthorizationException if the token is not authorized
*/
public AccessLevel getAccessLevel(String token, String applicationId)
throws CloudFoundryAuthorizationException {
try {
URI uri = getPermissionsUri(applicationId);
RequestEntity<?> request = RequestEntity.get(uri)
.header("Authorization", "bearer " + token).build();
Map<?, ?> body = this.restTemplate.exchange(request, Map.class).getBody();
if (Boolean.TRUE.equals(body.get("read_sensitive_data"))) {
return AccessLevel.FULL;
}
return AccessLevel.RESTRICTED;
}
catch (HttpClientErrorException ex) {
if (ex.getStatusCode().equals(HttpStatus.FORBIDDEN)) {
throw new CloudFoundryAuthorizationException(Reason.ACCESS_DENIED,
"Access denied");
}
throw new CloudFoundryAuthorizationException(Reason.INVALID_TOKEN,
"Invalid token", ex);
}
catch (HttpServerErrorException ex) {
throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE,
"Cloud controller not reachable");
}
}
private URI getPermissionsUri(String applicationId) {
try {
return new URI(this.cloudControllerUrl + "/v2/apps/" + applicationId
+ "/permissions");
}
catch (URISyntaxException ex) {
throw new IllegalStateException(ex);
}
}
/**
* Return all token keys known by the UAA.
* @return a list of token keys
*/
public Map<String, String> fetchTokenKeys() {
try {
return extractTokenKeys(this.restTemplate
.getForObject(getUaaUrl() + "/token_keys", Map.class));
}
catch (HttpStatusCodeException e) {
throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE,
"UAA not reachable");
}
}
private Map<String, String> extractTokenKeys(Map<?, ?> response) {
Map<String, String> tokenKeys = new HashMap<>();
for (Object key : (List<?>) response.get("keys")) {
Map<?, ?> tokenKey = (Map<?, ?>) key;
tokenKeys.put((String) tokenKey.get("kid"), (String) tokenKey.get("value"));
}
return tokenKeys;
}
/**
* Return the URL of the UAA.
* @return the UAA url
*/
public String getUaaUrl() {
if (this.uaaUrl == null) {
try {
Map<?, ?> response = this.restTemplate
.getForObject(this.cloudControllerUrl + "/info", Map.class);
this.uaaUrl = (String) response.get("token_endpoint");
}
catch (HttpStatusCodeException ex) {
throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE,
"Unable to fetch token keys from UAA");
}
}
return this.uaaUrl;
}
}

@ -1,93 +0,0 @@
/*
* Copyright 2012-2016 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.io.IOException;
import java.net.HttpURLConnection;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
/**
* {@link SimpleClientHttpRequestFactory} that skips SSL certificate verification.
*
* @author Madhura Bhave
*/
class SkipSslVerificationHttpRequestFactory extends SimpleClientHttpRequestFactory {
@Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod)
throws IOException {
if (connection instanceof HttpsURLConnection) {
prepareHttpsConnection((HttpsURLConnection) connection);
}
super.prepareConnection(connection, httpMethod);
}
private void prepareHttpsConnection(HttpsURLConnection connection) {
connection.setHostnameVerifier(new SkipHostnameVerifier());
try {
connection.setSSLSocketFactory(createSslSocketFactory());
}
catch (Exception ex) {
// Ignore
}
}
private SSLSocketFactory createSslSocketFactory() throws Exception {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[] { new SkipX509TrustManager() },
new SecureRandom());
return context.getSocketFactory();
}
private class SkipHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
}
private static class SkipX509TrustManager implements X509TrustManager {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
}
}

@ -1,125 +0,0 @@
/*
* 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.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;
/**
* The JSON web token provided with each request that originates from Cloud Foundry.
*
* @author Madhura Bhave
*/
class Token {
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final String encoded;
private final String signature;
private final Map<String, Object> header;
private final Map<String, Object> claims;
Token(String encoded) {
this.encoded = encoded;
int firstPeriod = encoded.indexOf('.');
int lastPeriod = encoded.lastIndexOf('.');
if (firstPeriod <= 0 || lastPeriod <= firstPeriod) {
throw new CloudFoundryAuthorizationException(
CloudFoundryAuthorizationException.Reason.INVALID_TOKEN,
"JWT must have header, body and signature");
}
this.header = parseJson(encoded.substring(0, firstPeriod));
this.claims = parseJson(encoded.substring(firstPeriod + 1, lastPeriod));
this.signature = encoded.substring(lastPeriod + 1);
if (!StringUtils.hasLength(this.signature)) {
throw new CloudFoundryAuthorizationException(
CloudFoundryAuthorizationException.Reason.INVALID_TOKEN,
"Token must have non-empty crypto segment");
}
}
private Map<String, Object> parseJson(String base64) {
try {
byte[] bytes = Base64Utils.decodeFromUrlSafeString(base64);
return JsonParserFactory.getJsonParser().parseMap(new String(bytes, UTF_8));
}
catch (RuntimeException ex) {
throw new CloudFoundryAuthorizationException(
CloudFoundryAuthorizationException.Reason.INVALID_TOKEN,
"Token could not be parsed", ex);
}
}
public byte[] getContent() {
return this.encoded.substring(0, this.encoded.lastIndexOf(".")).getBytes();
}
public byte[] getSignature() {
return Base64Utils.decodeFromUrlSafeString(this.signature);
}
public String getSignatureAlgorithm() {
return getRequired(this.header, "alg", String.class);
}
public String getIssuer() {
return getRequired(this.claims, "iss", String.class);
}
public long getExpiry() {
return getRequired(this.claims, "exp", Integer.class).longValue();
}
@SuppressWarnings("unchecked")
public List<String> getScope() {
return getRequired(this.claims, "scope", List.class);
}
public String getKeyId() {
return getRequired(this.header, "kid", String.class);
}
@SuppressWarnings("unchecked")
private <T> T getRequired(Map<String, Object> map, String key, Class<T> type) {
Object value = map.get(key);
if (value == null) {
throw new CloudFoundryAuthorizationException(
CloudFoundryAuthorizationException.Reason.INVALID_TOKEN,
"Unable to get value from key " + key);
}
if (!type.isInstance(value)) {
throw new CloudFoundryAuthorizationException(
CloudFoundryAuthorizationException.Reason.INVALID_TOKEN,
"Unexpected value type from key " + key + " value " + value);
}
return (T) value;
}
@Override
public String toString() {
return this.encoded;
};
}

@ -1,142 +0,0 @@
/*
* 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.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason;
import org.springframework.util.Base64Utils;
/**
* Validator used to ensure that a signed {@link Token} has not been tampered with.
*
* @author Madhura Bhave
* @since 2.0.0
*/
public class TokenValidator {
private final CloudFoundrySecurityService securityService;
private Map<String, String> tokenKeys;
public TokenValidator(CloudFoundrySecurityService cloudFoundrySecurityService) {
this.securityService = cloudFoundrySecurityService;
}
public void validate(Token token) {
validateAlgorithm(token);
validateKeyIdAndSignature(token);
validateExpiry(token);
validateIssuer(token);
validateAudience(token);
}
private void validateAlgorithm(Token token) {
String algorithm = token.getSignatureAlgorithm();
if (algorithm == null) {
throw new CloudFoundryAuthorizationException(Reason.INVALID_SIGNATURE,
"Signing algorithm cannot be null");
}
if (!algorithm.equals("RS256")) {
throw new CloudFoundryAuthorizationException(
Reason.UNSUPPORTED_TOKEN_SIGNING_ALGORITHM,
"Signing algorithm " + algorithm + " not supported");
}
}
private void validateKeyIdAndSignature(Token token) {
String keyId = token.getKeyId();
if (this.tokenKeys == null || !hasValidKeyId(keyId)) {
this.tokenKeys = this.securityService.fetchTokenKeys();
if (!hasValidKeyId(keyId)) {
throw new CloudFoundryAuthorizationException(Reason.INVALID_KEY_ID,
"Key Id present in token header does not match");
}
}
if (!hasValidSignature(token, this.tokenKeys.get(keyId))) {
throw new CloudFoundryAuthorizationException(Reason.INVALID_SIGNATURE,
"RSA Signature did not match content");
}
}
private boolean hasValidKeyId(String tokenKey) {
for (String candidate : this.tokenKeys.keySet()) {
if (tokenKey.equals(candidate)) {
return true;
}
}
return false;
}
private boolean hasValidSignature(Token token, String key) {
try {
PublicKey publicKey = getPublicKey(key);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(token.getContent());
return signature.verify(token.getSignature());
}
catch (GeneralSecurityException ex) {
return false;
}
}
private PublicKey getPublicKey(String key)
throws NoSuchAlgorithmException, InvalidKeySpecException {
key = key.replace("-----BEGIN PUBLIC KEY-----\n", "");
key = key.replace("-----END PUBLIC KEY-----", "");
key = key.trim().replace("\n", "");
byte[] bytes = Base64Utils.decodeFromString(key);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
}
private void validateExpiry(Token token) {
long currentTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
if (currentTime > token.getExpiry()) {
throw new CloudFoundryAuthorizationException(Reason.TOKEN_EXPIRED,
"Token expired");
}
}
private void validateIssuer(Token token) {
String uaaUrl = this.securityService.getUaaUrl();
String issuerUri = String.format("%s/oauth/token", uaaUrl);
if (!issuerUri.equals(token.getIssuer())) {
throw new CloudFoundryAuthorizationException(Reason.INVALID_ISSUER,
"Token issuer does not match " + uaaUrl + "/oauth/token");
}
}
private void validateAudience(Token token) {
if (!token.getScope().contains("actuator.read")) {
throw new CloudFoundryAuthorizationException(Reason.INVALID_AUDIENCE,
"Token does not have audience actuator");
}
}
}

@ -1,20 +0,0 @@
/*
* 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.
*/
/**
* Classes for CloudFoundry integration.
*/
package org.springframework.boot.actuate.cloudfoundry;

@ -2,7 +2,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cache.CacheStatisticsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryActuatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration,\

@ -1,54 +0,0 @@
/*
* Copyright 2012-2016 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 org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link AccessLevel}.
*
* @author Madhura Bhave
*/
public class AccessLevelTests {
@Test
public void accessToHealthEndpointShouldNotBeRestricted() throws Exception {
assertThat(AccessLevel.RESTRICTED.isAccessAllowed("/health")).isTrue();
assertThat(AccessLevel.FULL.isAccessAllowed("/health")).isTrue();
}
@Test
public void accessToInfoEndpointShouldNotBeRestricted() throws Exception {
assertThat(AccessLevel.RESTRICTED.isAccessAllowed("/info")).isTrue();
assertThat(AccessLevel.FULL.isAccessAllowed("/info")).isTrue();
}
@Test
public void accessToDiscoveryEndpointShouldNotBeRestricted() throws Exception {
assertThat(AccessLevel.RESTRICTED.isAccessAllowed("")).isTrue();
assertThat(AccessLevel.FULL.isAccessAllowed("")).isTrue();
}
@Test
public void accessToAnyOtherEndpointShouldBeRestricted() throws Exception {
assertThat(AccessLevel.RESTRICTED.isAccessAllowed("env")).isFalse();
assertThat(AccessLevel.FULL.isAccessAllowed("")).isTrue();
}
}

@ -1,48 +0,0 @@
/*
* Copyright 2012-2016 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 org.hamcrest.CustomMatcher;
import org.hamcrest.Matcher;
import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason;
/**
* Hamcrest matcher to check the {@link AuthorizationExceptionMatcher} {@link Reason}.
*
* @author Madhura Bhave
*/
final class AuthorizationExceptionMatcher {
private AuthorizationExceptionMatcher() {
}
static Matcher<?> withReason(final Reason reason) {
return new CustomMatcher<Object>(
"CloudFoundryAuthorizationException with " + reason + " reason") {
@Override
public boolean matches(Object object) {
return ((object instanceof CloudFoundryAuthorizationException)
&& ((CloudFoundryAuthorizationException) object)
.getReason() == reason);
}
};
}
}

@ -1,170 +0,0 @@
/*
* 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;
/**
* Tests for {@link CloudFoundryActuatorAutoConfiguration}.
*
* @author Madhura Bhave
*/
public class CloudFoundryActuatorAutoConfigurationTests {
// TODO CloudFoundry support
// private AnnotationConfigWebApplicationContext context;
//
// @Before
// public void setup() {
// this.context = new AnnotationConfigWebApplicationContext();
// this.context.setServletContext(new MockServletContext());
// this.context.register(SecurityAutoConfiguration.class,
// WebMvcAutoConfiguration.class,
// ManagementWebSecurityAutoConfiguration.class,
// JacksonAutoConfiguration.class,
// HttpMessageConvertersAutoConfiguration.class,
// EndpointAutoConfiguration.class, EndpointServletWebAutoConfiguration.class,
// PropertyPlaceholderAutoConfiguration.class,
// RestTemplateAutoConfiguration.class,
// EndpointWebMvcManagementContextConfiguration.class,
// CloudFoundryActuatorAutoConfiguration.class);
// }
//
// @After
// public void close() {
// if (this.context != null) {
// this.context.close();
// }
// }
//
// @Test
// public void cloudFoundryPlatformActive() throws Exception {
// CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping();
// assertThat(handlerMapping.getPrefix()).isEqualTo("/cloudfoundryapplication");
// CorsConfiguration corsConfiguration = (CorsConfiguration) ReflectionTestUtils
// .getField(handlerMapping, "corsConfiguration");
// assertThat(corsConfiguration.getAllowedOrigins()).contains("*");
// assertThat(corsConfiguration.getAllowedMethods()).containsAll(
// Arrays.asList(HttpMethod.GET.name(), HttpMethod.POST.name()));
// assertThat(corsConfiguration.getAllowedHeaders()).containsAll(
// Arrays.asList("Authorization", "X-Cf-App-Instance", "Content-Type"));
// }
//
// @Test
// public void cloudFoundryPlatformActiveSetsApplicationId() throws Exception {
// CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping();
// Object interceptor = ReflectionTestUtils.getField(handlerMapping,
// "securityInterceptor");
// String applicationId = (String) ReflectionTestUtils.getField(interceptor,
// "applicationId");
// assertThat(applicationId).isEqualTo("my-app-id");
// }
//
// @Test
// public void cloudFoundryPlatformActiveSetsCloudControllerUrl() throws Exception {
// CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping();
// Object interceptor = ReflectionTestUtils.getField(handlerMapping,
// "securityInterceptor");
// Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor,
// "cloudFoundrySecurityService");
// String cloudControllerUrl = (String) ReflectionTestUtils
// .getField(interceptorSecurityService, "cloudControllerUrl");
// assertThat(cloudControllerUrl).isEqualTo("http://my-cloud-controller.com");
// }
//
// @Test
// public void skipSslValidation() throws Exception {
// TestPropertyValues.of("management.cloudfoundry.skipSslValidation:true")
// .applyTo(this.context);
// ConfigurationPropertySources.attach(this.context.getEnvironment());
// this.context.refresh();
// CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping();
// Object interceptor = ReflectionTestUtils.getField(handlerMapping,
// "securityInterceptor");
// Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor,
// "cloudFoundrySecurityService");
// RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils
// .getField(interceptorSecurityService, "restTemplate");
// assertThat(restTemplate.getRequestFactory())
// .isInstanceOf(SkipSslVerificationHttpRequestFactory.class);
// }
//
// @Test
// public void cloudFoundryPlatformActiveAndCloudControllerUrlNotPresent()
// throws Exception {
// TestPropertyValues
// .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id")
// .applyTo(this.context);
// this.context.refresh();
// CloudFoundryEndpointHandlerMapping handlerMapping = this.context.getBean(
// "cloudFoundryEndpointHandlerMapping",
// CloudFoundryEndpointHandlerMapping.class);
// Object securityInterceptor = ReflectionTestUtils.getField(handlerMapping,
// "securityInterceptor");
// Object interceptorSecurityService = ReflectionTestUtils
// .getField(securityInterceptor, "cloudFoundrySecurityService");
// assertThat(interceptorSecurityService).isNull();
// }
//
// @Test
// public void cloudFoundryPathsIgnoredBySpringSecurity() throws Exception {
// TestPropertyValues
// .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id")
// .applyTo(this.context);
// this.context.refresh();
// IgnoredRequestCustomizer customizer = (IgnoredRequestCustomizer) this.context
// .getBean("cloudFoundryIgnoredRequestCustomizer");
// IgnoredRequestConfigurer configurer = mock(IgnoredRequestConfigurer.class);
// customizer.customize(configurer);
// ArgumentCaptor<RequestMatcher> requestMatcher = ArgumentCaptor
// .forClass(RequestMatcher.class);
// verify(configurer).requestMatchers(requestMatcher.capture());
// RequestMatcher matcher = requestMatcher.getValue();
// MockHttpServletRequest request = new MockHttpServletRequest();
// request.setServletPath("/cloudfoundryapplication/my-path");
// assertThat(matcher.matches(request)).isTrue();
// request.setServletPath("/some-other-path");
// assertThat(matcher.matches(request)).isFalse();
// }
//
// @Test
// public void cloudFoundryPlatformInactive() throws Exception {
// this.context.refresh();
// assertThat(this.context.containsBean("cloudFoundryEndpointHandlerMapping"))
// .isFalse();
// }
//
// @Test
// public void cloudFoundryManagementEndpointsDisabled() throws Exception {
// TestPropertyValues
// .of("VCAP_APPLICATION=---", "management.cloudfoundry.enabled:false")
// .applyTo(this.context);
// this.context.refresh();
// assertThat(this.context.containsBean("cloudFoundryEndpointHandlerMapping"))
// .isFalse();
// }
//
// private CloudFoundryEndpointHandlerMapping getHandlerMapping() {
// TestPropertyValues
// .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id",
// "vcap.application.cf_api:http://my-cloud-controller.com")
// .applyTo(this.context);
// this.context.refresh();
// return this.context.getBean("cloudFoundryEndpointHandlerMapping",
// CloudFoundryEndpointHandlerMapping.class);
// }
}

@ -1,92 +0,0 @@
/*
* Copyright 2012-2016 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 org.junit.Test;
import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason;
import org.springframework.http.HttpStatus;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CloudFoundryAuthorizationException}.
*
* @author Madhura Bhave
*/
public class CloudFoundryAuthorizationExceptionTests {
@Test
public void statusCodeForInvalidTokenReasonShouldBe401() throws Exception {
assertThat(createException(Reason.INVALID_TOKEN).getStatusCode())
.isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
public void statusCodeForInvalidIssuerReasonShouldBe401() throws Exception {
assertThat(createException(Reason.INVALID_ISSUER).getStatusCode())
.isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
public void statusCodeForInvalidAudienceReasonShouldBe401() throws Exception {
assertThat(createException(Reason.INVALID_AUDIENCE).getStatusCode())
.isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
public void statusCodeForInvalidSignatureReasonShouldBe401() throws Exception {
assertThat(createException(Reason.INVALID_SIGNATURE).getStatusCode())
.isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
public void statusCodeForMissingAuthorizationReasonShouldBe401() throws Exception {
assertThat(createException(Reason.MISSING_AUTHORIZATION).getStatusCode())
.isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
public void statusCodeForUnsupportedSignatureAlgorithmReasonShouldBe401()
throws Exception {
assertThat(createException(Reason.UNSUPPORTED_TOKEN_SIGNING_ALGORITHM)
.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
public void statusCodeForTokenExpiredReasonShouldBe401() throws Exception {
assertThat(createException(Reason.TOKEN_EXPIRED).getStatusCode())
.isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
public void statusCodeForAccessDeniedReasonShouldBe403() throws Exception {
assertThat(createException(Reason.ACCESS_DENIED).getStatusCode())
.isEqualTo(HttpStatus.FORBIDDEN);
}
@Test
public void statusCodeForServiceUnavailableReasonShouldBe503() throws Exception {
assertThat(createException(Reason.SERVICE_UNAVAILABLE).getStatusCode())
.isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
}
private CloudFoundryAuthorizationException createException(Reason reason) {
return new CloudFoundryAuthorizationException(reason, "message");
}
}

@ -1,109 +0,0 @@
/*
* 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;
/**
* Tests for {@link CloudFoundryDiscoveryMvcEndpoint}.
*
* @author Madhura Bhave
*/
public class CloudFoundryDiscoveryMvcEndpointTests {
// TODO CloudFoundry support
// private CloudFoundryDiscoveryMvcEndpoint endpoint;
//
// private Set<NamedMvcEndpoint> endpoints;
//
// @Before
// public void setup() {
// NamedMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a"));
// this.endpoints = new LinkedHashSet<>();
// this.endpoints.add(endpoint);
// this.endpoint = new CloudFoundryDiscoveryMvcEndpoint(this.endpoints);
// }
//
// @Test
// public void cloudfoundryHalJsonEndpointHasEmptyPath() throws Exception {
// assertThat(this.endpoint.getPath()).isEmpty();
// }
//
// @Test
// public void linksResponseWhenRequestUriHasNoTrailingSlash() throws Exception {
// MockHttpServletRequest request = new MockHttpServletRequest("GET",
// "/cloudfoundryapplication");
// AccessLevel.FULL.put(request);
// Map<String, CloudFoundryDiscoveryMvcEndpoint.Link> links = this.endpoint
// .links(request).get("_links");
// assertThat(links.get("self").getHref())
// .isEqualTo("http://localhost/cloudfoundryapplication");
// assertThat(links.get("a").getHref())
// .isEqualTo("http://localhost/cloudfoundryapplication/a");
// }
//
// @Test
// public void linksResponseWhenRequestUriHasTrailingSlash() throws Exception {
// MockHttpServletRequest request = new MockHttpServletRequest("GET",
// "/cloudfoundryapplication/");
// AccessLevel.FULL.put(request);
// Map<String, CloudFoundryDiscoveryMvcEndpoint.Link> links = this.endpoint
// .links(request).get("_links");
// assertThat(links.get("self").getHref())
// .isEqualTo("http://localhost/cloudfoundryapplication");
// assertThat(links.get("a").getHref())
// .isEqualTo("http://localhost/cloudfoundryapplication/a");
// }
//
// @Test
// public void linksResponseWhenRequestHasAccessLevelRestricted() throws Exception {
// NamedMvcEndpoint testHealthMvcEndpoint = new TestMvcEndpoint(
// new TestEndpoint("health"));
// this.endpoints.add(testHealthMvcEndpoint);
// MockHttpServletRequest request = new MockHttpServletRequest("GET",
// "/cloudfoundryapplication/");
// AccessLevel.RESTRICTED.put(request);
// Map<String, CloudFoundryDiscoveryMvcEndpoint.Link> links = this.endpoint
// .links(request).get("_links");
// assertThat(links.get("self").getHref())
// .isEqualTo("http://localhost/cloudfoundryapplication");
// assertThat(links.get("health").getHref())
// .isEqualTo("http://localhost/cloudfoundryapplication/health");
// assertThat(links.get("a")).isNull();
// }
//
// private static class TestEndpoint extends AbstractEndpoint<Object> {
//
// TestEndpoint(String id) {
// super(id);
// }
//
// @Override
// public Object invoke() {
// return null;
// }
//
// }
//
// private static class TestMvcEndpoint extends EndpointMvcAdapter {
//
// TestMvcEndpoint(TestEndpoint delegate) {
// super(delegate);
// }
//
// }
}

@ -1,125 +0,0 @@
/*
* 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;
/**
* Tests for {@link CloudFoundryEndpointHandlerMapping}.
*
* @author Madhura Bhave
*/
public class CloudFoundryEndpointHandlerMappingTests {
// TODO CloudFoundry support
// @Test
// public void getHandlerExecutionChainWhenEndpointHasPathShouldMapAgainstName()
// throws Exception {
// TestMvcEndpoint testMvcEndpoint = new TestMvcEndpoint(new TestEndpoint("a"));
// testMvcEndpoint.setPath("something-else");
// CloudFoundryEndpointHandlerMapping handlerMapping = new
// CloudFoundryEndpointHandlerMapping(
// Collections.singleton(testMvcEndpoint), null, null);
// assertThat(handlerMapping.getPath(testMvcEndpoint)).isEqualTo("/a");
// }
//
// @Test
// public void doesNotRegisterHalJsonMvcEndpoint() throws Exception {
// CloudFoundryEndpointHandlerMapping handlerMapping = new
// CloudFoundryEndpointHandlerMapping(
// Collections.singleton(new TestHalJsonMvcEndpoint()), null, null);
// assertThat(handlerMapping.getEndpoints()).hasSize(0);
// }
//
// @Test
// public void registersCloudFoundryDiscoveryEndpoint() throws Exception {
// StaticApplicationContext context = new StaticApplicationContext();
// CloudFoundryEndpointHandlerMapping handlerMapping = new
// CloudFoundryEndpointHandlerMapping(
// Collections.<NamedMvcEndpoint>emptySet(), null, null);
// handlerMapping.setPrefix("/test");
// handlerMapping.setApplicationContext(context);
// handlerMapping.afterPropertiesSet();
// HandlerExecutionChain handler = handlerMapping
// .getHandler(new MockHttpServletRequest("GET", "/test"));
// HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler();
// assertThat(handlerMethod.getBean())
// .isInstanceOf(CloudFoundryDiscoveryMvcEndpoint.class);
// }
//
// @Test
// public void registersCloudFoundryHealthEndpoint() throws Exception {
// StaticApplicationContext context = new StaticApplicationContext();
// HealthEndpoint delegate = new HealthEndpoint(new OrderedHealthAggregator(),
// Collections.<String, HealthIndicator>emptyMap());
// CloudFoundryEndpointHandlerMapping handlerMapping = new
// CloudFoundryEndpointHandlerMapping(
// Collections.singleton(new TestHealthMvcEndpoint(delegate)), null, null);
// handlerMapping.setPrefix("/test");
// handlerMapping.setApplicationContext(context);
// handlerMapping.afterPropertiesSet();
// HandlerExecutionChain handler = handlerMapping
// .getHandler(new MockHttpServletRequest("GET", "/test/health"));
// HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler();
// Object handlerMethodBean = handlerMethod.getBean();
// assertThat(handlerMethodBean).isInstanceOf(CloudFoundryHealthMvcEndpoint.class);
// }
//
// private static class TestEndpoint extends AbstractEndpoint<Object> {
//
// TestEndpoint(String id) {
// super(id);
// }
//
// @Override
// public Object invoke() {
// return null;
// }
//
// }
//
// private static class TestMvcEndpoint extends EndpointMvcAdapter {
//
// TestMvcEndpoint(TestEndpoint delegate) {
// super(delegate);
// }
//
// }
//
// private static class TestHalJsonMvcEndpoint extends HalJsonMvcEndpoint {
//
// TestHalJsonMvcEndpoint() {
// super(new ManagementServletContext() {
//
// @Override
// public String getContextPath() {
// return "";
// }
//
// });
// }
//
// }
//
// private static class TestHealthMvcEndpoint extends HealthWebEndpointExtension {
//
// TestHealthMvcEndpoint(HealthEndpoint delegate) {
// super(delegate);
// }
//
// }
}

@ -1,43 +0,0 @@
/*
* 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;
/**
* Tests for {@link CloudFoundryHealthMvcEndpoint}.
*
* @author Madhura Bhave
*/
public class CloudFoundryHealthMvcEndpointTests {
// TODO CloudFoundry-specific health endpoint?
// @Test
// public void cloudFoundryHealthEndpointShouldAlwaysReturnAllHealthDetails()
// throws Exception {
// HealthEndpoint endpoint = mock(HealthEndpoint.class);
// given(endpoint.isEnabled()).willReturn(true);
// CloudFoundryHealthMvcEndpoint mvc = new CloudFoundryHealthMvcEndpoint(endpoint);
// given(endpoint.invoke())
// .willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
// given(endpoint.isSensitive()).willReturn(false);
// Object result = mvc.invoke(null, null);
// assertThat(result instanceof Health).isTrue();
// assertThat(((Health) result).getStatus() == Status.UP).isTrue();
// assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar");
// }
}

@ -1,192 +0,0 @@
/*
* Copyright 2012-2016 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;
/**
* Tests for {@link CloudFoundrySecurityInterceptor}.
*
* @author Madhura Bhave
*/
public class CloudFoundrySecurityInterceptorTests {
// TODO CloudFoundry-specific security
// @Mock
// private TokenValidator tokenValidator;
//
// @Mock
// private CloudFoundrySecurityService securityService;
//
// private CloudFoundrySecurityInterceptor interceptor;
//
// private TestMvcEndpoint endpoint;
//
// private HandlerMethod handlerMethod;
//
// private MockHttpServletRequest request;
//
// private MockHttpServletResponse response;
//
// @Before
// public void setup() throws Exception {
// MockitoAnnotations.initMocks(this);
// this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator,
// this.securityService, "my-app-id");
// this.endpoint = new TestMvcEndpoint(new TestEndpoint("a"));
// this.handlerMethod = new HandlerMethod(this.endpoint, "invoke");
// this.request = new MockHttpServletRequest();
// this.response = new MockHttpServletResponse();
// }
//
// @Test
// public void preHandleWhenRequestIsPreFlightShouldReturnTrue() throws Exception {
// this.request.setMethod("OPTIONS");
// this.request.addHeader(HttpHeaders.ORIGIN, "http://example.com");
// this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
// boolean preHandle = this.interceptor.preHandle(this.request, this.response,
// this.handlerMethod);
// assertThat(preHandle).isTrue();
// }
//
// @Test
// public void preHandleWhenTokenIsMissingShouldReturnFalse() throws Exception {
// boolean preHandle = this.interceptor.preHandle(this.request, this.response,
// this.handlerMethod);
// assertThat(preHandle).isFalse();
// assertThat(this.response.getStatus())
// .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus().value());
// assertThat(this.response.getContentAsString()).contains("security_error");
// assertThat(this.response.getContentType())
// .isEqualTo(MediaType.APPLICATION_JSON.toString());
// }
//
// @Test
// public void preHandleWhenTokenIsNotBearerShouldReturnFalse() throws Exception {
// this.request.addHeader("Authorization", mockAccessToken());
// boolean preHandle = this.interceptor.preHandle(this.request, this.response,
// this.handlerMethod);
// assertThat(preHandle).isFalse();
// assertThat(this.response.getStatus())
// .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus().value());
// }
//
// @Test
// public void preHandleWhenApplicationIdIsNullShouldReturnFalse() throws Exception {
// this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator,
// this.securityService, null);
// this.request.addHeader("Authorization", "bearer " + mockAccessToken());
// boolean preHandle = this.interceptor.preHandle(this.request, this.response,
// this.handlerMethod);
// assertThat(preHandle).isFalse();
// assertThat(this.response.getStatus())
// .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus().value());
// }
//
// @Test
// public void preHandleWhenCloudFoundrySecurityServiceIsNullShouldReturnFalse()
// throws Exception {
// this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, null,
// "my-app-id");
// this.request.addHeader("Authorization", "bearer " + mockAccessToken());
// boolean preHandle = this.interceptor.preHandle(this.request, this.response,
// this.handlerMethod);
// assertThat(preHandle).isFalse();
// assertThat(this.response.getStatus())
// .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus().value());
// }
//
// @Test
// public void preHandleWhenAccessIsNotAllowedShouldReturnFalse() throws Exception {
// this.endpoint = new TestMvcEndpoint(new TestEndpoint("env"));
// this.handlerMethod = new HandlerMethod(this.endpoint, "invoke");
// String accessToken = mockAccessToken();
// this.request.addHeader("Authorization", "bearer " + accessToken);
// BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id"))
// .willReturn(AccessLevel.RESTRICTED);
// boolean preHandle = this.interceptor.preHandle(this.request, this.response,
// this.handlerMethod);
// assertThat(preHandle).isFalse();
// assertThat(this.response.getStatus())
// .isEqualTo(Reason.ACCESS_DENIED.getStatus().value());
// }
//
// @Test
// public void preHandleSuccessfulWithFullAccess() throws Exception {
// String accessToken = mockAccessToken();
// this.request.addHeader("Authorization", "Bearer " + accessToken);
// BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id"))
// .willReturn(AccessLevel.FULL);
// boolean preHandle = this.interceptor.preHandle(this.request, this.response,
// this.handlerMethod);
// ArgumentCaptor<Token> tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class);
// verify(this.tokenValidator).validate(tokenArgumentCaptor.capture());
// Token token = tokenArgumentCaptor.getValue();
// assertThat(token.toString()).isEqualTo(accessToken);
// assertThat(preHandle).isTrue();
// assertThat(this.response.getStatus()).isEqualTo(HttpStatus.OK.value());
// assertThat(this.request.getAttribute("cloudFoundryAccessLevel"))
// .isEqualTo(AccessLevel.FULL);
// }
//
// @Test
// public void preHandleSuccessfulWithRestrictedAccess() throws Exception {
// this.endpoint = new TestMvcEndpoint(new TestEndpoint("info"));
// this.handlerMethod = new HandlerMethod(this.endpoint, "invoke");
// String accessToken = mockAccessToken();
// this.request.addHeader("Authorization", "Bearer " + accessToken);
// BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id"))
// .willReturn(AccessLevel.RESTRICTED);
// boolean preHandle = this.interceptor.preHandle(this.request, this.response,
// this.handlerMethod);
// ArgumentCaptor<Token> tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class);
// verify(this.tokenValidator).validate(tokenArgumentCaptor.capture());
// Token token = tokenArgumentCaptor.getValue();
// assertThat(token.toString()).isEqualTo(accessToken);
// assertThat(preHandle).isTrue();
// assertThat(this.response.getStatus()).isEqualTo(HttpStatus.OK.value());
// assertThat(this.request.getAttribute("cloudFoundryAccessLevel"))
// .isEqualTo(AccessLevel.RESTRICTED);
// }
//
// private String mockAccessToken() {
// return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b3B0YWwu"
// + "Y29tIiwiZXhwIjoxNDI2NDIwODAwLCJhd2Vzb21lIjp0cnVlfQ."
// + Base64Utils.encodeToString("signature".getBytes());
// }
//
// private static class TestEndpoint extends AbstractEndpoint<Object> {
//
// TestEndpoint(String id) {
// super(id);
// }
//
// @Override
// public Object invoke() {
// return null;
// }
//
// }
//
// private static class TestMvcEndpoint extends EndpointMvcAdapter {
//
// TestMvcEndpoint(TestEndpoint delegate) {
// super(delegate);
// }
//
// }
}

@ -1,219 +0,0 @@
/*
* 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();
}
}

@ -1,93 +0,0 @@
/*
* 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 javax.net.ssl.SSLHandshakeException;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.testsupport.web.servlet.ExampleServlet;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.instanceOf;
/**
* Test for {@link SkipSslVerificationHttpRequestFactory}.
*/
public class SkipSslVerificationHttpRequestFactoryTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private WebServer webServer;
@After
public void shutdownContainer() {
if (this.webServer != null) {
this.webServer.stop();
}
}
@Test
public void restCallToSelfSignedServerShouldNotThrowSslException() throws Exception {
String httpsUrl = getHttpsUrl();
SkipSslVerificationHttpRequestFactory requestFactory = new SkipSslVerificationHttpRequestFactory();
RestTemplate restTemplate = new RestTemplate(requestFactory);
ResponseEntity<String> responseEntity = restTemplate.getForEntity(httpsUrl,
String.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
this.thrown.expect(ResourceAccessException.class);
this.thrown.expectCause(isSSLHandshakeException());
RestTemplate otherRestTemplate = new RestTemplate();
otherRestTemplate.getForEntity(httpsUrl, String.class);
}
private Matcher<Throwable> isSSLHandshakeException() {
return instanceOf(SSLHandshakeException.class);
}
private String getHttpsUrl() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(0);
factory.setSsl(getSsl("password", "classpath:test.jks"));
this.webServer = factory.getWebServer(
new ServletRegistrationBean<>(new ExampleServlet(), "/hello"));
this.webServer.start();
return "https://localhost:" + this.webServer.getPort() + "/hello";
}
private Ssl getSsl(String keyPassword, String keyStore) {
Ssl ssl = new Ssl();
ssl.setEnabled(true);
ssl.setKeyPassword(keyPassword);
ssl.setKeyStore(keyStore);
ssl.setKeyStorePassword("secret");
return ssl;
}
}

@ -1,139 +0,0 @@
/*
* 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 org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason;
import org.springframework.util.Base64Utils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link Token}.
*
* @author Madhura Bhave
*/
public class TokenTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void invalidJwtShouldThrowException() throws Exception {
this.thrown
.expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN));
new Token("invalid-token");
}
@Test
public void invalidJwtClaimsShouldThrowException() throws Exception {
String header = "{\"alg\": \"RS256\", \"kid\": \"key-id\", \"typ\": \"JWT\"}";
String claims = "invalid-claims";
this.thrown
.expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN));
new Token(Base64Utils.encodeToString(header.getBytes()) + "."
+ Base64Utils.encodeToString(claims.getBytes()));
}
@Test
public void invalidJwtHeaderShouldThrowException() throws Exception {
String header = "invalid-header";
String claims = "{\"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\"}";
this.thrown
.expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN));
new Token(Base64Utils.encodeToString(header.getBytes()) + "."
+ Base64Utils.encodeToString(claims.getBytes()));
}
@Test
public void emptyJwtSignatureShouldThrowException() throws Exception {
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b3B0YWwu"
+ "Y29tIiwiZXhwIjoxNDI2NDIwODAwLCJhd2Vzb21lIjp0cnVlfQ.";
this.thrown
.expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN));
new Token(token);
}
@Test
public void validJwt() throws Exception {
String header = "{\"alg\": \"RS256\", \"kid\": \"key-id\", \"typ\": \"JWT\"}";
String claims = "{\"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\"}";
String content = Base64Utils.encodeToString(header.getBytes()) + "."
+ Base64Utils.encodeToString(claims.getBytes());
String signature = Base64Utils.encodeToString("signature".getBytes());
Token token = new Token(content + "." + signature);
assertThat(token.getExpiry()).isEqualTo(2147483647);
assertThat(token.getIssuer()).isEqualTo("http://localhost:8080/uaa/oauth/token");
assertThat(token.getSignatureAlgorithm()).isEqualTo("RS256");
assertThat(token.getKeyId()).isEqualTo("key-id");
assertThat(token.getContent()).isEqualTo(content.getBytes());
assertThat(token.getSignature())
.isEqualTo(Base64Utils.decodeFromString(signature));
}
@Test
public void getSignatureAlgorithmWhenAlgIsNullShouldThrowException()
throws Exception {
String header = "{\"kid\": \"key-id\", \"typ\": \"JWT\"}";
String claims = "{\"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\"}";
Token token = createToken(header, claims);
this.thrown
.expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN));
token.getSignatureAlgorithm();
}
@Test
public void getIssuerWhenIssIsNullShouldThrowException() throws Exception {
String header = "{\"alg\": \"RS256\", \"kid\": \"key-id\", \"typ\": \"JWT\"}";
String claims = "{\"exp\": 2147483647}";
Token token = createToken(header, claims);
this.thrown
.expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN));
token.getIssuer();
}
@Test
public void getKidWhenKidIsNullShouldThrowException() throws Exception {
String header = "{\"alg\": \"RS256\", \"typ\": \"JWT\"}";
String claims = "{\"exp\": 2147483647}";
Token token = createToken(header, claims);
this.thrown
.expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN));
token.getKeyId();
}
@Test
public void getExpiryWhenExpIsNullShouldThrowException() throws Exception {
String header = "{\"alg\": \"RS256\", \"kid\": \"key-id\", \"typ\": \"JWT\"}";
String claims = "{\"iss\": \"http://localhost:8080/uaa/oauth/token\"" + "}";
Token token = createToken(header, claims);
this.thrown
.expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN));
token.getExpiry();
}
private Token createToken(String header, String claims) {
Token token = new Token(Base64Utils.encodeToString(header.getBytes()) + "."
+ Base64Utils.encodeToString(claims.getBytes()) + "."
+ Base64Utils.encodeToString("signature".getBytes()));
return token;
}
}

@ -1,268 +0,0 @@
/*
* 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.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Collections;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.Base64Utils;
import org.springframework.util.StreamUtils;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link TokenValidator}.
*
* @author Madhura Bhave
*/
public class TokenValidatorTests {
private static final byte[] DOT = ".".getBytes();
private static final Charset UTF_8 = Charset.forName("UTF-8");
@Rule
public ExpectedException thrown = ExpectedException.none();
@Mock
private CloudFoundrySecurityService securityService;
private TokenValidator tokenValidator;
private static final String VALID_KEY = "-----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-----";
private static final String INVALID_KEY = "-----BEGIN PUBLIC KEY-----\n"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxzYuc22QSst/dS7geYYK\n"
+ "5l5kLxU0tayNdixkEQ17ix+CUcUbKIsnyftZxaCYT46rQtXgCaYRdJcbB3hmyrOa\n"
+ "vkhTpX79xJZnQmfuamMbZBqitvscxW9zRR9tBUL6vdi/0rpoUwPMEh8+Bw7CgYR0\n"
+ "FK0DhWYBNDfe9HKcyZEv3max8Cdq18htxjEsdYO0iwzhtKRXomBWTdhD5ykd/fAC\n"
+ "VTr4+KEY+IeLvubHVmLUhbE5NgWXxrRpGasDqzKhCTmsa2Ysf712rl57SlH0Wz/M\n"
+ "r3F7aM9YpErzeYLrl0GhQr9BVJxOvXcVd4kmY+XkiCcrkyS1cnghnllh+LCwQu1s\n"
+ "YwIDAQAB\n-----END PUBLIC KEY-----";
private static final Map<String, String> INVALID_KEYS = Collections
.singletonMap("invalid-key", INVALID_KEY);
private static final Map<String, String> VALID_KEYS = Collections
.singletonMap("valid-key", VALID_KEY);
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
this.tokenValidator = new TokenValidator(this.securityService);
}
@Test
public void validateTokenWhenKidValidationFailsTwiceShouldThrowException()
throws Exception {
ReflectionTestUtils.setField(this.tokenValidator, "tokenKeys", INVALID_KEYS);
given(this.securityService.fetchTokenKeys()).willReturn(INVALID_KEYS);
String header = "{\"alg\": \"RS256\", \"kid\": \"valid-key\",\"typ\": \"JWT\"}";
String claims = "{\"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}";
this.thrown
.expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_KEY_ID));
this.tokenValidator.validate(
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
}
@Test
public void validateTokenWhenKidValidationSucceedsInTheSecondAttempt()
throws Exception {
ReflectionTestUtils.setField(this.tokenValidator, "tokenKeys", INVALID_KEYS);
given(this.securityService.fetchTokenKeys()).willReturn(VALID_KEYS);
given(this.securityService.getUaaUrl()).willReturn("http://localhost:8080/uaa");
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\",\"typ\": \"JWT\"}";
String claims = "{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}";
this.tokenValidator.validate(
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
verify(this.securityService).fetchTokenKeys();
}
@Test
public void validateTokenShouldFetchTokenKeysIfNull() throws Exception {
given(this.securityService.fetchTokenKeys()).willReturn(VALID_KEYS);
given(this.securityService.getUaaUrl()).willReturn("http://localhost:8080/uaa");
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\",\"typ\": \"JWT\"}";
String claims = "{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}";
this.tokenValidator.validate(
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
verify(this.securityService).fetchTokenKeys();
}
@Test
public void validateTokenWhenValidShouldNotFetchTokenKeys() throws Exception {
ReflectionTestUtils.setField(this.tokenValidator, "tokenKeys", VALID_KEYS);
given(this.securityService.getUaaUrl()).willReturn("http://localhost:8080/uaa");
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\",\"typ\": \"JWT\"}";
String claims = "{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}";
this.tokenValidator.validate(
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
verify(this.securityService, Mockito.never()).fetchTokenKeys();
}
@Test
public void validateTokenWhenSignatureInvalidShouldThrowException() throws Exception {
ReflectionTestUtils.setField(this.tokenValidator, "tokenKeys",
Collections.singletonMap("valid-key", INVALID_KEY));
given(this.securityService.getUaaUrl()).willReturn("http://localhost:8080/uaa");
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\",\"typ\": \"JWT\"}";
String claims = "{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}";
this.thrown.expect(
AuthorizationExceptionMatcher.withReason(Reason.INVALID_SIGNATURE));
this.tokenValidator.validate(
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
}
@Test
public void validateTokenWhenTokenAlgorithmIsNotRS256ShouldThrowException()
throws Exception {
given(this.securityService.fetchTokenKeys()).willReturn(VALID_KEYS);
String header = "{ \"alg\": \"HS256\", \"typ\": \"JWT\"}";
String claims = "{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}";
this.thrown.expect(AuthorizationExceptionMatcher
.withReason(Reason.UNSUPPORTED_TOKEN_SIGNING_ALGORITHM));
this.tokenValidator.validate(
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
}
@Test
public void validateTokenWhenExpiredShouldThrowException() throws Exception {
given(this.securityService.fetchTokenKeys()).willReturn(VALID_KEYS);
given(this.securityService.fetchTokenKeys()).willReturn(VALID_KEYS);
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\", \"typ\": \"JWT\"}";
String claims = "{ \"jti\": \"0236399c350c47f3ae77e67a75e75e7d\", \"exp\": 1477509977, \"scope\": [\"actuator.read\"]}";
this.thrown
.expect(AuthorizationExceptionMatcher.withReason(Reason.TOKEN_EXPIRED));
this.tokenValidator.validate(
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
}
@Test
public void validateTokenWhenIssuerIsNotValidShouldThrowException() throws Exception {
given(this.securityService.fetchTokenKeys()).willReturn(VALID_KEYS);
given(this.securityService.getUaaUrl()).willReturn("http://other-uaa.com");
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\", \"typ\": \"JWT\", \"scope\": [\"actuator.read\"]}";
String claims = "{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\"}";
this.thrown
.expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_ISSUER));
this.tokenValidator.validate(
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
}
@Test
public void validateTokenWhenAudienceIsNotValidShouldThrowException()
throws Exception {
given(this.securityService.fetchTokenKeys()).willReturn(VALID_KEYS);
given(this.securityService.getUaaUrl()).willReturn("http://localhost:8080/uaa");
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\", \"typ\": \"JWT\"}";
String claims = "{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"foo.bar\"]}";
this.thrown.expect(
AuthorizationExceptionMatcher.withReason(Reason.INVALID_AUDIENCE));
this.tokenValidator.validate(
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
}
private String getSignedToken(byte[] header, byte[] claims) throws Exception {
PrivateKey privateKey = getPrivateKey();
Signature signature = Signature.getInstance("SHA256WithRSA");
signature.initSign(privateKey);
byte[] content = dotConcat(Base64Utils.encodeUrlSafe(header),
Base64Utils.encode(claims));
signature.update(content);
byte[] crypto = signature.sign();
byte[] token = dotConcat(Base64Utils.encodeUrlSafe(header),
Base64Utils.encodeUrlSafe(claims), Base64Utils.encodeUrlSafe(crypto));
return new String(token, UTF_8);
}
private PrivateKey getPrivateKey()
throws InvalidKeySpecException, NoSuchAlgorithmException {
String signingKey = "-----BEGIN PRIVATE KEY-----\n"
+ "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDSbn2Xa72IOcxu\n"
+ "tcd+qQ6ufZ1VDe98EmpwO4VQrTd37U9kZtWU0KqeSkgnyzIWmlbyWOdbB4/v4uJa\n"
+ "lGjPQjt9hvd3xOOFXzpj33sWXgMGvGAzopMk64T+7GegOFlDXguA5TZyReM7M51O\n"
+ "ycYwpAEsKXS+lxcG0UsxpJum/WjOLyHsMnJVnoScVBlRYZ2BMyEOuap69/H3lT/X\n"
+ "pzlYEM6SrAifsaWvL2f1K7HKBt/yDkDOlZy6xmAMsghnslNSV0FvypTZrQOXia8t\n"
+ "k6fjA+iN+P0LDZAgKxzn4/B/bV8/6HN/7VZJEdudi/y5qdE7SBnx6QZqCEz/YfqC\n"
+ "olujacgnAgMBAAECggEAc9X2tJ/OWWrXqinOg160gkELloJxTi8lAFsDbAGuAwpT\n"
+ "JcWl1KF5CmGBjsY/8ElNi2J9GJL1HOwcBhikCVNARD1DhF6RkB13mvquWwWtTMvt\n"
+ "eP8JWM19DIc+E+hw2rCuTGngqs7l4vTqpzBTNPtS2eiIJ1IsjsgvSEiAlk/wnW48\n"
+ "11cf6SQMQcT3HNTWrS+yLycEuWKb6Khh8RpD9D+i8w2+IspWz5lTP7BrKCUNsLOx\n"
+ "6+5T52HcaZ9z3wMnDqfqIKWl3h8M+q+HFQ4EN5BPWYV4fF7EOx7+Qf2fKDFPoTjC\n"
+ "VTWzDRNAA1xPqwdF7IdPVOXCdaUJDOhHeXZGaTNSwQKBgQDxb9UiR/Jh1R3muL7I\n"
+ "neIt1gXa0O+SK7NWYl4DkArYo7V81ztxI8r+xKEeu5zRZZkpaJHxOnd3VfADascw\n"
+ "UfALvxGxN2z42lE6zdhrmxZ3ma+akQFsv7NyXcBT00sdW+xmOiCaAj0cgxNOXiV3\n"
+ "sYOwUy3SqUIPO2obpb+KC5ALHwKBgQDfH+NSQ/jn89oVZ3lzUORa+Z+aL1TGsgzs\n"
+ "p7IG0MTEYiR9/AExYUwJab0M4PDXhumeoACMfkCFALNVhpch2nXZv7X5445yRgfD\n"
+ "ONY4WknecuA0rfCLTruNWnQ3RR+BXmd9jD/5igd9hEIawz3V+jCHvAtzI8/CZIBt\n"
+ "AArBs5kp+QKBgQCdxwN1n6baIDemK10iJWtFoPO6h4fH8h8EeMwPb/ZmlLVpnA4Q\n"
+ "Zd+mlkDkoJ5eiRKKaPfWuOqRZeuvj/wTq7g/NOIO+bWQ+rrSvuqLh5IrHpgPXmub\n"
+ "8bsHJhUlspMH4KagN6ROgOAG3fGj6Qp7KdpxRCpR3KJ66czxvGNrhxre6QKBgB+s\n"
+ "MCGiYnfSprd5G8VhyziazKwfYeJerfT+DQhopDXYVKPJnQW8cQW5C8wDNkzx6sHI\n"
+ "pqtK1K/MnKhcVaHJmAcT7qoNQlA4Xqu4qrgPIQNBvU/dDRNJVthG6c5aspEzrG8m\n"
+ "9IHgtRV9K8EOy/1O6YqrB9kNUVWf3JccdWpvqyNJAoGAORzJiQCOk4egbdcozDTo\n"
+ "4Tg4qk/03qpTy5k64DxkX1nJHu8V/hsKwq9Af7Fj/iHy2Av54BLPlBaGPwMi2bzB\n"
+ "gYjmUomvx/fqOTQks9Rc4PIMB43p6Rdj0sh+52SKPDR2eHbwsmpuQUXnAs20BPPI\n"
+ "J/OOn5zOs8yf26os0q3+JUM=\n-----END PRIVATE KEY-----";
String privateKey = signingKey.replace("-----BEGIN PRIVATE KEY-----\n", "");
privateKey = privateKey.replace("-----END PRIVATE KEY-----", "");
byte[] pkcs8EncodedBytes = Base64.decodeBase64(privateKey);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8EncodedBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
private byte[] dotConcat(byte[]... bytes) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
for (int i = 0; i < bytes.length; i++) {
if (i > 0) {
StreamUtils.copy(DOT, result);
}
StreamUtils.copy(bytes[i], result);
}
return result.toByteArray();
}
}
Loading…
Cancel
Save