Skip to content

Commit

Permalink
add security headers
Browse files Browse the repository at this point in the history
  • Loading branch information
atomfrede committed Jul 21, 2020
1 parent 736f1b4 commit 60a0194
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 0 deletions.
4 changes: 4 additions & 0 deletions generators/heroku/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ module.exports = class extends HerokuGeneratorOverride {
'SSLEnforcingHostResolver.java.ejs',
`${constants.SERVER_MAIN_SRC_DIR}${this.packageFolder}/config/SSLEnforcingHostResolver.java`
);
this.template(
'StrictTransportSecurityHeaderFilter.java.ejs',
`${constants.SERVER_MAIN_SRC_DIR}${this.packageFolder}/security/StrictTransportSecurityHeaderFilter.java`
);
if (this.buildTool === 'gradle') {
this.template('heroku.gradle.ejs', 'gradle/heroku.gradle');
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package <%=packageName%>.security;

import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.core.async.publisher.Publishers;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.filter.OncePerRequestHttpServerFilter;
import io.micronaut.http.filter.ServerFilterChain;
import org.reactivestreams.Publisher;

import static io.micronaut.http.annotation.Filter.MATCH_ALL_PATTERN;

@Filter(patterns = {MATCH_ALL_PATTERN})
@Requires(env = Environment.HEROKU)
public class StrictTransportSecurityHeaderFilter extends OncePerRequestHttpServerFilter {

private static final String STRICT_TRANSPORT_SECURITY_HEADER = "Strict-Transport-Security";

@Override
protected Publisher<MutableHttpResponse<?>> doFilterOnce(HttpRequest<?> request, ServerFilterChain chain) {

return Publishers.map(chain.proceed(request), mutableHttpResponse -> {
mutableHttpResponse.header(STRICT_TRANSPORT_SECURITY_HEADER, "max-age=31536000; includeSubDomains");
return mutableHttpResponse;
});
}
}
10 changes: 10 additions & 0 deletions generators/server/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,11 @@ const serverFiles = {
renameTo: generator => `${generator.javaDir}security/UserNotActivatedException.java`,
useBluePrint: true,
},
{
file: 'package/security/SecurityHeaderFilter.java',
renameTo: generator => `${generator.javaDir}security/SecurityHeaderFilter.java`,
useBluePrint: true,
},
],
},
{
Expand Down Expand Up @@ -751,6 +756,11 @@ const serverFiles = {
renameTo: generator => `${generator.javaDir}security/jwt/JWTFilterTest.java`,
useBluePrint: true,
},
{
file: 'package/security/SecurityHeaderFilterTest.java',
renameTo: generator => `${generator.javaDir}security/SecurityHeaderFilterTest.java`,
useBluePrint: true,
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package <%=packageName%>.security;

import io.micronaut.core.async.publisher.Publishers;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.filter.OncePerRequestHttpServerFilter;
import io.micronaut.http.filter.ServerFilterChain;
import org.reactivestreams.Publisher;

import static io.micronaut.http.annotation.Filter.MATCH_ALL_PATTERN;

@Filter(patterns = {MATCH_ALL_PATTERN})
public class SecurityHeaderFilter extends OncePerRequestHttpServerFilter {

private static final String X_FRAME_OPTIONS_HEADER = "X-Frame-Options";
private static final String X_CONTENT_TYPE_OPTIONS_HEADER = "X-Content-Type-Options";
private static final String X_XSS_PROTECTION_HEADER = "X-XSS-Protection";
private static final String REFERRER_POLICY_HEADER = "Referrer-Policy";
private static final String FEATURE_POLICY_HEADER = "Feature-Policy";
private static final String CONTENT_SECURITY_POLICY_HEADER = "Content-Security-Policy";

@Override
protected Publisher<MutableHttpResponse<?>> doFilterOnce(HttpRequest<?> request, ServerFilterChain chain) {

return Publishers.map(chain.proceed(request), mutableHttpResponse -> {
addSecurityHeaders(mutableHttpResponse);
return mutableHttpResponse;
});
}

protected void addSecurityHeaders(MutableHttpResponse<?> response) {
response.header(X_FRAME_OPTIONS_HEADER, "DENY");
response.header(X_CONTENT_TYPE_OPTIONS_HEADER, "nosniff");
response.header(X_XSS_PROTECTION_HEADER, "1; mode=block");
response.header(REFERRER_POLICY_HEADER, "strict-origin-when-cross-origin");
response.header(FEATURE_POLICY_HEADER, "geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none'");
<%_ if (clientTheme !== 'none') { _%>
response.header(CONTENT_SECURITY_POLICY_HEADER, "default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' https://fonts.googleapis.com 'unsafe-inline'; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com data:");
<%_ } else { _%>
response.header(CONTENT_SECURITY_POLICY_HEADER, "default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:");
<%_ } _%>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package <%=packageName%>.security;

import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.simple.SimpleHttpResponseFactory;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;

class SecurityHeaderFilterTest {

private final SecurityHeaderFilter subject = new SecurityHeaderFilter();

@Test
public void assertSecurityHeadersAreAdded() {

MutableHttpResponse<Object> response = new SimpleHttpResponseFactory().ok();
subject.addSecurityHeaders(response);

assertThat(response.header("X-Frame-Options")).isEqualTo("DENY");
assertThat(response.header("X-Content-Type-Options")).isEqualTo("nosniff");
assertThat(response.header("X-XSS-Protection")).isEqualTo("1; mode=block");
assertThat(response.header("Referrer-Policy")).isEqualTo("strict-origin-when-cross-origin");
assertThat(response.header("Feature-Policy")).isEqualTo("geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none'");
<%_ if (clientTheme !== 'none') { _%>
assertThat(response.header("Content-Security-Policy")).isEqualTo("default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' https://fonts.googleapis.com 'unsafe-inline'; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com data:");
<%_ } else { _%>
assertThat(response.header("Content-Security-Policy")).isEqualTo("default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:");
<%_ } _%>
}
}

0 comments on commit 60a0194

Please sign in to comment.