mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-01-06 13:56:14 +08:00
fully implemented ProjectPermission annotation
If error w/status code 401/403 and user is not logged in, they are redirected to login.
This commit is contained in:
parent
10929c97e6
commit
2e596e7591
@ -4,26 +4,40 @@ import io.papermc.hangar.security.metadatasources.GlobalPermissionSource;
|
||||
import io.papermc.hangar.security.metadatasources.HangarMetadataSources;
|
||||
import io.papermc.hangar.security.metadatasources.ProjectPermissionSource;
|
||||
import io.papermc.hangar.security.voters.GlobalPermissionVoter;
|
||||
import io.papermc.hangar.security.voters.ProjectPermissionVoter;
|
||||
import io.papermc.hangar.service.PermissionService;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.access.AccessDecisionManager;
|
||||
import org.springframework.security.access.AccessDecisionVoter;
|
||||
import org.springframework.security.access.annotation.Jsr250Voter;
|
||||
import org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice;
|
||||
import org.springframework.security.access.method.MethodSecurityMetadataSource;
|
||||
import org.springframework.security.access.vote.AbstractAccessDecisionManager;
|
||||
import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter;
|
||||
import org.springframework.security.access.vote.AuthenticatedVoter;
|
||||
import org.springframework.security.access.vote.RoleVoter;
|
||||
import org.springframework.security.access.vote.UnanimousBased;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@AutoConfigureBefore(SecurityConfig.class)
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
|
||||
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
private final PermissionService permissionService;
|
||||
|
||||
@Autowired
|
||||
public MethodSecurityConfig(PermissionService permissionService) {
|
||||
public MethodSecurityConfig(ApplicationContext applicationContext, PermissionService permissionService) {
|
||||
this.applicationContext = applicationContext;
|
||||
this.permissionService = permissionService;
|
||||
}
|
||||
|
||||
@ -34,8 +48,21 @@ public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
|
||||
|
||||
@Override
|
||||
protected AccessDecisionManager accessDecisionManager() {
|
||||
AbstractAccessDecisionManager manager = (AbstractAccessDecisionManager) super.accessDecisionManager();
|
||||
manager.getDecisionVoters().add(new GlobalPermissionVoter(permissionService));
|
||||
return manager;
|
||||
List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
|
||||
ExpressionBasedPreInvocationAdvice expressionAdvice =
|
||||
new ExpressionBasedPreInvocationAdvice();
|
||||
expressionAdvice.setExpressionHandler(getExpressionHandler());
|
||||
decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice));
|
||||
decisionVoters.add(new Jsr250Voter());
|
||||
RoleVoter roleVoter = new RoleVoter();
|
||||
try {
|
||||
GrantedAuthorityDefaults grantedAuthorityDefaults = applicationContext.getBean(GrantedAuthorityDefaults.class);
|
||||
roleVoter.setRolePrefix(grantedAuthorityDefaults.getRolePrefix());
|
||||
} catch (BeansException ignored) { }
|
||||
decisionVoters.add(roleVoter);
|
||||
decisionVoters.add(new AuthenticatedVoter());
|
||||
decisionVoters.add(new ProjectPermissionVoter(permissionService));
|
||||
decisionVoters.add(new GlobalPermissionVoter(permissionService));
|
||||
return new UnanimousBased(decisionVoters);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package io.papermc.hangar.config;
|
||||
import freemarker.template.TemplateException;
|
||||
import io.papermc.hangar.controller.converters.ColorHexConverter;
|
||||
import io.papermc.hangar.controller.converters.StringToEnumConverterFactory;
|
||||
import io.papermc.hangar.controller.interceptors.ProjectsInterceptor;
|
||||
import io.papermc.hangar.service.PermissionService;
|
||||
import io.papermc.hangar.service.project.ProjectService;
|
||||
import io.papermc.hangar.util.Routes;
|
||||
@ -22,7 +21,6 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.resource.VersionResourceResolver;
|
||||
@ -129,11 +127,6 @@ public class MvcConfig implements WebMvcConfigurer {
|
||||
registry.addConverter(new ColorHexConverter());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new ProjectsInterceptor(projectService, permissionService));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
@ -3,6 +3,7 @@ package io.papermc.hangar.config;
|
||||
import io.papermc.hangar.filter.HangarAuthenticationFilter;
|
||||
import io.papermc.hangar.security.HangarAuthenticationProvider;
|
||||
import io.papermc.hangar.security.voters.GlobalPermissionVoter;
|
||||
import io.papermc.hangar.security.voters.ProjectPermissionVoter;
|
||||
import io.papermc.hangar.service.PermissionService;
|
||||
import io.papermc.hangar.util.Routes;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -66,6 +67,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
new WebExpressionVoter(),
|
||||
new RoleVoter(),
|
||||
new AuthenticatedVoter(),
|
||||
new ProjectPermissionVoter(permissionService),
|
||||
new GlobalPermissionVoter(permissionService)
|
||||
);
|
||||
return new UnanimousBased(decisionVoters);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.papermc.hangar.controller;
|
||||
|
||||
import io.papermc.hangar.util.Routes;
|
||||
import org.springframework.boot.web.servlet.error.ErrorController;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@ -14,7 +15,8 @@ public class HangarErrorController extends HangarController implements ErrorCont
|
||||
|
||||
@RequestMapping("/error")
|
||||
public ModelAndView handleError(HttpServletRequest request) {
|
||||
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
|
||||
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); // TODO redirect to sign on if not signed in
|
||||
String errorRequestUri = (String) request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
|
||||
|
||||
ModelAndView mav = new ModelAndView("errors/error"); // TODO show custom message with error if applicable
|
||||
if (status != null) {
|
||||
@ -24,6 +26,8 @@ public class HangarErrorController extends HangarController implements ErrorCont
|
||||
mav = new ModelAndView("errors/notFound");
|
||||
} else if (statusCode == HttpStatus.GATEWAY_TIMEOUT.value() || statusCode == HttpStatus.REQUEST_TIMEOUT.value()) {
|
||||
mav = new ModelAndView("errors/timeout");
|
||||
} else if ((statusCode == HttpStatus.FORBIDDEN.value() || statusCode == HttpStatus.UNAUTHORIZED.value()) && currentUser.get().isEmpty() && errorRequestUri != null) {
|
||||
return Routes.USERS_LOGIN.getRedirect("", "", errorRequestUri);
|
||||
}
|
||||
}
|
||||
return fillModel(mav);
|
||||
|
@ -352,6 +352,7 @@ public class ProjectsController extends HangarController {
|
||||
userActionLogService.project(request, LoggedActionType.PROJECT_ICON_CHANGED.with(ProjectContext.of(project.getId())), "", "");
|
||||
}
|
||||
|
||||
@ProjectPermission(NamedPermission.EDIT_SUBJECT_SETTINGS)
|
||||
@Secured("ROLE_USER")
|
||||
@GetMapping("/{author}/{slug}/manage")
|
||||
public ModelAndView showSettings(@PathVariable String author, @PathVariable String slug) {
|
||||
|
@ -1,68 +0,0 @@
|
||||
package io.papermc.hangar.controller.interceptors;
|
||||
|
||||
import io.papermc.hangar.security.HangarAuthentication;
|
||||
import io.papermc.hangar.service.project.ProjectService;
|
||||
import io.papermc.hangar.model.NamedPermission;
|
||||
import io.papermc.hangar.security.annotations.ProjectPermission;
|
||||
import io.papermc.hangar.service.PermissionService;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
public class ProjectsInterceptor extends HandlerInterceptorAdapter {
|
||||
|
||||
private final ProjectService projectService;
|
||||
private final PermissionService permissionService;
|
||||
|
||||
@Autowired
|
||||
public ProjectsInterceptor(ProjectService projectService, PermissionService permissionService) {
|
||||
this.projectService = projectService;
|
||||
this.permissionService = permissionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception {
|
||||
if (!(handler instanceof HandlerMethod)) return true;
|
||||
HandlerMethod method = (HandlerMethod) handler;
|
||||
if (method.getMethod().isAnnotationPresent(ProjectPermission.class)) {
|
||||
if (!(SecurityContextHolder.getContext().getAuthentication() instanceof HangarAuthentication)) {
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> pathParamMap = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
if (!pathParamMap.keySet().equals(Set.of("author", "slug"))) {
|
||||
System.err.println("Can't have @ProjectPermission annotation on a request method without an 'author' and 'slug' path parameter");
|
||||
return false; // shouldnt happen. just don't place the annotations on methods that don't have these parameters
|
||||
}
|
||||
if (!(SecurityContextHolder.getContext().getAuthentication() instanceof HangarAuthentication)) {
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
long userId = ((HangarAuthentication) SecurityContextHolder.getContext().getAuthentication()).getUserId();
|
||||
Collection<NamedPermission> userProjectPermissions = permissionService.getProjectPermissions(userId, projectService.getProjectData(pathParamMap.get("author"), pathParamMap.get("slug")).getProject().getId()).toNamed(); // TODO maybe remove call to getProjectData here and switch to bean
|
||||
Collection<NamedPermission> requirePermissions = Arrays.asList(AnnotationUtils.getAnnotation(method.getMethod(), ProjectPermission.class).value());
|
||||
if (!userProjectPermissions.containsAll(requirePermissions)) {
|
||||
System.out.println("Required perms: " + requirePermissions.toString());
|
||||
System.out.println("User perms: " + userProjectPermissions.toString());
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -33,6 +33,15 @@ public interface PermissionsDao {
|
||||
" WHERE u.id = :userId")
|
||||
Permission getProjectPermission(long userId, String pluginId);
|
||||
|
||||
@SqlQuery("SELECT (coalesce(gt.permission, B'0'::BIT(64)) | coalesce(pt.permission, B'0'::BIT(64)) | coalesce(ot.permission, B'0'::BIT(64)))::BIGINT AS perm_value" +
|
||||
" FROM users u " +
|
||||
" LEFT JOIN global_trust gt ON u.id = gt.user_id" +
|
||||
" LEFT JOIN projects p ON lower(p.owner_name) = lower(:author) AND p.slug = :slug" +
|
||||
" LEFT JOIN project_trust pt ON u.id = pt.user_id AND pt.project_id = p.id" +
|
||||
" LEFT JOIN organization_trust ot ON u.id = ot.user_id AND ot.organization_id = p.owner_id" +
|
||||
" WHERE u.id = :userId")
|
||||
Permission getProjectPermission(long userId, String author, String slug);
|
||||
|
||||
@SqlQuery("SELECT (coalesce(gt.permission, B'0'::BIT(64)) | coalesce(ot.permission, B'0'::BIT(64)))::BIGINT AS perm_value" +
|
||||
" FROM users u " +
|
||||
" LEFT JOIN global_trust gt ON u.id = gt.user_id" +
|
||||
|
@ -1,40 +0,0 @@
|
||||
package io.papermc.hangar.security;
|
||||
|
||||
import io.papermc.hangar.model.NamedPermission;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
/**
|
||||
* @deprecated Not used atm. This can be used if we want to store global roles on the auth model
|
||||
*/
|
||||
@Deprecated
|
||||
public class PermissionAuthority implements GrantedAuthority {
|
||||
private final NamedPermission permission;
|
||||
|
||||
public PermissionAuthority(NamedPermission permission) {
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.permission.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj instanceof PermissionAuthority) {
|
||||
return permission == (((PermissionAuthority) obj).permission);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return permission.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package io.papermc.hangar.security.attributes;
|
||||
|
||||
import io.papermc.hangar.model.NamedPermission;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GlobalPermissionAttribute extends PermissionAttribute {
|
||||
|
||||
|
||||
public GlobalPermissionAttribute(NamedPermission permission) {
|
||||
super(permission, PermissionAttribute.GLOBAL_TYPE);
|
||||
}
|
||||
|
||||
public static List<ConfigAttribute> createList(NamedPermission...permissions) {
|
||||
Assert.notNull(permissions, "You must supply an array of permissions");
|
||||
List<ConfigAttribute> attributes = new ArrayList<>(permissions.length);
|
||||
for (NamedPermission permission : permissions) {
|
||||
attributes.add(new GlobalPermissionAttribute(permission));
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
}
|
@ -1,17 +1,16 @@
|
||||
package io.papermc.hangar.security;
|
||||
package io.papermc.hangar.security.attributes;
|
||||
|
||||
import io.papermc.hangar.model.NamedPermission;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PermissionAttribute implements ConfigAttribute {
|
||||
public abstract class PermissionAttribute implements ConfigAttribute {
|
||||
|
||||
private final NamedPermission permission;
|
||||
private final String type;
|
||||
|
||||
public PermissionAttribute(NamedPermission permission) {
|
||||
public PermissionAttribute(NamedPermission permission, String type) {
|
||||
this.type = type;
|
||||
Assert.notNull(permission, "You must provide a NamedPermission");
|
||||
this.permission = permission;
|
||||
}
|
||||
@ -34,6 +33,10 @@ public class PermissionAttribute implements ConfigAttribute {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.permission.hashCode();
|
||||
@ -44,12 +47,6 @@ public class PermissionAttribute implements ConfigAttribute {
|
||||
return this.permission.toString();
|
||||
}
|
||||
|
||||
public static List<ConfigAttribute> createList(NamedPermission...permissions) {
|
||||
Assert.notNull(permissions, "You must supply an array of permissions");
|
||||
List<ConfigAttribute> attributes = new ArrayList<>(permissions.length);
|
||||
for (NamedPermission permission : permissions) {
|
||||
attributes.add(new PermissionAttribute(permission));
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
public static final String GLOBAL_TYPE = "GLOBAL";
|
||||
public static final String PROJECT_TYPE = "PROJECT";
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package io.papermc.hangar.security.attributes;
|
||||
|
||||
import io.papermc.hangar.model.NamedPermission;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ProjectPermissionAttribute extends PermissionAttribute {
|
||||
|
||||
public ProjectPermissionAttribute(NamedPermission permission) {
|
||||
super(permission, PermissionAttribute.PROJECT_TYPE);
|
||||
}
|
||||
|
||||
public static List<ConfigAttribute> createList(NamedPermission...permissions) {
|
||||
Assert.notNull(permissions, "You must supply an array of permissions");
|
||||
List<ConfigAttribute> attributes = new ArrayList<>(permissions.length);
|
||||
for (NamedPermission permission : permissions) {
|
||||
attributes.add(new ProjectPermissionAttribute(permission));
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package io.papermc.hangar.security.metadatasources;
|
||||
|
||||
import io.papermc.hangar.security.PermissionAttribute;
|
||||
import io.papermc.hangar.security.annotations.GlobalPermission;
|
||||
import io.papermc.hangar.security.attributes.GlobalPermissionAttribute;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.annotation.AnnotationMetadataExtractor;
|
||||
|
||||
@ -10,6 +10,6 @@ import java.util.Collection;
|
||||
public class GlobalPermissionSource implements AnnotationMetadataExtractor<GlobalPermission> {
|
||||
@Override
|
||||
public Collection<? extends ConfigAttribute> extractAttributes(GlobalPermission securityAnnotation) {
|
||||
return PermissionAttribute.createList(securityAnnotation.value());
|
||||
return GlobalPermissionAttribute.createList(securityAnnotation.value());
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ public class HangarMetadataSources extends AbstractFallbackMethodSecurityMetadat
|
||||
|
||||
annotationMetadataExtractors.forEach(annotationMetadataExtractor -> {
|
||||
Class<? extends Annotation> annotationType = (Class<? extends Annotation>) GenericTypeResolver.resolveTypeArgument(annotationMetadataExtractor.getClass(), AnnotationMetadataExtractor.class);
|
||||
Assert.notNull(annotationType, () -> annotationMetadataExtractor.getClass().getName() + " must supply a generic parameter for AnnotationMetadataExtracto");
|
||||
Assert.notNull(annotationType, () -> annotationMetadataExtractor.getClass().getName() + " must supply a generic parameter for AnnotationMetadataExtractor");
|
||||
annotationExtractors.put(annotationType, annotationMetadataExtractor);
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package io.papermc.hangar.security.metadatasources;
|
||||
|
||||
import io.papermc.hangar.security.PermissionAttribute;
|
||||
import io.papermc.hangar.security.annotations.ProjectPermission;
|
||||
import io.papermc.hangar.security.attributes.ProjectPermissionAttribute;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.annotation.AnnotationMetadataExtractor;
|
||||
|
||||
@ -10,6 +10,6 @@ import java.util.Collection;
|
||||
public class ProjectPermissionSource implements AnnotationMetadataExtractor<ProjectPermission> {
|
||||
@Override
|
||||
public Collection<? extends ConfigAttribute> extractAttributes(ProjectPermission securityAnnotation) {
|
||||
return PermissionAttribute.createList(securityAnnotation.value());
|
||||
return ProjectPermissionAttribute.createList(securityAnnotation.value());
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,13 @@
|
||||
package io.papermc.hangar.security.voters;
|
||||
|
||||
import io.papermc.hangar.model.NamedPermission;
|
||||
import io.papermc.hangar.security.attributes.PermissionAttribute;
|
||||
import io.papermc.hangar.service.PermissionService;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
public class GlobalPermissionVoter extends HangarPermissionVoter {
|
||||
|
||||
public GlobalPermissionVoter(PermissionService permissionService) {
|
||||
super(hangarAuth -> {
|
||||
Collection<NamedPermission> globalPerms = permissionService.getGlobalPermissions(hangarAuth.getUserId()).toNamed();
|
||||
if (globalPerms.isEmpty()) globalPerms = Set.of(NamedPermission.HARD_DELETE_PROJECT, NamedPermission.SEE_HIDDEN); // TODO for development
|
||||
return globalPerms;
|
||||
}, hangarAuth -> true);
|
||||
super((hangarAuth, ignored) -> permissionService.getGlobalPermissions(hangarAuth.getUserId()).toNamed(),
|
||||
hangarAuth -> true,
|
||||
permissionAttribute -> permissionAttribute.getType().equals(PermissionAttribute.GLOBAL_TYPE));
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,28 @@
|
||||
package io.papermc.hangar.security.voters;
|
||||
|
||||
import io.papermc.hangar.security.HangarAuthentication;
|
||||
import io.papermc.hangar.model.NamedPermission;
|
||||
import io.papermc.hangar.security.PermissionAttribute;
|
||||
import io.papermc.hangar.security.HangarAuthentication;
|
||||
import io.papermc.hangar.security.attributes.PermissionAttribute;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.security.access.AccessDecisionVoter;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class HangarPermissionVoter implements AccessDecisionVoter {
|
||||
|
||||
private final Function<HangarAuthentication, Collection<NamedPermission>> getUserPermissions;
|
||||
private final BiFunction<HangarAuthentication, MethodInvocation, Collection<NamedPermission>> getUserPermissions;
|
||||
private final Predicate<HangarAuthentication> authChecks;
|
||||
private final Predicate<PermissionAttribute> typeCheck;
|
||||
|
||||
public HangarPermissionVoter(Function<HangarAuthentication, Collection<NamedPermission>> getUserPermissions, Predicate<HangarAuthentication> authChecks) {
|
||||
public HangarPermissionVoter(BiFunction<HangarAuthentication, MethodInvocation, Collection<NamedPermission>> getUserPermissions, Predicate<HangarAuthentication> authChecks, Predicate<PermissionAttribute> typeCheck) {
|
||||
this.getUserPermissions = getUserPermissions;
|
||||
this.authChecks = authChecks;
|
||||
this.typeCheck = typeCheck;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -34,7 +37,9 @@ public abstract class HangarPermissionVoter implements AccessDecisionVoter {
|
||||
|
||||
@Override
|
||||
public int vote(Authentication authentication, Object object, Collection collection) {
|
||||
Collection<NamedPermission> requiredPermissions = ((Collection<ConfigAttribute>) collection).stream().filter(this::supports).map(PermissionAttribute.class::cast).map(PermissionAttribute::getPermission).collect(Collectors.toSet());
|
||||
if (!(object instanceof MethodInvocation)) return ACCESS_ABSTAIN;
|
||||
MethodInvocation method = (MethodInvocation) object;
|
||||
Collection<NamedPermission> requiredPermissions = ((Collection<ConfigAttribute>) collection).stream().filter(this::supports).map(PermissionAttribute.class::cast).filter(typeCheck).map(PermissionAttribute::getPermission).collect(Collectors.toSet());
|
||||
if (requiredPermissions.isEmpty() && (!(authentication instanceof HangarAuthentication) || authentication.getPrincipal().equals("anonymousUser"))) {
|
||||
return ACCESS_ABSTAIN;
|
||||
}
|
||||
@ -44,7 +49,7 @@ public abstract class HangarPermissionVoter implements AccessDecisionVoter {
|
||||
|
||||
HangarAuthentication hangarAuth = (HangarAuthentication) authentication;
|
||||
if (!authChecks.test(hangarAuth)) return ACCESS_DENIED;
|
||||
Collection<NamedPermission> userPermissions = getUserPermissions.apply(hangarAuth);
|
||||
Collection<NamedPermission> userPermissions = getUserPermissions.apply(hangarAuth, method);
|
||||
if (!userPermissions.containsAll(requiredPermissions)) {
|
||||
System.out.println("Required perms: " + requiredPermissions.toString());
|
||||
System.out.println("User perms: " + userPermissions.toString());
|
||||
|
@ -0,0 +1,54 @@
|
||||
package io.papermc.hangar.security.voters;
|
||||
|
||||
import io.papermc.hangar.security.attributes.PermissionAttribute;
|
||||
import io.papermc.hangar.service.PermissionService;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.Set;
|
||||
|
||||
public class ProjectPermissionVoter extends HangarPermissionVoter {
|
||||
public ProjectPermissionVoter(PermissionService permissionService) {
|
||||
super(
|
||||
(hangarAuthentication, methodInvocation) -> {
|
||||
Method method = methodInvocation.getMethod();
|
||||
if (method.getParameterCount() == 0) return Set.of();
|
||||
String author = null;
|
||||
String slug = null;
|
||||
Object[] arguments = methodInvocation.getArguments();
|
||||
for (int i = 0; i < method.getParameters().length; i++) {
|
||||
Parameter parameter = method.getParameters()[i];
|
||||
if (parameter.isAnnotationPresent(PathVariable.class)) { // TODO too tired to figure out what the nicer looking way of doing this is
|
||||
PathVariable pathVarAnnotation = parameter.getAnnotation(PathVariable.class);
|
||||
if (pathVarAnnotation.name().equalsIgnoreCase("author")) {
|
||||
author = arguments[i].toString();
|
||||
continue;
|
||||
}
|
||||
else if (pathVarAnnotation.value().equalsIgnoreCase("author")) {
|
||||
author = arguments[i].toString();
|
||||
continue;
|
||||
} else if (parameter.getName().equalsIgnoreCase("author")) {
|
||||
author = arguments[i].toString();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pathVarAnnotation.name().equalsIgnoreCase("slug")) {
|
||||
slug = arguments[i].toString();
|
||||
continue;
|
||||
} else if (pathVarAnnotation.value().equalsIgnoreCase("slug")) {
|
||||
slug = arguments[i].toString();
|
||||
continue;
|
||||
} else if (parameter.getName().equalsIgnoreCase("slug")) {
|
||||
slug = arguments[i].toString();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return permissionService.getProjectPermissions(hangarAuthentication.getUserId(), author, slug).toNamed();
|
||||
},
|
||||
hangarAuthentication -> true,
|
||||
permissionAttribute -> permissionAttribute.getType().equals(PermissionAttribute.PROJECT_TYPE)
|
||||
);
|
||||
}
|
||||
}
|
@ -32,6 +32,10 @@ public class PermissionService {
|
||||
return addDefaults(permissionsDao.get().getProjectPermission(userId, projectId));
|
||||
}
|
||||
|
||||
public Permission getProjectPermissions(long userId, String author, String slug) {
|
||||
return addDefaults(permissionsDao.get().getProjectPermission(userId, author, slug));
|
||||
}
|
||||
|
||||
public Permission getProjectPermissions(UsersTable usersTable, String pluginId) {
|
||||
if (usersTable == null) {
|
||||
return Permission.None;
|
||||
|
Loading…
Reference in New Issue
Block a user