Skip to content

Commit

Permalink
Merge pull request #3604 from inception-project/feature/3602-Enable-S…
Browse files Browse the repository at this point in the history
…pring-MVC-CSP

#3602 - Enable Spring MVC CSP
  • Loading branch information
reckart authored Dec 4, 2022
2 parents 32bb564 + 0272401 commit 74a695b
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 57 deletions.
4 changes: 4 additions & 0 deletions inception/inception-app-webapp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,10 @@
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
package de.tudarmstadt.ukp.inception.app.config;

import static de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase.NS_PROJECT;
import static de.tudarmstadt.ukp.inception.app.config.InceptionSecurityWebUIShared.accessToApplication;
import static de.tudarmstadt.ukp.inception.app.config.InceptionSecurityWebUIShared.accessToRemoteApiAndSwagger;
import static de.tudarmstadt.ukp.inception.app.config.InceptionSecurityWebUIShared.accessToStaticResources;

import java.util.Optional;

Expand Down Expand Up @@ -49,27 +52,13 @@ public SecurityFilterChain webUiFilterChain(HttpSecurity aHttp,
aHttp.csrf().disable();
aHttp.headers().frameOptions().sameOrigin();

aHttp.authorizeRequests() //
.antMatchers("/login.html*").permitAll() //
// Resources need to be publicly accessible so they don't trigger the login
// page. Otherwise it could happen that the user is redirected to a resource
// upon login instead of being forwarded to a proper application page.
.antMatchers("/favicon.ico").permitAll() //
.antMatchers("/favicon.png").permitAll() //
.antMatchers("/assets/**").permitAll() //
.antMatchers("/images/**").permitAll() //
.antMatchers("/resources/**").permitAll() //
.antMatchers("/whoops").permitAll() //
.antMatchers("/about/**").permitAll() //
.antMatchers("/wicket/resource/**").permitAll() //
.antMatchers("/" + NS_PROJECT + "/*/join-project/**").permitAll() //
.antMatchers("/swagger-ui/**").access("hasAnyRole('ROLE_REMOTE')") //
.antMatchers("/swagger-ui.html").access("hasAnyRole('ROLE_REMOTE')") //
.antMatchers("/v3/**").access("hasAnyRole('ROLE_REMOTE')") //
.antMatchers("/admin/**").access("hasAnyRole('ROLE_ADMIN')") //
.antMatchers("/doc/**").access("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')") //
.antMatchers("/**").access("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')") //
.anyRequest().denyAll();
var authorizations = aHttp.authorizeRequests();
authorizations.antMatchers("/login.html*").permitAll();
accessToStaticResources(authorizations);
accessToRemoteApiAndSwagger(authorizations);
authorizations.antMatchers("/" + NS_PROJECT + "/*/join-project/**").permitAll();
accessToApplication(authorizations);
authorizations.anyRequest().denyAll();

// Must use "defaultAuthenticationEntryPointFor" instead of "formLogin" because
// if we use formLogin, Spring will handle the form submit and we want the Wicket
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
*/
package de.tudarmstadt.ukp.inception.app.config;

import static de.tudarmstadt.ukp.inception.app.config.InceptionSecurityWebUIShared.accessToApplication;
import static de.tudarmstadt.ukp.inception.app.config.InceptionSecurityWebUIShared.accessToRemoteApiAndSwagger;
import static de.tudarmstadt.ukp.inception.app.config.InceptionSecurityWebUIShared.accessToStaticResources;
import static de.tudarmstadt.ukp.inception.support.deployment.DeploymentModeService.PROFILE_AUTH_MODE_EXTERNAL_PREAUTH;

import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -51,28 +54,15 @@ public SecurityFilterChain externalPreAuthenticationFilterChain(HttpSecurity aHt
.rememberMe()
.and()
.csrf().disable()
.addFilterBefore(aFilter, RequestHeaderAuthenticationFilter.class)
.authorizeRequests()
// Resources need to be publicly accessible so they don't trigger the login
// page. Otherwise it could happen that the user is redirected to a resource
// upon login instead of being forwarded to a proper application page.
.antMatchers("/favicon.ico").permitAll()
.antMatchers("/favicon.png").permitAll()
.antMatchers("/assets/**").permitAll()
.antMatchers("/images/**").permitAll()
.antMatchers("/resources/**").permitAll()
.antMatchers("/whoops").permitAll()
.antMatchers("/about/**").permitAll()
.antMatchers("/wicket/resource/**").permitAll()
.antMatchers("/swagger-ui/**").access("hasAnyRole('ROLE_REMOTE')")
.antMatchers("/swagger-ui.html").access("hasAnyRole('ROLE_REMOTE')")
.antMatchers("/v3/**").access("hasAnyRole('ROLE_REMOTE')")
.antMatchers("/admin/**").access("hasAnyRole('ROLE_ADMIN')")
.antMatchers("/doc/**").access("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')")
.antMatchers("/**").access("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')")
.anyRequest().denyAll()
.and()
.exceptionHandling()
.addFilterBefore(aFilter, RequestHeaderAuthenticationFilter.class);

var authorizations = aHttp.authorizeRequests();
accessToStaticResources(authorizations);
accessToRemoteApiAndSwagger(authorizations);
accessToApplication(authorizations);
authorizations.anyRequest().denyAll();

aHttp.exceptionHandling()
.authenticationEntryPoint(new Http403ForbiddenEntryPoint())
.and()
.headers().frameOptions().sameOrigin()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Licensed to the Technische Universität Darmstadt under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The Technische Universität Darmstadt
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.
*
* 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 de.tudarmstadt.ukp.inception.app.config;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;

public class InceptionSecurityWebUIShared
{
public static void accessToStaticResources(
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry aCfg)
{
// Resources need to be publicly accessible so they don't trigger the login
// page. Otherwise it could happen that the user is redirected to a resource
// upon login instead of being forwarded to a proper application page.
aCfg //
.antMatchers("/favicon.ico").permitAll() //
.antMatchers("/favicon.png").permitAll() //
.antMatchers("/assets/**").permitAll() //
.antMatchers("/images/**").permitAll() //
.antMatchers("/resources/**").permitAll() //
.antMatchers("/whoops").permitAll() //
.antMatchers("/about/**").permitAll() //
.antMatchers("/wicket/resource/**").permitAll();
}

public static void accessToRemoteApiAndSwagger(
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry aCfg)
{
aCfg //
.antMatchers("/swagger-ui/**").access("hasAnyRole('ROLE_REMOTE')")
.antMatchers("/swagger-ui.html").access("hasAnyRole('ROLE_REMOTE')")
.antMatchers("/v3/**").access("hasAnyRole('ROLE_REMOTE')");
}

public static void accessToApplication(
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry aCfg)
{
aCfg //
.antMatchers("/admin/**").access("hasAnyRole('ROLE_ADMIN')") //
.antMatchers("/doc/**").access("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')") //
.antMatchers("/**").access("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')");

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,19 @@
import java.io.IOException;
import java.util.EnumSet;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.web.filter.OncePerRequestFilter;

import de.tudarmstadt.ukp.clarin.webanno.api.config.RepositoryProperties;
import de.tudarmstadt.ukp.clarin.webanno.support.logging.LoggingFilter;
Expand Down Expand Up @@ -82,19 +81,19 @@ private void configureLogging(ServletContext aServletContext)

private void configureCoep(ServletContext aServletContext)
{
FilterRegistration coepFilter = aServletContext.addFilter("coep", new Filter()
FilterRegistration coepFilter = aServletContext.addFilter("coep", new OncePerRequestFilter()
{
@Override
public void doFilter(ServletRequest aServletRequest, ServletResponse aServletResponse,
FilterChain aFilterChain)
throws IOException, ServletException
protected void doFilterInternal(HttpServletRequest aRequest,
HttpServletResponse aResponse, FilterChain aFilterChain)
throws ServletException, IOException
{
// We need this in particular for non-Wicket resources served by Spring MVC
HttpServletResponse response = (HttpServletResponse) aServletResponse;
HttpServletResponse response = (HttpServletResponse) aResponse;
response.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
response.setHeader("Cross-Origin-Resource-Policy", "same-site");
response.setHeader("Cross-Origin-Opener-Policy", "same-origin");
aFilterChain.doFilter(aServletRequest, aServletResponse);
aFilterChain.doFilter(aRequest, aResponse);
}
});
coepFilter.addMappingForUrlPatterns(EnumSet.of(REQUEST, FORWARD, ASYNC), false, "/*");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,9 @@ public XHtmlXmlDocumentViewControllerImpl(DocumentService aDocumentService,
@Override
public String getDocumentUrl(SourceDocument aDoc)
{
return servletContext.getContextPath() + BASE_URL
+ GET_DOCUMENT_PATH
.replace("{projectId}", String.valueOf(aDoc.getProject().getId()))
.replace("{documentId}", String.valueOf(aDoc.getId()));
return servletContext.getContextPath() + BASE_URL + GET_DOCUMENT_PATH //
.replace("{projectId}", String.valueOf(aDoc.getProject().getId()))
.replace("{documentId}", String.valueOf(aDoc.getId()));
}

private void renderXmlStylesheet(ContentHandler ch, String aStylesheetUrl) throws SAXException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package de.tudarmstadt.ukp.inception.security.config;

import static java.lang.String.join;
import static org.springframework.security.config.http.SessionCreationPolicy.NEVER;

import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
Expand Down Expand Up @@ -49,7 +50,21 @@ public SecurityFilterChain uiViewFilterChain(HttpSecurity aHttp) throws Exceptio
{
aHttp.antMatcher(BASE_VIEW_URL + "/**");
// Views render data that we generally want to display in an IFrame on the editor page
aHttp.headers().frameOptions().sameOrigin();
aHttp.headers() //
.frameOptions().sameOrigin()//
.contentSecurityPolicy(csp -> {
csp.policyDirectives(join(";", //
"default-src 'none'", //
"script-src 'strict-dynamic' 'unsafe-eval'", //
"style-src 'self' 'unsafe-inline'", //
"img-src 'self' data:", //
"connect-src 'self'", //
"font-src 'self'", //
"manifest-src 'self'", //
"child-src 'self'", //
"base-uri 'self'", //
"frame-src 'self' 'self'"));
});
commonConfiguration(aHttp);
return aHttp.build();
}
Expand Down

0 comments on commit 74a695b

Please sign in to comment.