mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-03-13 15:39:18 +08:00
work on csp header
This commit is contained in:
parent
67d0bbedb5
commit
6f0d4b29e6
@ -1,5 +1,6 @@
|
||||
[#ftl]
|
||||
[#-- @implicitly included --]
|
||||
[#-- @ftlvariable name="nonce" type="java.lang.String" --]
|
||||
[#-- @ftlvariable name="mapper" type="com.fasterxml.jackson.databind.ObjectMapper" --]
|
||||
[#-- @ftlvariable name="_csrf" type="org.springframework.security.web.csrf.CsrfToken" --]
|
||||
[#-- @ftlvariable name="cu" type="io.papermc.hangar.db.model.UsersTable" --]
|
||||
|
@ -9,7 +9,6 @@ import io.papermc.hangar.security.voters.ProjectPermissionVoter;
|
||||
import io.papermc.hangar.security.voters.UserLockVoter;
|
||||
import io.papermc.hangar.service.PermissionService;
|
||||
import io.papermc.hangar.service.UserService;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -32,9 +31,6 @@ import java.util.List;
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private static final String CSP = "script-src 'self'{nonce}";
|
||||
public String CSP_NONCE;
|
||||
|
||||
private final HangarAuthenticationProvider authProvider;
|
||||
private final PermissionService permissionService;
|
||||
private final UserService userService;
|
||||
@ -48,20 +44,18 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
CSP_NONCE = RandomStringUtils.randomAlphanumeric(64);
|
||||
|
||||
// TODO CSP nonce
|
||||
// http.headers().contentSecurityPolicy(CSP.replace("{nonce}", " 'nonce-" + CSP_NONCE + "'"));
|
||||
|
||||
http.csrf().ignoringAntMatchers(
|
||||
"/api/v2/authenticate", "/api/v2/sessions/current", "/api/v2/keys", "/api/sync_sso"
|
||||
);
|
||||
|
||||
http.addFilter(new HangarAuthenticationFilter());
|
||||
|
||||
http.exceptionHandling().authenticationEntryPoint(new HangarAuthenticationEntryPoint());
|
||||
|
||||
http.authorizeRequests().anyRequest().permitAll().accessDecisionManager(accessDecisionManager()); // we use method security
|
||||
http
|
||||
.csrf()
|
||||
.ignoringAntMatchers(
|
||||
"/api/v2/authenticate",
|
||||
"/api/v2/sessions/current",
|
||||
"/api/v2/keys",
|
||||
"/api/sync_sso"
|
||||
)
|
||||
.and()
|
||||
.addFilter(new HangarAuthenticationFilter())
|
||||
.exceptionHandling().authenticationEntryPoint(new HangarAuthenticationEntryPoint())
|
||||
.and().authorizeRequests().anyRequest().permitAll().accessDecisionManager(accessDecisionManager());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -75,6 +69,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
return super.authenticationManager();
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public AccessDecisionManager accessDecisionManager() {
|
||||
List<AccessDecisionVoter<? extends Object>> decisionVoters = Arrays.asList(
|
||||
|
@ -14,13 +14,11 @@ import io.papermc.hangar.db.model.ProjectVersionsTable;
|
||||
import io.papermc.hangar.db.model.ProjectsTable;
|
||||
import io.papermc.hangar.db.model.UserProjectRolesTable;
|
||||
import io.papermc.hangar.db.model.UsersTable;
|
||||
import io.papermc.hangar.model.Category;
|
||||
import io.papermc.hangar.model.Platform;
|
||||
import io.papermc.hangar.model.Role;
|
||||
import io.papermc.hangar.model.SsoSyncData;
|
||||
import io.papermc.hangar.model.TagColor;
|
||||
import io.papermc.hangar.model.Visibility;
|
||||
import io.papermc.hangar.model.generated.ProjectSortingStrategy;
|
||||
import io.papermc.hangar.model.viewhelpers.ProjectPage;
|
||||
import io.papermc.hangar.model.viewhelpers.UserData;
|
||||
import io.papermc.hangar.security.annotations.UserLock;
|
||||
@ -104,21 +102,21 @@ public class Apiv1Controller extends HangarController {
|
||||
this.projectsTable = projectsTable;
|
||||
}
|
||||
|
||||
@GetMapping("/v1/projects")
|
||||
public ResponseEntity<ArrayNode> listProjects(@RequestParam(defaultValue = "") List<Category> categories, @RequestParam(defaultValue = "4") int sort, @RequestParam(required = false) String q, @RequestParam(required = false) Long limit, @RequestParam(required = false) Long offset) {
|
||||
int maxLoad = hangarConfig.projects.getInitLoad();
|
||||
long realLimit = ApiUtil.limitOrDefault(limit, maxLoad);
|
||||
long realOffset = ApiUtil.offsetOrZero(offset);
|
||||
ProjectSortingStrategy strategy;
|
||||
try {
|
||||
strategy = ProjectSortingStrategy.VALUES[sort];
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
List<ProjectsTable> sortedProjects = v1ApiService.getProjects(q, categories.stream().map(Category::getValue).collect(Collectors.toList()), strategy, realLimit, realOffset);
|
||||
return ResponseEntity.ok(writeProjects(sortedProjects));
|
||||
}
|
||||
// @GetMapping("/v1/projects")
|
||||
// public ResponseEntity<ArrayNode> listProjects(@RequestParam(defaultValue = "") List<Category> categories, @RequestParam(defaultValue = "4") int sort, @RequestParam(required = false) String q, @RequestParam(required = false) Long limit, @RequestParam(required = false) Long offset) {
|
||||
// int maxLoad = hangarConfig.projects.getInitLoad();
|
||||
// long realLimit = ApiUtil.limitOrDefault(limit, maxLoad);
|
||||
// long realOffset = ApiUtil.offsetOrZero(offset);
|
||||
// ProjectSortingStrategy strategy;
|
||||
// try {
|
||||
// strategy = ProjectSortingStrategy.VALUES[sort];
|
||||
// } catch (ArrayIndexOutOfBoundsException e) {
|
||||
// throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
||||
// }
|
||||
//
|
||||
// List<ProjectsTable> sortedProjects = v1ApiService.getProjects(q, categories.stream().map(Category::getValue).collect(Collectors.toList()), strategy, realLimit, realOffset);
|
||||
// return ResponseEntity.ok(writeProjects(sortedProjects));
|
||||
// }
|
||||
|
||||
@GetMapping("/v1/projects/{author}/{slug}")
|
||||
public ResponseEntity<ObjectNode> showProject(@PathVariable String author, @PathVariable String slug) {
|
||||
@ -126,11 +124,11 @@ public class Apiv1Controller extends HangarController {
|
||||
return ResponseEntity.ok((ObjectNode) writeProjects(List.of(project)).get(0));
|
||||
}
|
||||
|
||||
@GetMapping("v1/projects/{id}")
|
||||
public ResponseEntity<ObjectNode> showProject(@PathVariable long id) {
|
||||
ProjectsTable project = projectService.getProjectsTable(id);
|
||||
return ResponseEntity.ok((ObjectNode) writeProjects(List.of(project)).get(0));
|
||||
}
|
||||
// @GetMapping("/v1/projects/{id}")
|
||||
// public ResponseEntity<ObjectNode> showProject(@PathVariable long id) {
|
||||
// ProjectsTable project = projectService.getProjectsTable(id);
|
||||
// return ResponseEntity.ok((ObjectNode) writeProjects(List.of(project)).get(0));
|
||||
// }
|
||||
|
||||
@PreAuthorize("@authenticationService.authV1ApiRequest(T(io.papermc.hangar.model.Permission).EditApiKeys, T(io.papermc.hangar.controller.util.ApiScope).forProject(#author, #slug))")
|
||||
@UserLock
|
||||
|
@ -15,10 +15,13 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public abstract class HangarController {
|
||||
|
||||
@ -32,11 +35,15 @@ public abstract class HangarController {
|
||||
private TemplateHelper templateHelper;
|
||||
@Autowired
|
||||
private ObjectMapper mapper;
|
||||
@Autowired
|
||||
private HttpServletResponse response;
|
||||
|
||||
|
||||
@Autowired
|
||||
protected Supplier<Optional<UsersTable>> currentUser;
|
||||
|
||||
private static final Pattern NONCE_PATTERN = Pattern.compile("(?<=nonce-)[a-zA-Z0-9]+");
|
||||
|
||||
protected ModelAndView fillModel(ModelAndView mav) {
|
||||
// helpers
|
||||
BeansWrapperBuilder builder = new BeansWrapperBuilder(Configuration.VERSION_2_3_30);
|
||||
@ -62,6 +69,16 @@ public abstract class HangarController {
|
||||
}
|
||||
mav.addObject("cu", currentUser.get().orElse(null));
|
||||
mav.addObject("headerData", userService.getHeaderData());
|
||||
if (response.containsHeader("Content-Security-Policy")) {
|
||||
Matcher nonceMatcher = NONCE_PATTERN.matcher(response.getHeader("Content-Security-Policy"));
|
||||
if (!nonceMatcher.find()) {
|
||||
throw new IllegalStateException("Must have script nonce defined");
|
||||
}
|
||||
String nonce = nonceMatcher.group();
|
||||
mav.addObject("nonce", nonce);
|
||||
} else {
|
||||
mav.addObject("nonce", "missing-csp-header");
|
||||
}
|
||||
return mav;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,43 @@
|
||||
package io.papermc.hangar.controller.filters;
|
||||
|
||||
import io.papermc.hangar.config.hangar.HangarConfig;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class ContentSecurityPolicyFilter extends OncePerRequestFilter {
|
||||
|
||||
public final HangarConfig hangarConfig;
|
||||
|
||||
private static final String CSP = "default-src 'self' {additional-uris} fonts.googleapis.com; style-src fonts.googleapis.com 'self' {additional-uris} 'unsafe-inline'; font-src fonts.gstatic.com; script-src {additional-uris} 'self' 'nonce-{nonce}' 'unsafe-eval'; img-src 'self' papermc.io paper.readthedocs.io {additional-uris} {auth-uri}; manifest-src {manifest-uri}; prefetch-src {prefetch-uri}; media {prefetch-uri}; object-src 'none'; block-all-mixed-content; frame-ancestors 'none'; base-uri 'none'";
|
||||
|
||||
|
||||
@Autowired
|
||||
public ContentSecurityPolicyFilter(HangarConfig hangarConfig) {
|
||||
this.hangarConfig = hangarConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
|
||||
String CSP_NONCE = RandomStringUtils.randomAlphanumeric(64);
|
||||
// response.addHeader(
|
||||
// "Content-Security-Policy",
|
||||
// CSP
|
||||
// .replace("{additional-uris}", hangarConfig.isUseWebpack() ? "http://localhost:8081" : "")
|
||||
// .replace("{auth-uri}", hangarConfig.getAuthUrl())
|
||||
// .replace("{manifest-uri}", hangarConfig.isUseWebpack() ? "http://localhost:8081/manifest/manifest.json" : "'self'")
|
||||
// .replace("{prefetch-uri}", hangarConfig.isUseWebpack() ? "http://localhost:8081" : "'self'")
|
||||
// .replace("{nonce}", CSP_NONCE));
|
||||
// TODO still some changes to make to the header
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package io.papermc.hangar.db.dao;
|
||||
|
||||
import io.papermc.hangar.db.customtypes.JSONB;
|
||||
import io.papermc.hangar.db.mappers.DependencyMapper;
|
||||
import io.papermc.hangar.db.mappers.PlatformDependencyMapper;
|
||||
import io.papermc.hangar.db.mappers.VersionDependenciesMapper;
|
||||
import io.papermc.hangar.db.model.ProjectVersionTagsTable;
|
||||
import io.papermc.hangar.db.model.ProjectVersionsTable;
|
||||
import io.papermc.hangar.model.generated.ReviewState;
|
||||
@ -21,7 +21,7 @@ import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
@RegisterColumnMapper(DependencyMapper.class)
|
||||
@RegisterColumnMapper(VersionDependenciesMapper.class)
|
||||
@RegisterColumnMapper(PlatformDependencyMapper.class)
|
||||
@RegisterBeanMapper(ProjectVersionsTable.class)
|
||||
@RegisterBeanMapper(ProjectVersionTagsTable.class)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package io.papermc.hangar.db.dao.api;
|
||||
|
||||
import io.papermc.hangar.db.mappers.DependencyMapper;
|
||||
import io.papermc.hangar.db.mappers.PlatformDependencyMapper;
|
||||
import io.papermc.hangar.db.mappers.VersionDependenciesMapper;
|
||||
import io.papermc.hangar.db.model.ProjectChannelsTable;
|
||||
import io.papermc.hangar.db.model.ProjectVersionTagsTable;
|
||||
import io.papermc.hangar.db.model.ProjectVersionsTable;
|
||||
@ -54,7 +54,7 @@ public interface V1ApiDao {
|
||||
|
||||
@UseStringTemplateEngine
|
||||
@RegisterBeanMapper(ProjectVersionsTable.class)
|
||||
@RegisterColumnMapper(DependencyMapper.class)
|
||||
@RegisterColumnMapper(VersionDependenciesMapper.class)
|
||||
@RegisterColumnMapper(PlatformDependencyMapper.class)
|
||||
@SqlQuery("SELECT pv.* " +
|
||||
" FROM project_versions pv" +
|
||||
@ -83,7 +83,7 @@ public interface V1ApiDao {
|
||||
|
||||
@KeyColumn("p_id")
|
||||
@RegisterBeanMapper(ProjectVersionsTable.class)
|
||||
@RegisterColumnMapper(DependencyMapper.class)
|
||||
@RegisterColumnMapper(VersionDependenciesMapper.class)
|
||||
@RegisterColumnMapper(PlatformDependencyMapper.class)
|
||||
@SqlQuery("SELECT p.id p_id, pv.* FROM project_versions pv JOIN projects p ON pv.project_id = p.id WHERE p.recommended_version_id = pv.id AND p.id IN (<projectIds>)")
|
||||
Map<Long, ProjectVersionsTable> getProjectsRecommendedVersion(@BindList(onEmpty = EmptyHandling.NULL_STRING) List<Long> projectIds);
|
||||
|
@ -1,7 +1,7 @@
|
||||
package io.papermc.hangar.db.dao.api;
|
||||
|
||||
import io.papermc.hangar.db.dao.api.mappers.VersionMapper;
|
||||
import io.papermc.hangar.db.mappers.DependencyMapper;
|
||||
import io.papermc.hangar.db.mappers.VersionDependenciesMapper;
|
||||
import io.papermc.hangar.model.generated.Version;
|
||||
import io.papermc.hangar.model.generated.VersionStatsDay;
|
||||
import org.jdbi.v3.sqlobject.config.KeyColumn;
|
||||
@ -24,7 +24,7 @@ import java.util.Map;
|
||||
public interface VersionsApiDao {
|
||||
|
||||
@UseStringTemplateEngine
|
||||
@RegisterColumnMapper(DependencyMapper.class)
|
||||
@RegisterColumnMapper(VersionDependenciesMapper.class)
|
||||
@SqlQuery("SELECT pv.created_at," +
|
||||
"pv.version_string," +
|
||||
"pv.dependencies," +
|
||||
@ -53,7 +53,7 @@ public interface VersionsApiDao {
|
||||
"ORDER BY pv.created_at DESC LIMIT 1")
|
||||
Version getVersion(String author, String slug, String versionString, @Define boolean canSeeHidden, @Define Long userId);
|
||||
|
||||
@RegisterColumnMapper(DependencyMapper.class)
|
||||
@RegisterColumnMapper(VersionDependenciesMapper.class)
|
||||
@UseStringTemplateEngine
|
||||
@SqlQuery("SELECT pv.created_at," +
|
||||
"pv.version_string," +
|
||||
|
@ -17,7 +17,7 @@ import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DependencyMapper implements ColumnMapper<VersionDependencies> {
|
||||
public class VersionDependenciesMapper implements ColumnMapper<VersionDependencies> {
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
@ -92,7 +92,7 @@ showFooter: Boolean = true, noContainer: Boolean = false, additionalMeta: Html =
|
||||
</#if>
|
||||
|
||||
<#if scriptsEnabled>
|
||||
<script>
|
||||
<script nonce="${nonce}">
|
||||
window.ROUTES = ${mapper.valueToTree(Routes.getJsRoutes())};
|
||||
window.ROUTES.parse = function (key, ...params) {
|
||||
var route = window.ROUTES[key];
|
||||
@ -103,7 +103,7 @@ showFooter: Boolean = true, noContainer: Boolean = false, additionalMeta: Html =
|
||||
};
|
||||
</script>
|
||||
<#if _csrf?? && _csrf.token??>
|
||||
<script>
|
||||
<script nonce="${nonce}">
|
||||
window.csrf = '${_csrf.token}';
|
||||
window.csrfInfo = {
|
||||
'parameterName': '${_csrf.parameterName}',
|
||||
|
@ -57,6 +57,4 @@
|
||||
|
||||
</div>
|
||||
|
||||
<#-- <@modalManage.modalManage />-->
|
||||
|
||||
</@base.base>
|
||||
|
Loading…
x
Reference in New Issue
Block a user