mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-01-06 13:56:14 +08:00
implemented UserLock annotation
has SPeL parsing for getting the args from the redirect from the method parameters
This commit is contained in:
parent
2e596e7591
commit
b14bf1bdfc
@ -3,9 +3,12 @@ package io.papermc.hangar.config;
|
||||
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.metadatasources.UserLockSource;
|
||||
import io.papermc.hangar.security.voters.GlobalPermissionVoter;
|
||||
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.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
@ -34,16 +37,18 @@ public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
private final PermissionService permissionService;
|
||||
private final UserService userService;
|
||||
|
||||
@Autowired
|
||||
public MethodSecurityConfig(ApplicationContext applicationContext, PermissionService permissionService) {
|
||||
public MethodSecurityConfig(ApplicationContext applicationContext, PermissionService permissionService, UserService userService) {
|
||||
this.applicationContext = applicationContext;
|
||||
this.permissionService = permissionService;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
|
||||
return new HangarMetadataSources(new GlobalPermissionSource(), new ProjectPermissionSource());
|
||||
return new HangarMetadataSources(new GlobalPermissionSource(), new ProjectPermissionSource(), new UserLockSource());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -63,6 +68,7 @@ public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
|
||||
decisionVoters.add(new AuthenticatedVoter());
|
||||
decisionVoters.add(new ProjectPermissionVoter(permissionService));
|
||||
decisionVoters.add(new GlobalPermissionVoter(permissionService));
|
||||
decisionVoters.add(new UserLockVoter(userService));
|
||||
return new UnanimousBased(decisionVoters);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ 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.security.UserLockExceptionResolver;
|
||||
import io.papermc.hangar.service.PermissionService;
|
||||
import io.papermc.hangar.service.project.ProjectService;
|
||||
import io.papermc.hangar.util.Routes;
|
||||
@ -119,9 +120,7 @@ public class MvcConfig implements WebMvcConfigurer {
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
// yeah, idk
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverterFactory(new StringToEnumConverterFactory());
|
||||
registry.addConverter(new ColorHexConverter());
|
||||
@ -136,4 +135,9 @@ public class MvcConfig implements WebMvcConfigurer {
|
||||
restTemplate.setMessageConverters(messageConverters);
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserLockExceptionResolver userLockExceptionResolver() {
|
||||
return new UserLockExceptionResolver();
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ 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.security.voters.UserLockVoter;
|
||||
import io.papermc.hangar.service.PermissionService;
|
||||
import io.papermc.hangar.service.UserService;
|
||||
import io.papermc.hangar.util.Routes;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -30,11 +32,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final HangarAuthenticationProvider authProvider;
|
||||
private final PermissionService permissionService;
|
||||
private final UserService userService;
|
||||
|
||||
@Autowired
|
||||
public SecurityConfig(HangarAuthenticationProvider authProvider, PermissionService permissionService) {
|
||||
public SecurityConfig(HangarAuthenticationProvider authProvider, PermissionService permissionService, UserService userService) {
|
||||
this.authProvider = authProvider;
|
||||
this.permissionService = permissionService;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -68,7 +72,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
new RoleVoter(),
|
||||
new AuthenticatedVoter(),
|
||||
new ProjectPermissionVoter(permissionService),
|
||||
new GlobalPermissionVoter(permissionService)
|
||||
new GlobalPermissionVoter(permissionService),
|
||||
new UserLockVoter(userService)
|
||||
);
|
||||
return new UnanimousBased(decisionVoters);
|
||||
}
|
||||
|
@ -15,9 +15,10 @@ public class HangarErrorController extends HangarController implements ErrorCont
|
||||
|
||||
@RequestMapping("/error")
|
||||
public ModelAndView handleError(HttpServletRequest request) {
|
||||
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); // TODO redirect to sign on if not signed in
|
||||
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
|
||||
String errorRequestUri = (String) request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
|
||||
|
||||
// request.getAttributeNames().asIterator().forEachRemaining(s -> System.out.println(s + ": " + request.getAttribute(s))); // TODO for logging attributes to see what's there
|
||||
ModelAndView mav = new ModelAndView("errors/error"); // TODO show custom message with error if applicable
|
||||
if (status != null) {
|
||||
int statusCode = Integer.parseInt(status.toString());
|
||||
|
@ -0,0 +1,21 @@
|
||||
package io.papermc.hangar.security;
|
||||
|
||||
import io.papermc.hangar.util.HangarException;
|
||||
import io.papermc.hangar.util.Routes;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
public class UserLockException extends HangarException {
|
||||
|
||||
private final Routes redirectRoute;
|
||||
private final String[] routeArgs;
|
||||
|
||||
public UserLockException(String messageKey, Routes redirectRoute, String[] routeArgs) {
|
||||
super(messageKey);
|
||||
this.redirectRoute = redirectRoute;
|
||||
this.routeArgs = routeArgs;
|
||||
}
|
||||
|
||||
public ModelAndView getRedirectView() {
|
||||
return redirectRoute.getRedirect(routeArgs);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package io.papermc.hangar.security;
|
||||
|
||||
import io.papermc.hangar.util.AlertUtil;
|
||||
import io.papermc.hangar.util.AlertUtil.AlertType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.support.RequestContextUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public class UserLockExceptionResolver implements HandlerExceptionResolver {
|
||||
@Override
|
||||
public ModelAndView resolveException(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, Object handler, @NotNull Exception ex) {
|
||||
|
||||
if (ex instanceof UserLockException) {
|
||||
UserLockException exception = (UserLockException) ex;
|
||||
AlertUtil.applyAlert(RequestContextUtils.getOutputFlashMap(request), AlertType.ERROR, exception.getMessageKey());
|
||||
return exception.getRedirectView();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package io.papermc.hangar.security.annotations;
|
||||
|
||||
import io.papermc.hangar.util.Routes;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface UserLock {
|
||||
@AliasFor("route")
|
||||
Routes value() default Routes.SHOW_HOME;
|
||||
@AliasFor("value")
|
||||
Routes route() default Routes.SHOW_HOME;
|
||||
|
||||
String args() default "";
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package io.papermc.hangar.security.attributes;
|
||||
|
||||
import io.papermc.hangar.util.Routes;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class UserLockAttribute implements ConfigAttribute {
|
||||
|
||||
private final Routes route;
|
||||
private final String args;
|
||||
|
||||
public UserLockAttribute(Routes route, String args) {
|
||||
this.route = route;
|
||||
Assert.notNull(route, "You must provide a route!");
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public Routes getRoute() {
|
||||
return route;
|
||||
}
|
||||
|
||||
public String getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof UserLockAttribute)) return false;
|
||||
|
||||
UserLockAttribute that = (UserLockAttribute) obj;
|
||||
|
||||
return that.route == this.route && that.args == this.args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package io.papermc.hangar.security.metadatasources;
|
||||
|
||||
import io.papermc.hangar.security.annotations.UserLock;
|
||||
import io.papermc.hangar.security.attributes.UserLockAttribute;
|
||||
import io.papermc.hangar.util.Routes;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.annotation.AnnotationMetadataExtractor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class UserLockSource implements AnnotationMetadataExtractor<UserLock> {
|
||||
@Override
|
||||
public Collection<? extends ConfigAttribute> extractAttributes(UserLock securityAnnotation) {
|
||||
Set<UserLockAttribute> attributes = new HashSet<>();
|
||||
Routes route = securityAnnotation.value();
|
||||
if (securityAnnotation.route() != route && securityAnnotation.route() != Routes.SHOW_HOME) {
|
||||
route = securityAnnotation.route();
|
||||
}
|
||||
attributes.add(new UserLockAttribute(
|
||||
route,
|
||||
securityAnnotation.args()
|
||||
));
|
||||
return attributes;
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package io.papermc.hangar.security.voters;
|
||||
|
||||
import io.papermc.hangar.security.HangarAuthentication;
|
||||
import io.papermc.hangar.security.UserLockException;
|
||||
import io.papermc.hangar.security.attributes.UserLockAttribute;
|
||||
import io.papermc.hangar.service.UserService;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.context.expression.MethodBasedEvaluationContext;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
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.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class UserLockVoter implements AccessDecisionVoter {
|
||||
|
||||
private final UserService userService;
|
||||
private final ExpressionParser parser = new SpelExpressionParser();
|
||||
private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||
|
||||
public UserLockVoter(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(ConfigAttribute attribute) {
|
||||
return attribute instanceof UserLockAttribute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class clazz) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int vote(Authentication authentication, Object object, Collection collection) {
|
||||
if (!(object instanceof MethodInvocation)) return ACCESS_ABSTAIN;
|
||||
if (!(authentication instanceof HangarAuthentication) || authentication.getPrincipal().equals("anonymousUser")) {
|
||||
return ACCESS_ABSTAIN;
|
||||
}
|
||||
MethodInvocation methodInvocation = (MethodInvocation) object;
|
||||
HangarAuthentication hangarAuth = (HangarAuthentication) authentication;
|
||||
Collection<UserLockAttribute> attributes = ((Collection<ConfigAttribute>) collection).stream().filter(this::supports).map(UserLockAttribute.class::cast).collect(Collectors.toSet());
|
||||
if (attributes.size() > 1) {
|
||||
throw new IllegalStateException("Should have, at most, 1 user lock attribute");
|
||||
}
|
||||
if (attributes.isEmpty()) {
|
||||
return ACCESS_GRANTED;
|
||||
}
|
||||
UserLockAttribute userLockAttribute = attributes.stream().findAny().get();
|
||||
EvaluationContext context = new MethodBasedEvaluationContext(
|
||||
methodInvocation.getMethod().getDeclaringClass(),
|
||||
methodInvocation.getMethod(),
|
||||
methodInvocation.getArguments(),
|
||||
parameterNameDiscoverer
|
||||
);
|
||||
Expression exp = parser.parseExpression(userLockAttribute.getArgs());
|
||||
List<String> argList = (List<String>) exp.getValue(context);
|
||||
if (argList == null) {
|
||||
argList = List.of();
|
||||
}
|
||||
if (userService.getUsersTable(hangarAuth.getUserId()).isLocked()) {
|
||||
throw new UserLockException("error.user.locked", userLockAttribute.getRoute(), argList.toArray(new String[0]));
|
||||
}
|
||||
return ACCESS_GRANTED;
|
||||
}
|
||||
}
|
@ -75,7 +75,7 @@ public class UserService extends HangarService {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken)) {
|
||||
HangarAuthentication auth = (HangarAuthentication) authentication;
|
||||
return () -> Optional.ofNullable(auth.getTable());
|
||||
return () -> Optional.ofNullable(userDao.get().getById(auth.getUserId()));
|
||||
}
|
||||
return Optional::empty;
|
||||
}
|
||||
@ -188,6 +188,10 @@ public class UserService extends HangarService {
|
||||
return new UserData(getHeaderData(), user, isOrga, projectCount, organizations, globalRoles, userPerm, orgaPerm);
|
||||
}
|
||||
|
||||
public UsersTable getUsersTable(long userId) {
|
||||
return userDao.get().getById(userId);
|
||||
}
|
||||
|
||||
public List<UsersTable> getUsers(List<String> userNames) {
|
||||
return userDao.get().getUsers(userNames);
|
||||
}
|
||||
|
@ -20,16 +20,21 @@ public class AlertUtil {
|
||||
public static final String MSG = "alertMsg";
|
||||
public static final String ARGS = "alertArgs";
|
||||
|
||||
public static ModelAndView showAlert(ModelAndView mav, AlertType alertType, String alertMessage, Object...args) {
|
||||
Map<String, Object> alerts = (Map<String, Object>) mav.getModelMap().getAttribute("alerts");
|
||||
public static Map<String, Object> applyAlert(HashMap<String, Object> input, AlertType alertType, String alertMsg, Object...args) {
|
||||
Map<String, Object> alerts = (Map<String, Object>) input.get("alerts");
|
||||
if (alerts == null) {
|
||||
alerts = new HashMap<>();
|
||||
}
|
||||
Map<String, Object> thisAlert = new HashMap<>();
|
||||
thisAlert.put("message", alertMessage);
|
||||
thisAlert.put("message", alertMsg);
|
||||
thisAlert.put("args", args);
|
||||
alerts.put(alertType.name().toLowerCase(), thisAlert);
|
||||
mav.addObject("alerts", alerts);
|
||||
input.put("alerts", alerts);
|
||||
return input;
|
||||
}
|
||||
|
||||
public static ModelAndView showAlert(ModelAndView mav, AlertType alertType, String alertMessage, Object...args) {
|
||||
applyAlert(mav.getModelMap(), alertType, alertMessage, args);
|
||||
return mav;
|
||||
}
|
||||
|
||||
@ -49,6 +54,8 @@ public class AlertUtil {
|
||||
args = (String[]) modelMap.getAttribute(ARGS);
|
||||
}
|
||||
return showAlert(mav, type, msg, args);
|
||||
} else if (modelMap.containsAttribute("alerts")) {
|
||||
mav.addObject("alerts", modelMap.getAttribute("alerts"));
|
||||
}
|
||||
return mav;
|
||||
}
|
||||
|
@ -75,10 +75,10 @@
|
||||
<@panel title="Other Administration" size=3>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<a href="${config.authUrl}/admin/accounts/user/${user.user.id}/change/">HangarAuth Profile</a>
|
||||
<a href="${config.authUrl}/admin/accounts/user/${u.user.id}/change/">HangarAuth Profile</a>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<a href="https://papermc.io/forums${user.user.name}">Forum Profile</a>
|
||||
<a href="https://papermc.io/forums${u.user.name}">Forum Profile</a>
|
||||
</div>
|
||||
</div>
|
||||
</@panel>
|
||||
|
Loading…
Reference in New Issue
Block a user