Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in / Register
Toggle navigation
S
spring-boot
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
DEMO
spring-boot
Commits
4990b52b
Commit
4990b52b
authored
Aug 28, 2017
by
Madhura Bhave
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Reinstate CloudFoundry support for actuators
Closes gh-9996
parent
bacbe045
Changes
22
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
2872 additions
and
109 deletions
+2872
-109
AccessLevel.java
...pringframework/boot/actuate/cloudfoundry/AccessLevel.java
+67
-0
CloudFoundryActuatorAutoConfiguration.java
...e/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java
+132
-0
CloudFoundryAuthorizationException.java
...uate/cloudfoundry/CloudFoundryAuthorizationException.java
+92
-0
CloudFoundrySecurityInterceptor.java
...actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java
+141
-0
CloudFoundrySecurityService.java
...oot/actuate/cloudfoundry/CloudFoundrySecurityService.java
+146
-0
CloudFoundryWebEndpointServletHandlerMapping.java
...foundry/CloudFoundryWebEndpointServletHandlerMapping.java
+182
-0
SkipSslVerificationHttpRequestFactory.java
...e/cloudfoundry/SkipSslVerificationHttpRequestFactory.java
+94
-0
Token.java
.../org/springframework/boot/actuate/cloudfoundry/Token.java
+126
-0
TokenValidator.java
...ngframework/boot/actuate/cloudfoundry/TokenValidator.java
+142
-0
spring.factories
...oot-actuator/src/main/resources/META-INF/spring.factories
+1
-0
AccessLevelTests.java
...framework/boot/actuate/cloudfoundry/AccessLevelTests.java
+54
-0
AuthorizationExceptionMatcher.java
...t/actuate/cloudfoundry/AuthorizationExceptionMatcher.java
+49
-0
CloudFoundryActuatorAutoConfigurationTests.java
...udfoundry/CloudFoundryActuatorAutoConfigurationTests.java
+192
-0
CloudFoundryAuthorizationExceptionTests.java
...cloudfoundry/CloudFoundryAuthorizationExceptionTests.java
+92
-0
CloudFoundryMvcWebEndpointIntegrationTests.java
...udfoundry/CloudFoundryMvcWebEndpointIntegrationTests.java
+305
-0
CloudFoundrySecurityInterceptorTests.java
...te/cloudfoundry/CloudFoundrySecurityInterceptorTests.java
+154
-0
CloudFoundrySecurityServiceTests.java
...ctuate/cloudfoundry/CloudFoundrySecurityServiceTests.java
+219
-0
SkipSslVerificationHttpRequestFactoryTests.java
...udfoundry/SkipSslVerificationHttpRequestFactoryTests.java
+93
-0
TokenTests.java
...springframework/boot/actuate/cloudfoundry/TokenTests.java
+140
-0
TokenValidatorTests.java
...mework/boot/actuate/cloudfoundry/TokenValidatorTests.java
+269
-0
AbstractWebEndpointServletHandlerMapping.java
...int/web/mvc/AbstractWebEndpointServletHandlerMapping.java
+176
-0
WebEndpointServletHandlerMapping.java
...ot/endpoint/web/mvc/WebEndpointServletHandlerMapping.java
+6
-109
No files found.
spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/AccessLevel.java
0 → 100644
View file @
4990b52b
/*
* 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
);
}
}
spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java
0 → 100644
View file @
4990b52b
/*
* 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.Arrays
;
import
org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointProvider
;
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.ConditionalOnBean
;
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.condition.ConditionalOnWebApplication
;
import
org.springframework.boot.autoconfigure.security.SecurityProperties
;
import
org.springframework.boot.cloud.CloudPlatform
;
import
org.springframework.boot.endpoint.web.WebEndpointOperation
;
import
org.springframework.boot.web.client.RestTemplateBuilder
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.core.annotation.Order
;
import
org.springframework.core.env.Environment
;
import
org.springframework.http.HttpMethod
;
import
org.springframework.security.config.annotation.web.WebSecurityConfigurer
;
import
org.springframework.security.config.annotation.web.builders.WebSecurity
;
import
org.springframework.security.web.util.matcher.AntPathRequestMatcher
;
import
org.springframework.web.cors.CorsConfiguration
;
import
org.springframework.web.servlet.DispatcherServlet
;
/**
* {@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
{
/**
* Configuration for MVC endpoints on Cloud Foundry.
*/
@Configuration
@ConditionalOnWebApplication
(
type
=
ConditionalOnWebApplication
.
Type
.
SERVLET
)
@ConditionalOnClass
(
DispatcherServlet
.
class
)
@ConditionalOnBean
(
DispatcherServlet
.
class
)
static
class
MvcWebEndpointConfiguration
{
@Bean
public
CloudFoundryWebEndpointServletHandlerMapping
cloudFoundryWebEndpointServletHandlerMapping
(
EndpointProvider
<
WebEndpointOperation
>
provider
,
Environment
environment
,
RestTemplateBuilder
builder
)
{
CloudFoundryWebEndpointServletHandlerMapping
handlerMapping
=
new
CloudFoundryWebEndpointServletHandlerMapping
(
"/cloudfoundryapplication"
,
provider
.
getEndpoints
(),
getCorsConfiguration
(),
getSecurityInterceptor
(
builder
,
environment
));
return
handlerMapping
;
}
private
CloudFoundrySecurityInterceptor
getSecurityInterceptor
(
RestTemplateBuilder
restTemplateBuilder
,
Environment
environment
)
{
CloudFoundrySecurityService
cloudfoundrySecurityService
=
getCloudFoundrySecurityService
(
restTemplateBuilder
,
environment
);
TokenValidator
tokenValidator
=
new
TokenValidator
(
cloudfoundrySecurityService
);
return
new
CloudFoundrySecurityInterceptor
(
tokenValidator
,
cloudfoundrySecurityService
,
environment
.
getProperty
(
"vcap.application.application_id"
));
}
private
CloudFoundrySecurityService
getCloudFoundrySecurityService
(
RestTemplateBuilder
restTemplateBuilder
,
Environment
environment
)
{
String
cloudControllerUrl
=
environment
.
getProperty
(
"vcap.application.cf_api"
);
boolean
skipSslValidation
=
environment
.
getProperty
(
"management.cloudfoundry.skip-ssl-validation"
,
Boolean
.
class
,
false
);
return
cloudControllerUrl
==
null
?
null
:
new
CloudFoundrySecurityService
(
restTemplateBuilder
,
cloudControllerUrl
,
skipSslValidation
);
}
private
CorsConfiguration
getCorsConfiguration
()
{
CorsConfiguration
corsConfiguration
=
new
CorsConfiguration
();
corsConfiguration
.
addAllowedOrigin
(
CorsConfiguration
.
ALL
);
corsConfiguration
.
setAllowedMethods
(
Arrays
.
asList
(
HttpMethod
.
GET
.
name
(),
HttpMethod
.
POST
.
name
()));
corsConfiguration
.
setAllowedHeaders
(
Arrays
.
asList
(
"Authorization"
,
"X-Cf-App-Instance"
,
"Content-Type"
));
return
corsConfiguration
;
}
}
/**
* {@link WebSecurityConfigurer} to tell Spring Security to
* ignore cloudfoundry specific paths. The Cloud foundry endpoints
* are protected by their own security interceptor.
*/
@ConditionalOnClass
(
WebSecurity
.
class
)
@Order
(
SecurityProperties
.
IGNORED_ORDER
)
@Configuration
public
static
class
IgnoredPathsWebSecurityConfigurer
implements
WebSecurityConfigurer
<
WebSecurity
>
{
@Override
public
void
init
(
WebSecurity
builder
)
throws
Exception
{
builder
.
ignoring
().
requestMatchers
(
new
AntPathRequestMatcher
(
"/cloudfoundryapplication/**"
));
}
@Override
public
void
configure
(
WebSecurity
builder
)
throws
Exception
{
}
}
}
spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryAuthorizationException.java
0 → 100644
View file @
4990b52b
/*
* 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
;
}
}
}
spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java
0 → 100644
View file @
4990b52b
/*
* 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
org.apache.commons.logging.Log
;
import
org.apache.commons.logging.LogFactory
;
import
org.springframework.http.HttpMethod
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.util.StringUtils
;
import
org.springframework.web.cors.CorsUtils
;
/**
* Security interceptor to validate the cloud foundry token.
*
* @author Madhura Bhave
*/
class
CloudFoundrySecurityInterceptor
{
private
static
final
Log
logger
=
LogFactory
.
getLog
(
CloudFoundrySecurityInterceptor
.
class
);
private
final
TokenValidator
tokenValidator
;
private
final
CloudFoundrySecurityService
cloudFoundrySecurityService
;
private
final
String
applicationId
;
private
static
SecurityResponse
SUCCESS
=
SecurityResponse
.
success
();
CloudFoundrySecurityInterceptor
(
TokenValidator
tokenValidator
,
CloudFoundrySecurityService
cloudFoundrySecurityService
,
String
applicationId
)
{
this
.
tokenValidator
=
tokenValidator
;
this
.
cloudFoundrySecurityService
=
cloudFoundrySecurityService
;
this
.
applicationId
=
applicationId
;
}
SecurityResponse
preHandle
(
HttpServletRequest
request
,
String
endpointId
)
{
if
(
CorsUtils
.
isPreFlightRequest
(
request
))
{
return
SecurityResponse
.
success
();
}
try
{
if
(!
StringUtils
.
hasText
(
this
.
applicationId
))
{
throw
new
CloudFoundryAuthorizationException
(
CloudFoundryAuthorizationException
.
Reason
.
SERVICE_UNAVAILABLE
,
"Application id is not available"
);
}
if
(
this
.
cloudFoundrySecurityService
==
null
)
{
throw
new
CloudFoundryAuthorizationException
(
CloudFoundryAuthorizationException
.
Reason
.
SERVICE_UNAVAILABLE
,
"Cloud controller URL is not available"
);
}
if
(
HttpMethod
.
OPTIONS
.
matches
(
request
.
getMethod
()))
{
return
SUCCESS
;
}
check
(
request
,
endpointId
);
}
catch
(
Exception
ex
)
{
logger
.
error
(
ex
);
if
(
ex
instanceof
CloudFoundryAuthorizationException
)
{
CloudFoundryAuthorizationException
cfException
=
(
CloudFoundryAuthorizationException
)
ex
;
return
new
SecurityResponse
(
cfException
.
getStatusCode
(),
"{\"security_error\":\""
+
cfException
.
getMessage
()
+
"\"}"
);
}
return
new
SecurityResponse
(
HttpStatus
.
INTERNAL_SERVER_ERROR
,
ex
.
getMessage
());
}
return
SecurityResponse
.
success
();
}
private
void
check
(
HttpServletRequest
request
,
String
path
)
throws
Exception
{
Token
token
=
getToken
(
request
);
this
.
tokenValidator
.
validate
(
token
);
AccessLevel
accessLevel
=
this
.
cloudFoundrySecurityService
.
getAccessLevel
(
token
.
toString
(),
this
.
applicationId
);
if
(!
accessLevel
.
isAccessAllowed
(
path
))
{
throw
new
CloudFoundryAuthorizationException
(
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
(
CloudFoundryAuthorizationException
.
Reason
.
MISSING_AUTHORIZATION
,
"Authorization header is missing or invalid"
);
}
return
new
Token
(
authorization
.
substring
(
bearerPrefix
.
length
()));
}
/**
* Response from the security interceptor.
*/
static
class
SecurityResponse
{
private
final
HttpStatus
status
;
private
final
String
message
;
SecurityResponse
(
HttpStatus
status
)
{
this
(
status
,
null
);
}
SecurityResponse
(
HttpStatus
status
,
String
message
)
{
this
.
status
=
status
;
this
.
message
=
message
;
}
public
HttpStatus
getStatus
()
{
return
this
.
status
;
}
public
String
getMessage
()
{
return
this
.
message
;
}
static
SecurityResponse
success
()
{
return
new
SecurityResponse
(
HttpStatus
.
OK
);
}
}
}
spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityService.java
0 → 100644
View file @
4990b52b
/*
* 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
*/
class
CloudFoundrySecurityService
{
private
final
RestTemplate
restTemplate
;
private
final
String
cloudControllerUrl
;
private
String
uaaUrl
;
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
<
String
,
String
>();
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
;
}
}
spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryWebEndpointServletHandlerMapping.java
0 → 100644
View file @
4990b52b
/*
* 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.lang.reflect.Method
;
import
java.util.Arrays
;
import
java.util.Collection
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.LinkedHashMap
;
import
java.util.Map
;
import
java.util.stream.Collectors
;
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.endpoint.EndpointInfo
;
import
org.springframework.boot.endpoint.OperationInvoker
;
import
org.springframework.boot.endpoint.ParameterMappingException
;
import
org.springframework.boot.endpoint.web.EndpointLinksResolver
;
import
org.springframework.boot.endpoint.web.Link
;
import
org.springframework.boot.endpoint.web.WebEndpointOperation
;
import
org.springframework.boot.endpoint.web.WebEndpointResponse
;
import
org.springframework.boot.endpoint.web.mvc.AbstractWebEndpointServletHandlerMapping
;
import
org.springframework.http.HttpMethod
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.ResponseEntity
;
import
org.springframework.util.ReflectionUtils
;
import
org.springframework.web.bind.annotation.RequestBody
;
import
org.springframework.web.bind.annotation.ResponseBody
;
import
org.springframework.web.cors.CorsConfiguration
;
import
org.springframework.web.servlet.HandlerMapping
;
import
org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping
;
/**
* A custom {@link RequestMappingInfoHandlerMapping} that makes web endpoints available
* on Cloudfoundry specific URLS over HTTP using Spring MVC.
*
* @author Madhura Bhave
*/
class
CloudFoundryWebEndpointServletHandlerMapping
extends
AbstractWebEndpointServletHandlerMapping
{
private
final
Method
handle
=
ReflectionUtils
.
findMethod
(
OperationHandler
.
class
,
"handle"
,
HttpServletRequest
.
class
,
Map
.
class
);
private
final
Method
links
=
ReflectionUtils
.
findMethod
(
CloudFoundryWebEndpointServletHandlerMapping
.
class
,
"links"
,
HttpServletRequest
.
class
,
HttpServletResponse
.
class
);
private
static
final
Log
logger
=
LogFactory
.
getLog
(
CloudFoundryWebEndpointServletHandlerMapping
.
class
);
private
final
CloudFoundrySecurityInterceptor
securityInterceptor
;
private
final
EndpointLinksResolver
endpointLinksResolver
=
new
EndpointLinksResolver
();
CloudFoundryWebEndpointServletHandlerMapping
(
String
endpointPath
,
Collection
<
EndpointInfo
<
WebEndpointOperation
>>
webEndpoints
,
CorsConfiguration
corsConfiguration
,
CloudFoundrySecurityInterceptor
securityInterceptor
)
{
super
(
endpointPath
,
webEndpoints
,
corsConfiguration
);
this
.
securityInterceptor
=
securityInterceptor
;
}
@Override
protected
Method
getLinks
()
{
return
this
.
links
;
}
@ResponseBody
private
Map
<
String
,
Map
<
String
,
Link
>>
links
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
CloudFoundrySecurityInterceptor
.
SecurityResponse
securityResponse
=
this
.
securityInterceptor
.
preHandle
(
request
,
""
);
if
(!
securityResponse
.
getStatus
().
equals
(
HttpStatus
.
OK
))
{
sendFailureResponse
(
response
,
securityResponse
);
}
AccessLevel
accessLevel
=
AccessLevel
.
get
(
request
);
Map
<
String
,
Link
>
links
=
this
.
endpointLinksResolver
.
resolveLinks
(
getEndpoints
(),
request
.
getRequestURL
().
toString
());
Map
<
String
,
Link
>
filteredLinks
=
new
LinkedHashMap
<>();
if
(
accessLevel
==
null
)
{
return
Collections
.
singletonMap
(
"_links"
,
filteredLinks
);
}
filteredLinks
=
links
.
entrySet
().
stream
()
.
filter
(
e
->
e
.
getKey
().
equals
(
"self"
)
||
accessLevel
.
isAccessAllowed
(
e
.
getKey
()))
.
collect
(
Collectors
.
toMap
(
Map
.
Entry
::
getKey
,
Map
.
Entry
::
getValue
));
return
Collections
.
singletonMap
(
"_links"
,
filteredLinks
);
}
private
void
sendFailureResponse
(
HttpServletResponse
response
,
CloudFoundrySecurityInterceptor
.
SecurityResponse
securityResponse
)
{
try
{
response
.
sendError
(
securityResponse
.
getStatus
().
value
(),
securityResponse
.
getMessage
());
}
catch
(
Exception
ex
)
{
logger
.
debug
(
"Failed to send error response"
,
ex
);
}
}
@Override
protected
void
registerMappingForOperation
(
WebEndpointOperation
operation
)
{
registerMapping
(
createRequestMappingInfo
(
operation
),
new
OperationHandler
(
operation
.
getOperationInvoker
(),
operation
.
getId
(),
this
.
securityInterceptor
),
this
.
handle
);
}
/**
* Handler which has the handler method and security interceptor.
*/
final
class
OperationHandler
{
private
final
OperationInvoker
operationInvoker
;
private
final
String
endpointId
;
private
final
CloudFoundrySecurityInterceptor
securityInterceptor
;
OperationHandler
(
OperationInvoker
operationInvoker
,
String
id
,
CloudFoundrySecurityInterceptor
securityInterceptor
)
{
this
.
operationInvoker
=
operationInvoker
;
this
.
endpointId
=
id
;
this
.
securityInterceptor
=
securityInterceptor
;
}
@SuppressWarnings
(
"unchecked"
)
@ResponseBody
public
Object
handle
(
HttpServletRequest
request
,
@RequestBody
(
required
=
false
)
Map
<
String
,
String
>
body
)
{
CloudFoundrySecurityInterceptor
.
SecurityResponse
securityResponse
=
this
.
securityInterceptor
.
preHandle
(
request
,
this
.
endpointId
);
if
(!
securityResponse
.
getStatus
().
equals
(
HttpStatus
.
OK
))
{
return
failureResponse
(
securityResponse
);
}
Map
<
String
,
Object
>
arguments
=
new
HashMap
<>((
Map
<
String
,
String
>)
request
.
getAttribute
(
HandlerMapping
.
URI_TEMPLATE_VARIABLES_ATTRIBUTE
));
HttpMethod
httpMethod
=
HttpMethod
.
valueOf
(
request
.
getMethod
());
if
(
body
!=
null
&&
HttpMethod
.
POST
==
httpMethod
)
{
arguments
.
putAll
(
body
);
}
request
.
getParameterMap
().
forEach
((
name
,
values
)
->
arguments
.
put
(
name
,
values
.
length
==
1
?
values
[
0
]
:
Arrays
.
asList
(
values
)));
try
{
return
handleResult
(
this
.
operationInvoker
.
invoke
(
arguments
),
httpMethod
);
}
catch
(
ParameterMappingException
ex
)
{
return
new
ResponseEntity
<
Void
>(
HttpStatus
.
BAD_REQUEST
);
}
}
private
Object
failureResponse
(
CloudFoundrySecurityInterceptor
.
SecurityResponse
response
)
{
return
handleResult
(
new
WebEndpointResponse
<>(
response
.
getMessage
(),
response
.
getStatus
().
value
()));
}
private
Object
handleResult
(
Object
result
)
{
return
handleResult
(
result
,
null
);
}
private
Object
handleResult
(
Object
result
,
HttpMethod
httpMethod
)
{
if
(
result
==
null
)
{
return
new
ResponseEntity
<>(
httpMethod
==
HttpMethod
.
GET
?
HttpStatus
.
NOT_FOUND
:
HttpStatus
.
NO_CONTENT
);
}
if
(!(
result
instanceof
WebEndpointResponse
))
{
return
result
;
}
WebEndpointResponse
<?>
response
=
(
WebEndpointResponse
<?>)
result
;
return
new
ResponseEntity
<
Object
>(
response
.
getBody
(),
HttpStatus
.
valueOf
(
response
.
getStatus
()));
}
}
}
spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/SkipSslVerificationHttpRequestFactory.java
0 → 100644
View file @
4990b52b
/*
* 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
)
{
}
}
}
spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/Token.java
0 → 100644
View file @
4990b52b
/*
* 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
;
};
}
spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/TokenValidator.java
0 → 100644
View file @
4990b52b
/*
* 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
*/
class
TokenValidator
{
private
final
CloudFoundrySecurityService
securityService
;
private
Map
<
String
,
String
>
tokenKeys
;
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"
);
}
}
}
spring-boot-actuator/src/main/resources/META-INF/spring.factories
View file @
4990b52b
...
...
@@ -2,6 +2,7 @@ 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.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,\
...
...
spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/AccessLevelTests.java
0 → 100644
View file @
4990b52b
/*
* 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
();
}
}
spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/AuthorizationExceptionMatcher.java
0 → 100644
View file @
4990b52b
/*
* 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
);
}
};
}
}
spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java
0 → 100644
View file @
4990b52b
/*
* 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.Arrays
;
import
org.junit.After
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration
;
import
org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration
;
import
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
;
import
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
;
import
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
;
import
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration
;
import
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
;
import
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
;
import
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
;
import
org.springframework.boot.context.properties.source.ConfigurationPropertySources
;
import
org.springframework.boot.test.util.TestPropertyValues
;
import
org.springframework.http.HttpMethod
;
import
org.springframework.mock.web.MockHttpServletRequest
;
import
org.springframework.mock.web.MockServletContext
;
import
org.springframework.security.web.FilterChainProxy
;
import
org.springframework.security.web.SecurityFilterChain
;
import
org.springframework.test.util.ReflectionTestUtils
;
import
org.springframework.web.client.RestTemplate
;
import
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
;
import
org.springframework.web.cors.CorsConfiguration
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
assertThat
;
/**
* Tests for {@link CloudFoundryActuatorAutoConfiguration}.
*
* @author Madhura Bhave
*/
public
class
CloudFoundryActuatorAutoConfigurationTests
{
private
AnnotationConfigWebApplicationContext
context
;
@Before
public
void
setup
()
{
this
.
context
=
new
AnnotationConfigWebApplicationContext
();
this
.
context
.
setServletContext
(
new
MockServletContext
());
this
.
context
.
register
(
SecurityAutoConfiguration
.
class
,
WebMvcAutoConfiguration
.
class
,
JacksonAutoConfiguration
.
class
,
DispatcherServletAutoConfiguration
.
class
,
HttpMessageConvertersAutoConfiguration
.
class
,
PropertyPlaceholderAutoConfiguration
.
class
,
RestTemplateAutoConfiguration
.
class
,
EndpointInfrastructureAutoConfiguration
.
class
,
ServletEndpointAutoConfiguration
.
class
,
CloudFoundryActuatorAutoConfiguration
.
class
);
}
@After
public
void
close
()
{
if
(
this
.
context
!=
null
)
{
this
.
context
.
close
();
}
}
@Test
public
void
cloudFoundryPlatformActive
()
throws
Exception
{
CloudFoundryWebEndpointServletHandlerMapping
handlerMapping
=
getHandlerMapping
();
assertThat
(
handlerMapping
.
getEndpointPath
()).
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
{
CloudFoundryWebEndpointServletHandlerMapping
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
{
CloudFoundryWebEndpointServletHandlerMapping
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
();
CloudFoundryWebEndpointServletHandlerMapping
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
();
CloudFoundryWebEndpointServletHandlerMapping
handlerMapping
=
this
.
context
.
getBean
(
"cloudFoundryWebEndpointServletHandlerMapping"
,
CloudFoundryWebEndpointServletHandlerMapping
.
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
();
FilterChainProxy
securityFilterChain
=
(
FilterChainProxy
)
this
.
context
.
getBean
(
"springSecurityFilterChain"
);
SecurityFilterChain
chain
=
securityFilterChain
.
getFilterChains
().
get
(
0
);
MockHttpServletRequest
request
=
new
MockHttpServletRequest
();
request
.
setServletPath
(
"/cloudfoundryapplication/my-path"
);
assertThat
(
chain
.
getFilters
()).
isEmpty
();
assertThat
(
chain
.
matches
(
request
)).
isTrue
();
request
.
setServletPath
(
"/some-other-path"
);
assertThat
(
chain
.
matches
(
request
)).
isFalse
();
}
@Test
public
void
cloudFoundryPlatformInactive
()
throws
Exception
{
this
.
context
.
refresh
();
assertThat
(
this
.
context
.
containsBean
(
"cloudFoundryWebEndpointServletHandlerMapping"
))
.
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
CloudFoundryWebEndpointServletHandlerMapping
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
(
"cloudFoundryWebEndpointServletHandlerMapping"
,
CloudFoundryWebEndpointServletHandlerMapping
.
class
);
}
}
spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryAuthorizationExceptionTests.java
0 → 100644
View file @
4990b52b
/*
* 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"
);
}
}
spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryMvcWebEndpointIntegrationTests.java
0 → 100644
View file @
4990b52b
This diff is collapsed.
Click to expand it.
spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptorTests.java
0 → 100644
View file @
4990b52b
/*
* 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.Before
;
import
org.junit.Test
;
import
org.mockito.ArgumentCaptor
;
import
org.mockito.BDDMockito
;
import
org.mockito.Mock
;
import
org.mockito.MockitoAnnotations
;
import
org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason
;
import
org.springframework.http.HttpHeaders
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.mock.web.MockHttpServletRequest
;
import
org.springframework.util.Base64Utils
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
assertThat
;
import
static
org
.
mockito
.
Mockito
.
verify
;
/**
* Tests for {@link CloudFoundrySecurityInterceptor}.
*
* @author Madhura Bhave
*/
public
class
CloudFoundrySecurityInterceptorTests
{
@Mock
private
TokenValidator
tokenValidator
;
@Mock
private
CloudFoundrySecurityService
securityService
;
private
CloudFoundrySecurityInterceptor
interceptor
;
private
MockHttpServletRequest
request
;
@Before
public
void
setup
()
throws
Exception
{
MockitoAnnotations
.
initMocks
(
this
);
this
.
interceptor
=
new
CloudFoundrySecurityInterceptor
(
this
.
tokenValidator
,
this
.
securityService
,
"my-app-id"
);
this
.
request
=
new
MockHttpServletRequest
();
}
@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"
);
CloudFoundrySecurityInterceptor
.
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
assertThat
(
response
.
getStatus
()).
isEqualTo
(
HttpStatus
.
OK
);
}
@Test
public
void
preHandleWhenTokenIsMissingShouldReturnFalse
()
throws
Exception
{
CloudFoundrySecurityInterceptor
.
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
assertThat
(
response
.
getStatus
())
.
isEqualTo
(
Reason
.
MISSING_AUTHORIZATION
.
getStatus
());
}
@Test
public
void
preHandleWhenTokenIsNotBearerShouldReturnFalse
()
throws
Exception
{
this
.
request
.
addHeader
(
"Authorization"
,
mockAccessToken
());
CloudFoundrySecurityInterceptor
.
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
assertThat
(
response
.
getStatus
())
.
isEqualTo
(
Reason
.
MISSING_AUTHORIZATION
.
getStatus
());
}
@Test
public
void
preHandleWhenApplicationIdIsNullShouldReturnFalse
()
throws
Exception
{
this
.
interceptor
=
new
CloudFoundrySecurityInterceptor
(
this
.
tokenValidator
,
this
.
securityService
,
null
);
this
.
request
.
addHeader
(
"Authorization"
,
"bearer "
+
mockAccessToken
());
CloudFoundrySecurityInterceptor
.
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
assertThat
(
response
.
getStatus
())
.
isEqualTo
(
Reason
.
SERVICE_UNAVAILABLE
.
getStatus
());
}
@Test
public
void
preHandleWhenCloudFoundrySecurityServiceIsNullShouldReturnFalse
()
throws
Exception
{
this
.
interceptor
=
new
CloudFoundrySecurityInterceptor
(
this
.
tokenValidator
,
null
,
"my-app-id"
);
this
.
request
.
addHeader
(
"Authorization"
,
"bearer "
+
mockAccessToken
());
CloudFoundrySecurityInterceptor
.
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
assertThat
(
response
.
getStatus
())
.
isEqualTo
(
Reason
.
SERVICE_UNAVAILABLE
.
getStatus
());
}
@Test
public
void
preHandleWhenAccessIsNotAllowedShouldReturnFalse
()
throws
Exception
{
String
accessToken
=
mockAccessToken
();
this
.
request
.
addHeader
(
"Authorization"
,
"bearer "
+
accessToken
);
BDDMockito
.
given
(
this
.
securityService
.
getAccessLevel
(
accessToken
,
"my-app-id"
))
.
willReturn
(
AccessLevel
.
RESTRICTED
);
CloudFoundrySecurityInterceptor
.
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
assertThat
(
response
.
getStatus
())
.
isEqualTo
(
Reason
.
ACCESS_DENIED
.
getStatus
());
}
@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
);
CloudFoundrySecurityInterceptor
.
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
ArgumentCaptor
<
Token
>
tokenArgumentCaptor
=
ArgumentCaptor
.
forClass
(
Token
.
class
);
verify
(
this
.
tokenValidator
).
validate
(
tokenArgumentCaptor
.
capture
());
Token
token
=
tokenArgumentCaptor
.
getValue
();
assertThat
(
token
.
toString
()).
isEqualTo
(
accessToken
);
assertThat
(
response
.
getStatus
()).
isEqualTo
(
HttpStatus
.
OK
);
assertThat
(
this
.
request
.
getAttribute
(
"cloudFoundryAccessLevel"
))
.
isEqualTo
(
AccessLevel
.
FULL
);
}
@Test
public
void
preHandleSuccessfulWithRestrictedAccess
()
throws
Exception
{
String
accessToken
=
mockAccessToken
();
this
.
request
.
addHeader
(
"Authorization"
,
"Bearer "
+
accessToken
);
BDDMockito
.
given
(
this
.
securityService
.
getAccessLevel
(
accessToken
,
"my-app-id"
))
.
willReturn
(
AccessLevel
.
RESTRICTED
);
CloudFoundrySecurityInterceptor
.
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"info"
);
ArgumentCaptor
<
Token
>
tokenArgumentCaptor
=
ArgumentCaptor
.
forClass
(
Token
.
class
);
verify
(
this
.
tokenValidator
).
validate
(
tokenArgumentCaptor
.
capture
());
Token
token
=
tokenArgumentCaptor
.
getValue
();
assertThat
(
token
.
toString
()).
isEqualTo
(
accessToken
);
assertThat
(
response
.
getStatus
()).
isEqualTo
(
HttpStatus
.
OK
);
assertThat
(
this
.
request
.
getAttribute
(
"cloudFoundryAccessLevel"
))
.
isEqualTo
(
AccessLevel
.
RESTRICTED
);
}
private
String
mockAccessToken
()
{
return
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b3B0YWwu"
+
"Y29tIiwiZXhwIjoxNDI2NDIwODAwLCJhd2Vzb21lIjp0cnVlfQ."
+
Base64Utils
.
encodeToString
(
"signature"
.
getBytes
());
}
}
spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityServiceTests.java
0 → 100644
View file @
4990b52b
/*
* 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
();
}
}
spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/SkipSslVerificationHttpRequestFactoryTests.java
0 → 100644
View file @
4990b52b
/*
* 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
;
}
}
spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/TokenTests.java
0 → 100644
View file @
4990b52b
/*
* 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
;
}
}
spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/TokenValidatorTests.java
0 → 100644
View file @
4990b52b
This diff is collapsed.
Click to expand it.
spring-boot/src/main/java/org/springframework/boot/endpoint/web/mvc/AbstractWebEndpointServletHandlerMapping.java
0 → 100644
View file @
4990b52b
/*
* 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
.
endpoint
.
web
.
mvc
;
import
java.lang.reflect.Method
;
import
java.util.Collection
;
import
java.util.List
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
org.springframework.beans.factory.InitializingBean
;
import
org.springframework.boot.endpoint.EndpointInfo
;
import
org.springframework.boot.endpoint.web.OperationRequestPredicate
;
import
org.springframework.boot.endpoint.web.WebEndpointOperation
;
import
org.springframework.util.StringUtils
;
import
org.springframework.web.accept.PathExtensionContentNegotiationStrategy
;
import
org.springframework.web.bind.annotation.RequestMethod
;
import
org.springframework.web.cors.CorsConfiguration
;
import
org.springframework.web.servlet.handler.HandlerInterceptorAdapter
;
import
org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition
;
import
org.springframework.web.servlet.mvc.condition.PatternsRequestCondition
;
import
org.springframework.web.servlet.mvc.condition.ProducesRequestCondition
;
import
org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition
;
import
org.springframework.web.servlet.mvc.method.RequestMappingInfo
;
import
org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping
;
/**
* A custom {@link RequestMappingInfoHandlerMapping} that makes web endpoints available
* over HTTP using Spring MVC.
*
* @author Andy Wilkinson
* @author Madhura Bhave
* @since 2.0.0
*/
public
abstract
class
AbstractWebEndpointServletHandlerMapping
extends
RequestMappingInfoHandlerMapping
implements
InitializingBean
{
private
final
String
endpointPath
;
private
final
Collection
<
EndpointInfo
<
WebEndpointOperation
>>
webEndpoints
;
private
final
CorsConfiguration
corsConfiguration
;
/**
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
* operations of the given {@code webEndpoints}.
* @param endpointPath the path beneath which all endpoints should be mapped
* @param collection the web endpoints operations
*/
public
AbstractWebEndpointServletHandlerMapping
(
String
endpointPath
,
Collection
<
EndpointInfo
<
WebEndpointOperation
>>
collection
)
{
this
(
endpointPath
,
collection
,
null
);
}
/**
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
* operations of the given {@code webEndpoints}.
* @param endpointPath the path beneath which all endpoints should be mapped
* @param webEndpoints the web endpoints
* @param corsConfiguration the CORS configuraton for the endpoints
*/
public
AbstractWebEndpointServletHandlerMapping
(
String
endpointPath
,
Collection
<
EndpointInfo
<
WebEndpointOperation
>>
webEndpoints
,
CorsConfiguration
corsConfiguration
)
{
this
.
endpointPath
=
(
endpointPath
.
startsWith
(
"/"
)
?
""
:
"/"
)
+
endpointPath
;
this
.
webEndpoints
=
webEndpoints
;
this
.
corsConfiguration
=
corsConfiguration
;
setOrder
(-
100
);
}
public
Collection
<
EndpointInfo
<
WebEndpointOperation
>>
getEndpoints
()
{
return
this
.
webEndpoints
;
}
public
String
getEndpointPath
()
{
return
this
.
endpointPath
;
}
@Override
protected
void
initHandlerMethods
()
{
this
.
webEndpoints
.
stream
()
.
flatMap
((
webEndpoint
)
->
webEndpoint
.
getOperations
().
stream
())
.
forEach
(
this
::
registerMappingForOperation
);
registerMapping
(
new
RequestMappingInfo
(
patternsRequestConditionForPattern
(
""
),
new
RequestMethodsRequestCondition
(
RequestMethod
.
GET
),
null
,
null
,
null
,
null
,
null
),
this
,
getLinks
());
}
@Override
protected
CorsConfiguration
initCorsConfiguration
(
Object
handler
,
Method
method
,
RequestMappingInfo
mapping
)
{
return
this
.
corsConfiguration
;
}
protected
abstract
Method
getLinks
();
protected
abstract
void
registerMappingForOperation
(
WebEndpointOperation
operation
);
protected
RequestMappingInfo
createRequestMappingInfo
(
WebEndpointOperation
operationInfo
)
{
OperationRequestPredicate
requestPredicate
=
operationInfo
.
getRequestPredicate
();
return
new
RequestMappingInfo
(
null
,
patternsRequestConditionForPattern
(
requestPredicate
.
getPath
()),
new
RequestMethodsRequestCondition
(
RequestMethod
.
valueOf
(
requestPredicate
.
getHttpMethod
().
name
())),
null
,
null
,
new
ConsumesRequestCondition
(
toStringArray
(
requestPredicate
.
getConsumes
())),
new
ProducesRequestCondition
(
toStringArray
(
requestPredicate
.
getProduces
())),
null
);
}
private
PatternsRequestCondition
patternsRequestConditionForPattern
(
String
path
)
{
return
new
PatternsRequestCondition
(
new
String
[]
{
this
.
endpointPath
+
(
StringUtils
.
hasText
(
path
)
?
"/"
+
path
:
""
)
},
null
,
null
,
false
,
false
);
}
private
String
[]
toStringArray
(
Collection
<
String
>
collection
)
{
return
collection
.
toArray
(
new
String
[
collection
.
size
()]);
}
@Override
protected
boolean
isHandler
(
Class
<?>
beanType
)
{
return
false
;
}
@Override
protected
RequestMappingInfo
getMappingForMethod
(
Method
method
,
Class
<?>
handlerType
)
{
return
null
;
}
@Override
protected
void
extendInterceptors
(
List
<
Object
>
interceptors
)
{
interceptors
.
add
(
new
SkipPathExtensionContentNegotiation
());
}
/**
* {@link HandlerInterceptorAdapter} to ensure that
* {@link PathExtensionContentNegotiationStrategy} is skipped for web endpoints.
*/
private
static
final
class
SkipPathExtensionContentNegotiation
extends
HandlerInterceptorAdapter
{
private
static
final
String
SKIP_ATTRIBUTE
=
PathExtensionContentNegotiationStrategy
.
class
.
getName
()
+
".SKIP"
;
@Override
public
boolean
preHandle
(
HttpServletRequest
request
,
HttpServletResponse
response
,
Object
handler
)
throws
Exception
{
request
.
setAttribute
(
SKIP_ATTRIBUTE
,
Boolean
.
TRUE
);
return
true
;
}
}
}
spring-boot/src/main/java/org/springframework/boot/endpoint/web/mvc/WebEndpointServletHandlerMapping.java
View file @
4990b52b
...
...
@@ -21,38 +21,25 @@ import java.util.Arrays;
import
java.util.Collection
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
org.springframework.beans.factory.InitializingBean
;
import
org.springframework.boot.endpoint.EndpointInfo
;
import
org.springframework.boot.endpoint.OperationInvoker
;
import
org.springframework.boot.endpoint.ParameterMappingException
;
import
org.springframework.boot.endpoint.web.EndpointLinksResolver
;
import
org.springframework.boot.endpoint.web.Link
;
import
org.springframework.boot.endpoint.web.OperationRequestPredicate
;
import
org.springframework.boot.endpoint.web.WebEndpointOperation
;
import
org.springframework.boot.endpoint.web.WebEndpointResponse
;
import
org.springframework.http.HttpMethod
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.ResponseEntity
;
import
org.springframework.util.ReflectionUtils
;
import
org.springframework.util.StringUtils
;
import
org.springframework.web.accept.PathExtensionContentNegotiationStrategy
;
import
org.springframework.web.bind.annotation.RequestBody
;
import
org.springframework.web.bind.annotation.RequestMethod
;
import
org.springframework.web.bind.annotation.ResponseBody
;
import
org.springframework.web.cors.CorsConfiguration
;
import
org.springframework.web.servlet.HandlerMapping
;
import
org.springframework.web.servlet.handler.HandlerInterceptorAdapter
;
import
org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition
;
import
org.springframework.web.servlet.mvc.condition.PatternsRequestCondition
;
import
org.springframework.web.servlet.mvc.condition.ProducesRequestCondition
;
import
org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition
;
import
org.springframework.web.servlet.mvc.method.RequestMappingInfo
;
import
org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping
;
/**
...
...
@@ -62,8 +49,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
* @author Andy Wilkinson
* @since 2.0.0
*/
public
class
WebEndpointServletHandlerMapping
extends
RequestMappingInfoHandlerMapping
implements
InitializingBean
{
public
class
WebEndpointServletHandlerMapping
extends
AbstractWebEndpointServletHandlerMapping
{
private
final
Method
handle
=
ReflectionUtils
.
findMethod
(
OperationHandler
.
class
,
"handle"
,
HttpServletRequest
.
class
,
Map
.
class
);
...
...
@@ -73,12 +59,6 @@ public class WebEndpointServletHandlerMapping extends RequestMappingInfoHandlerM
private
final
EndpointLinksResolver
endpointLinksResolver
=
new
EndpointLinksResolver
();
private
final
String
endpointPath
;
private
final
Collection
<
EndpointInfo
<
WebEndpointOperation
>>
webEndpoints
;
private
final
CorsConfiguration
corsConfiguration
;
/**
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
* operations of the given {@code webEndpoints}.
...
...
@@ -100,87 +80,23 @@ public class WebEndpointServletHandlerMapping extends RequestMappingInfoHandlerM
public
WebEndpointServletHandlerMapping
(
String
endpointPath
,
Collection
<
EndpointInfo
<
WebEndpointOperation
>>
webEndpoints
,
CorsConfiguration
corsConfiguration
)
{
this
.
endpointPath
=
(
endpointPath
.
startsWith
(
"/"
)
?
""
:
"/"
)
+
endpointPath
;
this
.
webEndpoints
=
webEndpoints
;
this
.
corsConfiguration
=
corsConfiguration
;
super
(
endpointPath
,
webEndpoints
,
corsConfiguration
);
setOrder
(-
100
);
}
public
Collection
<
EndpointInfo
<
WebEndpointOperation
>>
getEndpoints
()
{
return
this
.
webEndpoints
;
}
public
String
getEndpointPath
()
{
return
this
.
endpointPath
;
}
@Override
protected
void
initHandlerMethods
()
{
this
.
webEndpoints
.
stream
()
.
flatMap
((
webEndpoint
)
->
webEndpoint
.
getOperations
().
stream
())
.
forEach
(
this
::
registerMappingForOperation
);
registerMapping
(
new
RequestMappingInfo
(
patternsRequestConditionForPattern
(
""
),
new
RequestMethodsRequestCondition
(
RequestMethod
.
GET
),
null
,
null
,
null
,
null
,
null
),
this
,
this
.
links
);
}
@Override
protected
CorsConfiguration
initCorsConfiguration
(
Object
handler
,
Method
method
,
RequestMappingInfo
mapping
)
{
return
this
.
corsConfiguration
;
}
private
void
registerMappingForOperation
(
WebEndpointOperation
operation
)
{
protected
void
registerMappingForOperation
(
WebEndpointOperation
operation
)
{
registerMapping
(
createRequestMappingInfo
(
operation
),
new
OperationHandler
(
operation
.
getOperationInvoker
()),
this
.
handle
);
}
private
RequestMappingInfo
createRequestMappingInfo
(
WebEndpointOperation
operationInfo
)
{
OperationRequestPredicate
requestPredicate
=
operationInfo
.
getRequestPredicate
();
return
new
RequestMappingInfo
(
null
,
patternsRequestConditionForPattern
(
requestPredicate
.
getPath
()),
new
RequestMethodsRequestCondition
(
RequestMethod
.
valueOf
(
requestPredicate
.
getHttpMethod
().
name
())),
null
,
null
,
new
ConsumesRequestCondition
(
toStringArray
(
requestPredicate
.
getConsumes
())),
new
ProducesRequestCondition
(
toStringArray
(
requestPredicate
.
getProduces
())),
null
);
}
private
PatternsRequestCondition
patternsRequestConditionForPattern
(
String
path
)
{
return
new
PatternsRequestCondition
(
new
String
[]
{
this
.
endpointPath
+
(
StringUtils
.
hasText
(
path
)
?
"/"
+
path
:
""
)
},
null
,
null
,
false
,
false
);
}
private
String
[]
toStringArray
(
Collection
<
String
>
collection
)
{
return
collection
.
toArray
(
new
String
[
collection
.
size
()]);
}
@Override
protected
boolean
isHandler
(
Class
<?>
beanType
)
{
return
false
;
}
@Override
protected
RequestMappingInfo
getMappingForMethod
(
Method
method
,
Class
<?>
handlerType
)
{
return
null
;
}
@Override
protected
void
extendInterceptors
(
List
<
Object
>
interceptors
)
{
interceptors
.
add
(
new
SkipPathExtensionContentNegotiation
());
protected
Method
getLinks
()
{
return
this
.
links
;
}
@ResponseBody
private
Map
<
String
,
Map
<
String
,
Link
>>
links
(
HttpServletRequest
request
)
{
return
Collections
.
singletonMap
(
"_links"
,
this
.
endpointLinksResolver
.
resolveLinks
(
this
.
webEndpoints
,
request
.
getRequestURL
().
toString
()));
.
resolveLinks
(
getEndpoints
()
,
request
.
getRequestURL
().
toString
()));
}
/**
...
...
@@ -229,23 +145,4 @@ public class WebEndpointServletHandlerMapping extends RequestMappingInfoHandlerM
}
/**
* {@link HandlerInterceptorAdapter} to ensure that
* {@link PathExtensionContentNegotiationStrategy} is skipped for web endpoints.
*/
private
static
final
class
SkipPathExtensionContentNegotiation
extends
HandlerInterceptorAdapter
{
private
static
final
String
SKIP_ATTRIBUTE
=
PathExtensionContentNegotiationStrategy
.
class
.
getName
()
+
".SKIP"
;
@Override
public
boolean
preHandle
(
HttpServletRequest
request
,
HttpServletResponse
response
,
Object
handler
)
throws
Exception
{
request
.
setAttribute
(
SKIP_ATTRIBUTE
,
Boolean
.
TRUE
);
return
true
;
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment