Spring Boot: How to change the Content Security Policy at runtime? - java

I'm trying to hot-reload a change in the content security policy (CSP) of my Spring Boot application, i.e. the user should be able to change it via an admin UI without restarting the server.
The regular approach in Spring Boot is:
#Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) {
// ... lots more config here...
http.headers()
.addHeaderWriter(
StaticHeadersWriter(
"Content-Security-Policy",
"<some policy string>"
)
)
}
}
... but this doesn't allow for reconfiguration once it has been assigned.
Can I make this (re-)configurable at runtime? Reloading the application context is not an option, I need to be able to adapt only this particular setting.

Easy-Peasy, we only need to expose a (n appropriate) HeaderWriter as a bean! ContentSecurityPolicyHeaderWriter looks appropriate & sufficient for us, but we are also free to implement a custom:
private static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'";
#Bean
public ContentSecurityPolicyHeaderWriter myWriter(
#Value("${#my.policy.directive:DEFAULT_SRC_SELF_POLICY}") String initalDirectives
) {
return new ContentSecurityPolicyHeaderWriter(initalDirectives);
}
Then with:
#Autowired
private ContentSecurityPolicyHeaderWriter myHeadersWriter;
#Override
public void configure(HttpSecurity http) throws Exception {
// ... lots more config here...
http.headers()
.addHeaderWriter(myHeadersWriter);
}
..., we can change the header value with these demo controllers:
#GetMapping("/")
public String home() {
myHeadersWriter.setPolicyDirectives(DEFAULT_SRC_SELF_POLICY);
return "header reset!";
}
#GetMapping("/foo")
public String foo() {
myHeadersWriter.setPolicyDirectives("FOO");
return "Hello from foo!";
}
#GetMapping("/bar")
public String bar() {
myHeadersWriter.setPolicyDirectives("BAR");
return "Hello from bar!";
}
We can test:
#SpringBootTest
#AutoConfigureMockMvc
class DemoApplicationTests {
#Autowired
private MockMvc mockMvc;
#Test
public void testHome() throws Exception {
this.mockMvc.perform(get("/"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("header reset!")))
.andExpect(header().string(CONTENT_SECURITY_POLICY_HEADER, DEFAULT_SRC_SELF_POLICY));
}
#Test
public void testFoo() throws Exception {
this.mockMvc.perform(get("/foo"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello from foo!")))
.andExpect(header().string(CONTENT_SECURITY_POLICY_HEADER, "FOO"));
}
#Test
public void testBar() throws Exception {
this.mockMvc.perform(get("/bar"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello from bar!")))
.andExpect(header().string(CONTENT_SECURITY_POLICY_HEADER, "BAR"));
}
}
... also in browser:
All in one github.(sorry all in main class!:)
Refs: only this

The problem with the (my) accepted answer is:
(just for the show case, but:) We modify "singleton scope property" on (every) request!!!
When we add a "stress" test wrapper like this.
( ... wait until all threads finish their work in java ?? -> ExecutorCompletionService, since Java:1.5;)
It badly fails (header has not the "expected" value):
#Test
void testParallel() throws Exception {
// 200 cycles, with 0 (== #cpu) threads ...
final StressTester<Void> stressTestHome = new StressTester<>(Void.class, 200, 0, // ... and these (three) jobs (firing requests at our app):
() -> {
home(); // here the original tests
return null;
},
() -> {
foo(); // ... with assertions ...
return null;
},
() -> {
bar(); // ... moved to private (non Test) methods
return null;
}
);
stressTestHome.test(); // run it, collect it and:
stressTestHome.printErrors(System.out);
assertTrue(stressTestHome.getExceptionList().isEmpty());
}
As in mock as in (full) server mode... ;(;(;(
We will encounter the same problem, when we want to change that header from a "lower scope" (than singleton..so any other scope:) ;(;(;(
If we want singleton scope policy for that header, and only "trigger the reload" (for all subsequent requests), we can stop reading. (answer 1 is ok, as i actually "initially understood" the question & answered:)
But if we want that "per request header" with spring-security, we have to pass this test! :)
One possible solution: Method Injection!
So back to our custom HeaderWriter implementation:
package com.example.demo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.web.header.HeaderWriter;
// abstract!
public abstract class MyContentSecurityPolicyHeaderWriter implements HeaderWriter {
// ... no state!!!
public static final String CONTENT_SECURITY_POLICY_HEADER = "Content-Security-Policy";
public static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'";
#Override // how cool, that there is a HttpServletRequest/-Response "at hand" !?!
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
if (!response.containsHeader(CONTENT_SECURITY_POLICY_HEADER)) {
// responsible for the header key, but for the value we ask: delegate
response.setHeader(CONTENT_SECURITY_POLICY_HEADER, policyDelegate().getPolicyDirectives());
}
}
// TLDR xDxD
protected abstract MyContentSecurityDelegate policyDelegate();
}
Thanks, again!;)
With this tiny (but managed) "context holder":
package com.example.demo;
import lombok.*;
#NoArgsConstructor
#AllArgsConstructor(staticName = "of")
public class MyContentSecurityDelegate {
#Getter
#Setter
private String policyDirectives;
}
We do this (with spring-java-config, How to create bean using #Bean in spring boot for abstract class):
#Configuration
class FreakyConfig {
#Value("${my.policy.directive:DEFAULT_SRC_SELF_POLICY}")
private String policy;
#Bean
#RequestScope // !! (that is suited for our controllers)
public MyContentSecurityDelegate delegate() {
return MyContentSecurityDelegate.of(policy);
}
#Bean
public MyContentSecurityPolicyHeaderWriter myWriter() {
return new MyContentSecurityPolicyHeaderWriter() { // anonymous inner class
#Override
protected MyContentSecurityDelegate policyDelegate() {
return delegate(); // with request scoped delegate.
}
};
}
}
..then our controllers do that (autowire & "talk" to the delegate):
#Autowired // !
private MyContentSecurityDelegate myRequestScopedDelegate;
#GetMapping("/foo")
public String foo() {
// !!
myRequestScopedDelegate.setPolicyDirectives("FOO");
return "Hello from foo!";
}
Then all tests pass! :) pushed to (same)github.
But to achieve the goal: "Write headers request (even thread) specific", we can use any other technique (matching our stack & needs, beyond spring-security):
with or without spring-boot
servlet
with spring-mvc/without
javax.servlet.*:
Any Servlet, Filter, or servlet *Listener instance that is a Spring bean is registered with the embedded container..
from Registering Servlets, Filters, and Listeners as Spring Beans
or reactive ...
Mo' Links:
How can I add a filter class in Spring Boot?
https://www.baeldung.com/spring-response-header
https://www.baeldung.com/spring-boot-add-filter
Happy Coding!

Related

Retryable annotation - Junit5 - Mockito - is it possible

Is it possible to write unit test using Junit 5 mockito for retryable annotations?
I am having a service interface which has only one method, which downloads the file from remote url
#service
interface downloadpdf{
#Retryable(value = { FileNotFoundException.class, HttpClientErrorException.class }, maxAttempts = 5, backoff = #Backoff(delay = 1000))
public string downloadpdffile(string remoteurl, string pdfname);
}
I have tried referring sites and found using Spring4JunitRunner implementation to test retry. Got confused with implementation. Is it possible to write unit test using Junit 5 mockito for retryable annotations?. Could you please elaborate on the solution here?
You need to use #SpringJUnitConfig (which is the equivalent of the JUnit4 runner). Or #SpringBootTest as you are using Boot.
#Retryable only works with beans managed by Spring - it wraps the bean in a proxy.
#SpringBootApplication
#EnableRetry
public class So71849077Application {
public static void main(String[] args) {
SpringApplication.run(So71849077Application.class, args);
}
}
#Component
class RetryableClass {
private SomeService service;
void setService(SomeService service) {
this.service = service;
}
#Retryable
void retryableMethod(String in) {
service.callme();
throw new RuntimeException();
}
#Recover
void recover(Exception ex, String in) {
service.failed();
}
}
interface SomeService {
void callme();
void failed();
}
#SpringBootTest
class So71849077ApplicationTests {
#MockBean
SomeService service;
#Test
void testRetry(#Autowired RetryableClass retryable) {
SomeService service = mock(SomeService.class);
retryable.setService(service);
retryable.retryableMethod("foo");
verify(service, times(3)).callme();
verify(service).failed();
}
}
I was also trying to implement this using Junit5.
Tried various options but that didn't help. Then after googling for few hours, got the following link and it helped to succeed.
https://doctorjw.wordpress.com/2022/04/29/spring-testing-a-single-bean-in-junit-5-springextension/
Reference code below, for detailed explanation, please refer the blog.
#Component
public class MyClass {
private ObjectMapper objectMapper;
private RestTemplate restTemplate;
#Value("${testValue:5}")
private int value;
#Retryable(....)
public void doStuff() throws SomeException {
...
}
}
What I’ve discovered is, if I declare my test class this way:
#ExtendWith( SpringExtension.class )
#Import( { MyClass.class, ObjectMapper.class } )
#EnableRetry
public class MyClassTest {
#Autowired
private MyClass myClass;
#MockBean
private RestTemplate restTemplate;
#Autowired
private ObjectMapper objectMapper;
#BeforeEach
public void setup() {
// If we are going to jack with the object configuration,
// we need to do so on the actual object, not the Spring proxy.
// So, use AopTestUtils to get around the proxy to the actual obj.
TestingUtils.setFieldValue( AopTestUtils.getTargetObject( myClass ), "value", 10 );
}
}
You will notice the inclusion of 1 other class, TestingUtils.class. This class looks like:
public class TestingUtils {
public static void setFieldValue( Object object, String fieldName, Object value ) {
Field field = ReflectionUtils.findField( object.getClass(), fieldName );
ReflectionUtils.makeAccessible( field );
ReflectionUtils.setField( field, object, value );
}
}
All credits goes to the author of the blog.

Getting 406 Could not find acceptable representation /Spring JSON Test. How to ignore .htm extension in tests?

Controller needs uses .htm extensions for all handlers, including JSON REST endpoints. How should I test for REST endpoints?
Problem:
I cannot disable suffix interpretation and I am getting 406 "Could not find acceptable representation"
Tried attempts:
I reviewed posts on stackoverflow related to 406, but could not find relevant one to the case where 'htm' suffix is used in tests. When you remove '.htm' suffix from both Controller and Test - the test is passing.
Here is controller with /changePassword.htm endpoint:
#Controller
public class MainController {
public static class ResultBean {
private final String result;
public String getResult() {
return result;
}
public ResultBean(String result) {
this.result = result;
}
}
#RequestMapping(value="/changePassword.htm", method= RequestMethod.POST, produces = { "application/json" })
public #ResponseBody ResultBean changePassword (
#RequestParam("username") String username, #RequestParam("password") String password) {
return new ResultBean("OK");
}
}
And here is the test with configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { HomeControllerTest.Config.class })
public class HomeControllerTest {
#InjectMocks
private MainController controller = new MainController();
private MockMvc mvc;
#Configuration
#EnableWebMvc
public static class Config extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.favorParameter(true)
.parameterName("mediaType")
.ignoreUnknownPathExtensions(true)
.ignoreAcceptHeader(false)
.useJaf(false)
.defaultContentType(MediaType.APPLICATION_JSON);
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(controller)
.build();
}
#Test
public void shouldPassChangePasswordBean() throws Exception {
mvc.perform(post("/changePassword.htm")
.accept("*/*")
.param("username", "example")
.param("password", "abcdef")
)
.andExpect(status().isOk()); // Test produces 406 instead of 200
}
}
Any idea?
On newer version of Spring (4+ I think), mime type is determined from suffix first.
So If you use a .htm suffix, Spring will default to produce HTML even if you don't want to.
One way to bypass this is to use a filter that rewrite URL. For instance tuckey URL rewriter filter
With this, you can set some rules like:
/my/page/that/return/json.htm is rewriten to /my/page/that/return/json so that Spring can produce data according to the Accept header.
with Spring 5, try changing your URL of your web service to .json! that is the right fix. great details here http://stick2code.blogspot.com/2014/03/solved-orgspringframeworkwebhttpmediaty.html

Camel processor unit/integration testing

I've probably completly missed something but I can't manage to test my route as I want to.
I've got the following bean :
#Component("fileProcessor")
public class FileProcessor {
public boolean valid(#Header("customObject) CustomObject customObject,Exchange exchange) throws IOException{
return false;
}
I've a route calling my bean like so :
from("direct:validationFile").routeId("validationFile").validate().method("fileProcessor","valid")
// Other stuff
.end();
Here is my unit test, based on a example I found:
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class})
#ContextConfiguration(locations = { "classpath:/tu-dao-beans.xml" })
public class FileProcessorTest extends CamelTestSupport {
#EndpointInject(uri = "mock:result")
protected MockEndpoint resultEndpoint;
#Produce(uri = "direct:start")
protected ProducerTemplate template;
#Override
public boolean isDumpRouteCoverage() {
return true;
}
#Test
public void testSendMatchingMessage() throws Exception {
String expectedBody = "<matched/>";
resultEndpoint.expectedBodiesReceived(expectedBody);
template.sendBodyAndHeader(expectedBody, "foo", "bar");
resultEndpoint.assertIsSatisfied();
}
#Test
public void testSendNotMatchingMessage() throws Exception {
resultEndpoint.expectedMessageCount(0);
template.sendBodyAndHeader("<notMatched/>", "foo", "notMatchedHeaderValue");
resultEndpoint.assertIsSatisfied();
}
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
public void configure() {
// from("direct:start").filter(header("foo").isEqualTo("bar")).to("mock:result");
from("direct:start").routeId("validationFile").validate().method("fileProcessor","valid").to("mock:result");
}
};
}
}
The test fails because fileProcessor is not found, yet I'm pretty sure my spring contextis properly loaded, I'm using the same beans.xmlfile for my dbunit tests and my DAO components are found just fine... What am I missing ?
EDIT:
Thanks to Jérémis B's answer I fixed my problem easily. In case someone stumble as I did here is the code I added:
#Autowired
private FileProcessor fileProcessor;
#Override
protected JndiRegistry createRegistry() throws Exception {
JndiRegistry registry = super.createRegistry();
registry.bind("fileProcessor", fileProcessor);
return registry;
}
You can see the official documentation for an "How to" test with Spring.
In your example, you create a Spring Context, but use the CamelTestSupport : This class create a CamelContext which is not aware of the Spring Context. The bean "fileProcessor" is not seen by this context.
There is a lot of ways to do this kind of test. The easiest, with the code you already have, is maybe to:
Inject the fileProcessor in your test class, with #Autowire
Override createRegistry and add the fileProcessor to the registry
You can too override CamelSpringTestSupport and implement createApplicationContext. Another way is to keep the route definition in a Spring Bean (through xml, or a RouteBuilder), and inject in your test MockEndpoints or ProducerTemplate.

Controller testing - The CacheManager has been shut down. It can no longer be used

I'm experiencing the error as per title while trying do Controller testing in Play framework 2.4.6 using Guice. It occurs with any view.render code in the Controller. redirect does not produce this issue.
Stack:
1) Error in custom provider, java.lang.IllegalStateException: The CacheManager has been shut down. It can no longer be used.
at play.api.cache.EhCacheModule.play$api$cache$EhCacheModule$$bindCache$1(Cache.scala:178):
Binding(interface net.sf.ehcache.Ehcache qualified with QualifierInstance(#play.cache.NamedCache(value=play)) to ProviderTarget(play.api.cache.NamedEhCacheProvider#4c8f1e9)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)
while locating net.sf.ehcache.Ehcache annotated with #play.cache.NamedCache(value=play)
at play.api.cache.EhCacheModule.play$api$cache$EhCacheModule$$bindCache$1(Cache.scala:179):
Binding(interface play.api.cache.CacheApi qualified with QualifierInstance(#play.cache.NamedCache(value=play)) to ProviderTarget(play.api.cache.NamedCacheApiProvider#38fd7da7)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)
while locating play.api.cache.CacheApi annotated with #play.cache.NamedCache(value=play)
while locating play.api.cache.CacheApi
for parameter 0 at play.cache.DefaultCacheApi.<init>(DefaultCacheApi.java:20)
at play.cache.DefaultCacheApi.class(DefaultCacheApi.java:20)
while locating play.cache.DefaultCacheApi
while locating play.cache.CacheApi
Below is my setup:
Controller:
#Singleton
public class AccountController extends Controller {
private AccountService accountService;
#Inject
public Controller(AccountService a) {
accountService = a;
}
public Result addAccount() {
boolean success = accountService.addAccount();
if (success)
return ok(CreateAccount.render());//<--THIS TRIGGERS THE ERROR WHEN RUNNING THE TEST
}
}
Interface:
#ImplementedBy(AccountServiceImpl.class)
public interface AccountService {
boolean addAccount();
}
Implementation:
public class AccountServiceImpl implements AccountService {
#Override
public boolean addAccount() {
}
}
Test:
public class TestClass {
#Inject
Application application;
final AccountService accountServiceMock = mock(AccountService.class);
#Before
public void setup() {
Module testModule = new AbstractModule() {
#Override
public void configure() {
bind(AccountService.class).toInstance(accountServiceMock);
}
};
GuiceApplicationBuilder builder = new GuiceApplicationLoader()
.builder(new ApplicationLoader.Context(Environment.simple()))
.overrides(testModule);
Guice.createInjector(builder.applicationModule()).injectMembers(this);
Helpers.start(application);
}
#Test
public void testMethod() throws Exception {
RequestBuilder request = new RequestBuilder()
.session("userId", "1")
.uri(controllers.routes.AccountController.addAccount().url());
running(application, () -> {
when(accountServiceMock.addAccount().thenReturn(true);
assertEquals(OK, route(request).status());
});
}
Any help appreciated!
EDIT: I have pinpointed the exact thing causing the issue, though I still don't know why.
RequestBuilder request = new RequestBuilder()
.session("userId", "1") // <--- THIS IS CAUSING THE PROBLEM
.uri(controllers.routes.AccountController.addAccount().url());
To be even more exact:
"userId" // <--- THIS. userIds, or any other I've tried, solved the issue.
To reinstate again, as long as userId session is set for RequestBuilder in the test method along with view.render in the Controller, this error happens.
It is regardless of whether I use such session in my html or even in the controller. Anyone knows what is happening here?
Move everything inside the test method to inside the running call:
running(application, () -> {
RequestBuilder request = new RequestBuilder()
.session("userId", "1")
.uri(controllers.routes.AccountController.addAccount().url());
when(accountServiceMock.addAccount().thenReturn(true);
assertEquals(OK, route(request).status());
});
The running method stops the application and then you are getting an error in your assert when routing a request.

Using Mockito 'when' in controller testing

I'm trying to test my controller method in Play framework 2.4.6.
Inside my controller method, I have the following code:
User user = accountService.getUserByEmail(email);
if (user == null) {
//Path A
}
//Path B
When running the test, user will be null. Hence I can't test Path B. I tried returning a User using Mockito when, but it didn't work either. Is there any other way of doing it?
Below is my test code:
RequestBuilder request = new RequestBuilder()
.method("POST")
.bodyForm(ImmutableMap.of("email", "test#test.com"))
.uri(controllers.routes.ResetPasswordController.postResetPassword().url());
when(accountService.getUserByEmail(anyString())).thenReturn(new User());
assertEquals(OK, route(request).status());
Thanks to #Andriy for pointing me in the right direction for Dependency Injection.
I managed to solved the issue with the following setup.
Test:
public class TestClass {
#Inject
Application application;
final AccountService accountServiceMock = mock(AccountService.class);
#Before
public void setup() {
Module testModule = new AbstractModule() {
#Override
public void configure() {
bind(AccountService.class).toInstance(accountServiceMock);
}
};
GuiceApplicationBuilder builder = new GuiceApplicationLoader()
.builder(new ApplicationLoader.Context(Environment.simple()))
.overrides(testModule);
Guice.createInjector(builder.applicationModule()).injectMembers(this);
Helpers.start(application);
}
#Test
public void testMethod() throws Exception {
RequestBuilder request = new RequestBuilder()
.session("userId", "1")
.uri(controllers.routes.AccountController.addAccount().url());
running(application, () -> {
when(accountServiceMock.addAccount().thenReturn(true);
assertEquals(OK, route(request).status());
});
}
Controller:
#Singleton
public class AccountController extends Controller {
private AccountService accountService;
#Inject
public Controller(AccountService a) {
accountService = a;
}
public Result addAccount() {
boolean success = accountService.addAccount();
}
}
Interface:
#ImplementedBy(AccountServiceImpl.class)
public interface AccountService {
boolean addAccount();
}
Implementation:
public class AccountServiceImpl implements AccountService {
#Override
public boolean addAccount() {
}
}
My knowledge is minimal on the concepts going on here, but roughly:
Controller is stateless just like HTML, hence you need runtime dependency injection to get Play to recognise the mock object.
Useful documentations:
https://www.playframework.com/documentation/2.4.x/JavaTestingWithGuice
https://www.playframework.com/documentation/2.4.x/JavaDependencyInjection

Categories

Resources