added GlobalPermission method security annotation (#51)

This commit is contained in:
Jake Potrebic 2020-07-30 12:02:25 -07:00 committed by GitHub
parent 0c9788fcce
commit ac07e27132
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 359 additions and 16 deletions

View File

@ -0,0 +1,38 @@
package me.minidigger.hangar.config;
import me.minidigger.hangar.security.annotations.GlobalPermissionMetadataSource;
import me.minidigger.hangar.security.voters.GlobalPermissionVoter;
import me.minidigger.hangar.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.access.vote.AbstractAccessDecisionManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
@Configuration
@AutoConfigureBefore(SecurityConfig.class)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
private final PermissionService permissionService;
@Autowired
public MethodSecurityConfig(PermissionService permissionService) {
this.permissionService = permissionService;
}
@Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new GlobalPermissionMetadataSource();
}
@Override
protected AccessDecisionManager accessDecisionManager() {
AbstractAccessDecisionManager manager = (AbstractAccessDecisionManager) super.accessDecisionManager();
manager.getDecisionVoters().add(new GlobalPermissionVoter(permissionService));
return manager;
}
}

View File

@ -1,31 +1,44 @@
package me.minidigger.hangar.config;
import me.minidigger.hangar.filter.HangarAuthenticationFilter;
import me.minidigger.hangar.security.HangarAuthenticationProvider;
import me.minidigger.hangar.security.voters.GlobalPermissionVoter;
import me.minidigger.hangar.service.PermissionService;
import me.minidigger.hangar.util.RouteHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
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.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import me.minidigger.hangar.filter.HangarAuthenticationFilter;
import me.minidigger.hangar.security.HangarAuthenticationProvider;
import me.minidigger.hangar.util.RouteHelper;
import java.util.Arrays;
import java.util.List;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final HangarAuthenticationProvider authProvider;
private final MethodSecurityConfig methodSecurityConfig;
private final RouteHelper routeHelper;
private final PermissionService permissionService;
@Autowired
public SecurityConfig(HangarAuthenticationProvider authProvider, RouteHelper routeHelper) {
public SecurityConfig(HangarAuthenticationProvider authProvider, MethodSecurityConfig methodSecurityConfig, RouteHelper routeHelper, PermissionService permissionService) {
this.authProvider = authProvider;
this.methodSecurityConfig = methodSecurityConfig;
this.routeHelper = routeHelper;
this.permissionService = permissionService;
}
@Override
@ -38,7 +51,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
http.exceptionHandling().authenticationEntryPoint((request, response, e) -> response.sendRedirect(routeHelper.getRouteUrl("users.login", "", "", request.getRequestURI())));
http.authorizeRequests().anyRequest().permitAll(); // we use method security
http.authorizeRequests().anyRequest().permitAll().accessDecisionManager(accessDecisionManager()); // we use method security
}
@Override
@ -51,4 +64,15 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Bean
public AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters = Arrays.asList(
new WebExpressionVoter(),
new RoleVoter(),
new AuthenticatedVoter(),
new GlobalPermissionVoter(permissionService)
);
return new UnanimousBased(decisionVoters);
}
}

View File

@ -8,11 +8,13 @@ import me.minidigger.hangar.db.dao.UserDao;
import me.minidigger.hangar.db.model.ProjectsTable;
import me.minidigger.hangar.db.model.UsersTable;
import me.minidigger.hangar.model.Category;
import me.minidigger.hangar.model.NamedPermission;
import me.minidigger.hangar.model.Permission;
import me.minidigger.hangar.model.Role;
import me.minidigger.hangar.model.viewhelpers.ProjectData;
import me.minidigger.hangar.model.viewhelpers.ProjectPage;
import me.minidigger.hangar.model.viewhelpers.ScopedProjectData;
import me.minidigger.hangar.security.annotations.GlobalPermission;
import me.minidigger.hangar.service.OrgService;
import me.minidigger.hangar.service.UserService;
import me.minidigger.hangar.service.project.PagesSerivce;
@ -24,6 +26,7 @@ import me.minidigger.hangar.util.HangarException;
import me.minidigger.hangar.util.RouteHelper;
import me.minidigger.hangar.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
@ -32,6 +35,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView;

View File

@ -0,0 +1,14 @@
package me.minidigger.hangar.db.dao;
import me.minidigger.hangar.model.Permission;
import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.springframework.stereotype.Repository;
@Repository
public interface PermissionsDao {
@RegisterBeanMapper(value = Permission.class, prefix = "perm")
@SqlQuery("SELECT gt.permission::bigint perm_value FROM global_trust gt JOIN users u on gt.user_id = u.id WHERE (u.id = :userId OR u.name = :userName)")
Permission getGlobalPermission(Long userId, String userName);
}

View File

@ -21,7 +21,11 @@ public interface UserGlobalRolesDao {
void insert(@BindBean UserGlobalRolesTable entry);
@AllowUnusedBindings
@SqlQuery("SELECT r.id, r.name, r.category, r.title, r.color, r.is_assignable, r.rank, r.permission FROM user_global_roles ugr JOIN roles r on ugr.role_id = r.id")
@SqlQuery("SELECT r.id, r.name, r.category, r.title, r.color, r.is_assignable, r.rank, r.permission " +
"FROM user_global_roles ugr " +
"JOIN roles r on ugr.role_id = r.id " +
"JOIN users u on ugr.user_id = u.id " +
"WHERE (u.id = :id OR u.name = :userName)")
@RegisterBeanMapper(RolesTable.class)
List<RolesTable> getRolesByUserId(long id);
List<RolesTable> getRolesByUserId(Long id, String userName);
}

View File

@ -1,14 +1,15 @@
package me.minidigger.hangar.security;
import me.minidigger.hangar.db.model.UsersTable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
import me.minidigger.hangar.db.model.UsersTable;
public class HangarAuthentication extends AbstractAuthenticationToken {
private static final long serialVersionUID = 3847099128940870714L;
private final String username;
private UsersTable table; // todo replace me
@ -18,7 +19,7 @@ public class HangarAuthentication extends AbstractAuthenticationToken {
setAuthenticated(false);
}
public HangarAuthentication(String username, UsersTable usersTable, Collection<? extends GrantedAuthority> authorities) {
public HangarAuthentication(String username, UsersTable usersTable, Collection<GrantedAuthority> authorities) {
super(authorities);
this.username = username;
this.table = usersTable;

View File

@ -1,13 +1,23 @@
package me.minidigger.hangar.security;
import me.minidigger.hangar.model.NamedPermission;
import me.minidigger.hangar.model.Permission;
import me.minidigger.hangar.security.authorities.PermissionAuthority;
import me.minidigger.hangar.service.PermissionService;
import me.minidigger.hangar.service.RoleService;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import me.minidigger.hangar.db.dao.HangarDao;
import me.minidigger.hangar.db.dao.UserDao;
@ -17,9 +27,11 @@ import me.minidigger.hangar.db.model.UsersTable;
public class HangarAuthenticationProvider implements AuthenticationProvider {
private final HangarDao<UserDao> userDao;
private final PermissionService permissionService;
public HangarAuthenticationProvider(HangarDao<UserDao> userDao) {
public HangarAuthenticationProvider(HangarDao<UserDao> userDao, PermissionService permissionService) {
this.userDao = userDao;
this.permissionService = permissionService;
}
@Override
@ -31,8 +43,11 @@ public class HangarAuthenticationProvider implements AuthenticationProvider {
UsersTable usersTable = userDao.get().getByName(name);
// TODO validate stuff, guess we need to pass sso stuff here?
Collection<GrantedAuthority> authorities = new HashSet<>();
authorities.addAll(List.of(new SimpleGrantedAuthority("ROLE_ADMIN"), new SimpleGrantedAuthority("ROLE_USER")));
if (usersTable != null) {
return new HangarAuthentication(name,usersTable , List.of(new SimpleGrantedAuthority("ROLE_ADMIN"), new SimpleGrantedAuthority("ROLE_USER")));
return new HangarAuthentication(name, usersTable, authorities);
} else {
return null;
}

View File

@ -0,0 +1,14 @@
package me.minidigger.hangar.security.annotations;
import me.minidigger.hangar.model.NamedPermission;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface GlobalPermission {
NamedPermission[] value();
}

View File

@ -0,0 +1,62 @@
package me.minidigger.hangar.security.annotations;
import me.minidigger.hangar.security.attributes.PermissionAttribute;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.annotation.AnnotationMetadataExtractor;
import org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource;
import org.springframework.util.Assert;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
public class GlobalPermissionMetadataSource extends AbstractFallbackMethodSecurityMetadataSource {
private final AnnotationMetadataExtractor annotationExtractor;
private Class<? extends Annotation> annotationType;
public GlobalPermissionMetadataSource() {
this(new GlobalPermissionAnnotationMetadataExtractor());
}
public GlobalPermissionMetadataSource(AnnotationMetadataExtractor annotationMetadataExtractor) {
Assert.notNull(annotationMetadataExtractor, "annotationMetadataExtractor cannot be null");
this.annotationExtractor = annotationMetadataExtractor;
annotationType = (Class<? extends Annotation>) GenericTypeResolver.resolveTypeArgument(annotationExtractor.getClass(), AnnotationMetadataExtractor.class);
Assert.notNull(annotationType, () -> annotationExtractor.getClass().getName() + " must supply a generic parameter for AnnotationMetadataExtractor");
}
@Override
protected Collection<ConfigAttribute> findAttributes(Method method, Class<?> targetClass) {
return processAnnotation(AnnotationUtils.findAnnotation(method, annotationType));
}
@Override
protected Collection<ConfigAttribute> findAttributes(Class<?> clazz) {
return processAnnotation(AnnotationUtils.findAnnotation(clazz, annotationType));
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
private Collection<ConfigAttribute> processAnnotation(Annotation a) {
if (a == null) {
return null;
}
return annotationExtractor.extractAttributes(a);
}
}
class GlobalPermissionAnnotationMetadataExtractor implements AnnotationMetadataExtractor<GlobalPermission> {
@Override
public Collection<? extends ConfigAttribute> extractAttributes(GlobalPermission securityAnnotation) {
return PermissionAttribute.createList(securityAnnotation.value());
}
}

View File

@ -0,0 +1,55 @@
package me.minidigger.hangar.security.attributes;
import me.minidigger.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 {
private final NamedPermission permission;
public PermissionAttribute(NamedPermission permission) {
Assert.notNull(permission, "You must provide a NamedPermission");
this.permission = permission;
}
public NamedPermission getPermission() {
return permission;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PermissionAttribute)) return false;
PermissionAttribute that = (PermissionAttribute) obj;
return that.permission == this.permission;
}
@Override
public String getAttribute() {
return null;
}
@Override
public int hashCode() {
return this.permission.hashCode();
}
@Override
public String toString() {
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;
}
}

View File

@ -0,0 +1,36 @@
package me.minidigger.hangar.security.authorities;
import me.minidigger.hangar.model.NamedPermission;
import org.springframework.security.core.GrantedAuthority;
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;
}
}

View File

@ -0,0 +1,47 @@
package me.minidigger.hangar.security.voters;
import me.minidigger.hangar.model.NamedPermission;
import me.minidigger.hangar.model.Permission;
import me.minidigger.hangar.security.attributes.PermissionAttribute;
import me.minidigger.hangar.service.PermissionService;
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.Set;
import java.util.stream.Collectors;
public class GlobalPermissionVoter implements AccessDecisionVoter {
private final PermissionService permissionService;
public GlobalPermissionVoter(PermissionService permissionService) {
this.permissionService = permissionService;
}
@Override
public boolean supports(ConfigAttribute attribute) {
return attribute instanceof PermissionAttribute;
}
@Override
public boolean supports(Class clazz) {
return true;
}
@Override
public int vote(Authentication authentication, Object object, Collection collection) {
Permission globalPerm = permissionService.getGlobalPermission(authentication.getName());
if (globalPerm == null) globalPerm = !authentication.getPrincipal().equals("anonymousUser") ? Permission.HardDeleteProject : Permission.None; // TODO testing
Set<NamedPermission> requiredPermissions = ((Collection<ConfigAttribute>) collection).stream().filter(this::supports).map(PermissionAttribute.class::cast).map(PermissionAttribute::getPermission).collect(Collectors.toSet());
Collection<NamedPermission> userGlobalPermissions = globalPerm.toNamed();
if (!userGlobalPermissions.containsAll(requiredPermissions)) {
System.out.println("Required perms: " + requiredPermissions.toString());
System.out.println("User perms: " + userGlobalPermissions.toString());
return ACCESS_DENIED;
}
return ACCESS_GRANTED;
}
}

View File

@ -0,0 +1,26 @@
package me.minidigger.hangar.service;
import me.minidigger.hangar.db.dao.HangarDao;
import me.minidigger.hangar.db.dao.PermissionsDao;
import me.minidigger.hangar.model.Permission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PermissionService {
private final HangarDao<PermissionsDao> permissionsDao;
@Autowired
public PermissionService(HangarDao<PermissionsDao> permissionsDao) {
this.permissionsDao = permissionsDao;
}
public Permission getGlobalPermission(long userId) {
return permissionsDao.get().getGlobalPermission(userId, null);
}
public Permission getGlobalPermission(String userName) {
return permissionsDao.get().getGlobalPermission(null, userName);
}
}

View File

@ -1,8 +1,10 @@
package me.minidigger.hangar.service;
import org.postgresql.shaded.com.ongres.scram.common.util.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import me.minidigger.hangar.db.dao.HangarDao;
@ -68,7 +70,8 @@ public class RoleService {
userGlobalRolesDao.get().insert(new UserGlobalRolesTable(userId, roleId));
}
public List<Role> getGlobalRolesForUser(long userId) {
return userGlobalRolesDao.get().getRolesByUserId(userId).stream().map(adf -> Role.fromId(adf.getId())).collect(Collectors.toList());
public List<Role> getGlobalRolesForUser(@Nullable Long userId, @Nullable String userName) {
Preconditions.checkArgument(userId != null || userName != null, "One of (userId, userName) must be nonnull");
return userGlobalRolesDao.get().getRolesByUserId(userId, userName).stream().map(adf -> Role.fromId(adf.getId())).collect(Collectors.toList());
}
}

View File

@ -131,7 +131,7 @@ public class UserService {
int projectCount = projectDao.get().getProjectCountByUserId(user.getId());
List<Organization> organizations = orgDao.get().getUserOrgs(user.getId());
// List<Role> globalRoles = List.of(Role.HANGAR_ADMIN);
List<Role> globalRoles = roleService.getGlobalRolesForUser(user.getId());
List<Role> globalRoles = roleService.getGlobalRolesForUser(user.getId(), null);
Permission userPerm = Permission.All;
Permission orgaPerm = Permission.None;
return new UserData(getHeaderData(), user, isOrga, projectCount, organizations, globalRoles, userPerm, orgaPerm);