From ac7b3dc1d31e7b8ef77d9cb01ce0a726407b7eb0 Mon Sep 17 00:00:00 2001 From: Sonia Zorba <sonia.zorba@inaf.it> Date: Mon, 15 Jul 2019 10:54:17 +0200 Subject: [PATCH] Groups persistence and logic --- .../gms/authn/CustomAuthenticationData.java | 18 +- .../ia2/gms/authn/CustomIdTokenConverter.java | 5 +- .../inaf/ia2/gms/authn/LoginController.java | 4 + .../it/inaf/ia2/gms/authn/SecurityConfig.java | 16 ++ .../it/inaf/ia2/gms/authn/SessionData.java | 41 +++++ .../ia2/gms/controller/GroupsController.java | 47 ++++++ .../gms/controller/GroupsModelController.java | 17 -- .../controller/model/GroupsModelRequest.java | 14 -- .../controller/model/GroupsModelResponse.java | 6 - .../gms/exception/UnauthorizedException.java | 8 + .../model/BaseModelRequest.java | 2 +- .../model/BaseModelResponse.java | 2 +- .../ia2/gms/model/CreateGroupRequest.java | 23 +++ .../inaf/ia2/gms/model/GroupBreadcrumb.java | 23 +++ .../java/it/inaf/ia2/gms/model/GroupNode.java | 83 ++++++++++ .../ia2/gms/model/GroupsModelRequest.java | 32 ++++ .../ia2/gms/model/GroupsModelResponse.java | 53 ++++++ .../java/it/inaf/ia2/gms/model/Identity.java | 23 +++ .../it/inaf/ia2/gms/model/IdentityType.java | 12 ++ .../it/inaf/ia2/gms/model/PaginatedData.java | 83 ++++++++++ .../it/inaf/ia2/gms/model/Permission.java | 9 + .../java/it/inaf/ia2/gms/model/RapUser.java | 34 ++++ .../it/inaf/ia2/gms/model/UserPermission.java | 23 +++ .../gms/persistence/MembershipRepository.java | 16 ++ .../persistence/PermissionsRepository.java | 8 +- .../UserGroupsFromParentSpecification.java | 5 - .../inaf/ia2/gms/persistence/model/Group.java | 13 +- .../ia2/gms/persistence/model/Membership.java | 44 +++++ .../gms/persistence/model/MembershipId.java | 39 +++++ .../inaf/ia2/gms/persistence/model/User.java | 2 + ...serGroup.java => UserGroupPermission.java} | 16 +- ...roupId.java => UserGroupPermissionId.java} | 6 +- .../java/it/inaf/ia2/gms/rap/RapClient.java | 8 + .../ia2/gms/service/GroupsModelService.java | 56 +++++++ .../inaf/ia2/gms/service/GroupsService.java | 154 ++++++++++++------ .../ia2/gms/service/MainModelService.java | 5 - .../it/inaf/ia2/gms/service/UsersService.java | 24 +++ .../inaf/ia2/gms/service/model/GroupNode.java | 48 ------ .../ia2/gms/service/model/Permission.java | 8 - .../gms/persistence/GroupsRepositoryTest.java | 63 ------- .../NestedGroupsIntegrationTest.java | 133 +++++++++++++-- 41 files changed, 968 insertions(+), 258 deletions(-) create mode 100644 gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/controller/GroupsModelController.java delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelRequest.java delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelResponse.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java rename gms/src/main/java/it/inaf/ia2/gms/{controller => }/model/BaseModelRequest.java (82%) rename gms/src/main/java/it/inaf/ia2/gms/{controller => }/model/BaseModelResponse.java (89%) create mode 100644 gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/model/GroupBreadcrumb.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelResponse.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/model/Identity.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/model/IdentityType.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/model/PaginatedData.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/model/Permission.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/model/RapUser.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/model/UserPermission.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipRepository.java delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/UserGroupsFromParentSpecification.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/model/Membership.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipId.java rename gms/src/main/java/it/inaf/ia2/gms/persistence/model/{UserGroup.java => UserGroupPermission.java} (80%) rename gms/src/main/java/it/inaf/ia2/gms/persistence/model/{UserGroupId.java => UserGroupPermissionId.java} (85%) create mode 100644 gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelService.java delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/service/MainModelService.java create mode 100644 gms/src/main/java/it/inaf/ia2/gms/service/UsersService.java delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/service/model/GroupNode.java delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/service/model/Permission.java delete mode 100644 gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsRepositoryTest.java diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/CustomAuthenticationData.java b/gms/src/main/java/it/inaf/ia2/gms/authn/CustomAuthenticationData.java index 66f86bc..71c9da2 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/authn/CustomAuthenticationData.java +++ b/gms/src/main/java/it/inaf/ia2/gms/authn/CustomAuthenticationData.java @@ -4,17 +4,33 @@ import java.util.Collection; import java.util.Map; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; public class CustomAuthenticationData extends UsernamePasswordAuthenticationToken { private final Map<String, Object> attributes; + private final OAuth2AccessToken accessToken; + private final OAuth2RefreshToken refreshToken; - public CustomAuthenticationData(String username, Map<String, Object> attributes, Collection<? extends GrantedAuthority> authorities) { + public CustomAuthenticationData(String username, Map<String, Object> attributes, + Collection<? extends GrantedAuthority> authorities, + OAuth2AccessToken accessToken, OAuth2RefreshToken refreshToken) { super(username, "N/A", authorities); this.attributes = attributes; + this.accessToken = accessToken; + this.refreshToken = refreshToken; } public Map<String, Object> getAttributes() { return attributes; } + + public OAuth2AccessToken getAccessToken() { + return accessToken; + } + + public OAuth2RefreshToken getRefreshToken() { + return refreshToken; + } } diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/CustomIdTokenConverter.java b/gms/src/main/java/it/inaf/ia2/gms/authn/CustomIdTokenConverter.java index d3decd3..7d0bc23 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/authn/CustomIdTokenConverter.java +++ b/gms/src/main/java/it/inaf/ia2/gms/authn/CustomIdTokenConverter.java @@ -6,6 +6,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter; import org.springframework.security.oauth2.provider.token.store.jwk.JwkTokenStore; @@ -25,12 +26,12 @@ public class CustomIdTokenConverter extends DefaultUserAuthenticationConverter { OAuth2AccessToken token = jwkTokenStore.readAccessToken(idToken); Map<String, Object> claims = token.getAdditionalInformation(); - //OAuth2RefreshToken refreshToken = token.getRefreshToken(); + OAuth2RefreshToken refreshToken = token.getRefreshToken(); String principal = (String) claims.get("sub"); List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER"); - return new CustomAuthenticationData(principal, claims, authorities); + return new CustomAuthenticationData(principal, claims, authorities, token, refreshToken); } } diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/LoginController.java b/gms/src/main/java/it/inaf/ia2/gms/authn/LoginController.java index 6e0b895..190aaf0 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/authn/LoginController.java +++ b/gms/src/main/java/it/inaf/ia2/gms/authn/LoginController.java @@ -1,6 +1,7 @@ package it.inaf.ia2.gms.authn; import java.security.Principal; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -8,6 +9,9 @@ import org.springframework.web.bind.annotation.RestController; @RestController public class LoginController { + @Autowired + private SessionData sessionData; + @GetMapping("/login") public Principal start(Principal principal) { return principal; diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java b/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java new file mode 100644 index 0000000..130505e --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java @@ -0,0 +1,16 @@ +package it.inaf.ia2.gms.authn; + +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@Order(1000) +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable(); + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java b/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java new file mode 100644 index 0000000..5a82085 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java @@ -0,0 +1,41 @@ +package it.inaf.ia2.gms.authn; + +import it.inaf.ia2.gms.persistence.UsersRepository; +import it.inaf.ia2.gms.persistence.model.User; +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.SessionScope; + +@Component +@SessionScope +public class SessionData { + + @Autowired + private HttpServletRequest request; + + @Autowired + private UsersRepository usersRepository; + + private String userId; + + @PostConstruct + public void init() { + CustomAuthenticationData authn = (CustomAuthenticationData) ((OAuth2Authentication) request.getUserPrincipal()).getUserAuthentication(); + userId = (String) authn.getPrincipal(); + //accessToken = (String) ((CustomAuthenticationData) request.getUserPrincipal()).getAttributes().get("access_token"); + //System.out.println("SessionData initialized: " + accessToken); + + if (!usersRepository.findById(userId).isPresent()) { + User user = new User(); + user.setId(userId); + usersRepository.save(user); + } + } + + public String getUserId() { + return userId; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java new file mode 100644 index 0000000..7586fd8 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java @@ -0,0 +1,47 @@ +package it.inaf.ia2.gms.controller; + +import it.inaf.ia2.gms.authn.SessionData; +import it.inaf.ia2.gms.model.CreateGroupRequest; +import it.inaf.ia2.gms.model.GroupsModelRequest; +import it.inaf.ia2.gms.model.GroupsModelResponse; +import it.inaf.ia2.gms.persistence.model.Group; +import it.inaf.ia2.gms.persistence.model.User; +import it.inaf.ia2.gms.service.GroupsModelService; +import it.inaf.ia2.gms.service.GroupsService; +import it.inaf.ia2.gms.service.UsersService; +import javax.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class GroupsController { + + @Autowired + private SessionData session; + + private UsersService usersService; + + @Autowired + private GroupsModelService groupsModelService; + + @Autowired + private GroupsService groupsService; + + @GetMapping(value = "/groups", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public GroupsModelResponse getGroupsModelResponse(@Valid @RequestBody GroupsModelRequest groupsModelRequest) { + return groupsModelService.getGroupsModel(groupsModelRequest, session.getUserId()); + } + + @PostMapping(value = "/group", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity<String> createGroup(@Valid @RequestBody CreateGroupRequest createGroupRequest) { + User user = usersService.getUserById(session.getUserId()); + Group group = groupsService.addGroup(createGroupRequest.getParentGroupId(), createGroupRequest.getGroupName(), user); + return new ResponseEntity<>(group.getId(), HttpStatus.CREATED); + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsModelController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsModelController.java deleted file mode 100644 index 56a344e..0000000 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsModelController.java +++ /dev/null @@ -1,17 +0,0 @@ -package it.inaf.ia2.gms.controller; - -import it.inaf.ia2.gms.controller.model.GroupsModelRequest; -import it.inaf.ia2.gms.controller.model.GroupsModelResponse; -import javax.validation.Valid; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class GroupsModelController { - - @GetMapping - public GroupsModelResponse getGroupsModelResponse(@Valid @RequestBody GroupsModelRequest groupsModelRequest) { - return null; - } -} diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelRequest.java b/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelRequest.java deleted file mode 100644 index 8a5c00a..0000000 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelRequest.java +++ /dev/null @@ -1,14 +0,0 @@ -package it.inaf.ia2.gms.controller.model; - -public class GroupsModelRequest extends BaseModelRequest { - - private String groupId; - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } -} diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelResponse.java b/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelResponse.java deleted file mode 100644 index 8bc0046..0000000 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelResponse.java +++ /dev/null @@ -1,6 +0,0 @@ -package it.inaf.ia2.gms.controller.model; - -public class GroupsModelResponse extends BaseModelResponse { - - -} diff --git a/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java b/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java new file mode 100644 index 0000000..2b07d8c --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java @@ -0,0 +1,8 @@ +package it.inaf.ia2.gms.exception; + +public class UnauthorizedException extends RuntimeException { + + public UnauthorizedException(String message) { + super(message); + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelRequest.java similarity index 82% rename from gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelRequest.java rename to gms/src/main/java/it/inaf/ia2/gms/model/BaseModelRequest.java index 3e93b83..1330e69 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelRequest.java +++ b/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelRequest.java @@ -1,4 +1,4 @@ -package it.inaf.ia2.gms.controller.model; +package it.inaf.ia2.gms.model; public abstract class BaseModelRequest { diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelResponse.java b/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelResponse.java similarity index 89% rename from gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelResponse.java rename to gms/src/main/java/it/inaf/ia2/gms/model/BaseModelResponse.java index 98c6b2a..b8999c5 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelResponse.java +++ b/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelResponse.java @@ -1,4 +1,4 @@ -package it.inaf.ia2.gms.controller.model; +package it.inaf.ia2.gms.model; public abstract class BaseModelResponse { diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java new file mode 100644 index 0000000..a62c852 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java @@ -0,0 +1,23 @@ +package it.inaf.ia2.gms.model; + +public class CreateGroupRequest { + + private String parentGroupId; + private String groupName; + + public String getParentGroupId() { + return parentGroupId; + } + + public void setParentGroupId(String parentGroupId) { + this.parentGroupId = parentGroupId; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupBreadcrumb.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupBreadcrumb.java new file mode 100644 index 0000000..8c16742 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupBreadcrumb.java @@ -0,0 +1,23 @@ +package it.inaf.ia2.gms.model; + +public class GroupBreadcrumb { + + private String groupId; + private String groupName; + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java new file mode 100644 index 0000000..6ca71a1 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java @@ -0,0 +1,83 @@ +package it.inaf.ia2.gms.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class GroupNode { + + private String groupId; + private String groupName; + private Set<Permission> permissions; + private boolean hasChildren; + + public GroupNode() { + permissions = new HashSet<>(); + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public List<Permission> getPermissions() { + return Collections.unmodifiableList(new ArrayList<>(permissions)); + } + + public void setPermissions(Set<Permission> permissions) { + this.permissions = permissions; + } + + public void addPermission(Permission permission) { + + // remove implied permissions + switch (permission) { + case ADMIN: + permissions.add(permission); + permissions.remove(Permission.MANAGE_MEMBERS); + permissions.remove(Permission.VIEW_MEMBERS); + permissions.remove(Permission.TRAVERSE); + break; + case MANAGE_MEMBERS: + if (!permissions.contains(Permission.ADMIN)) { + permissions.add(permission); + permissions.remove(Permission.VIEW_MEMBERS); + permissions.remove(Permission.TRAVERSE); + } + break; + case VIEW_MEMBERS: + if (!permissions.contains(Permission.ADMIN) + && !permissions.contains(Permission.MANAGE_MEMBERS)) { + permissions.add(permission); + permissions.remove(Permission.TRAVERSE); + } + break; + case TRAVERSE: + if (permissions.isEmpty()) { + permissions.add(permission); + } + break; + } + } + + public boolean isHasChildren() { + return hasChildren; + } + + public void setHasChildren(boolean hasChildren) { + this.hasChildren = hasChildren; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java new file mode 100644 index 0000000..9127d87 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java @@ -0,0 +1,32 @@ +package it.inaf.ia2.gms.model; + +public class GroupsModelRequest extends BaseModelRequest { + + private String groupId; + private int paginatorPage; + private int paginatorPageSize; + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public int getPaginatorPage() { + return paginatorPage; + } + + public void setPaginatorPage(int paginatorPage) { + this.paginatorPage = paginatorPage; + } + + public int getPaginatorPageSize() { + return paginatorPageSize; + } + + public void setPaginatorPageSize(int paginatorPageSize) { + this.paginatorPageSize = paginatorPageSize; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelResponse.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelResponse.java new file mode 100644 index 0000000..415ff5c --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelResponse.java @@ -0,0 +1,53 @@ +package it.inaf.ia2.gms.model; + +import java.util.List; + +public class GroupsModelResponse extends BaseModelResponse { + + private List<GroupBreadcrumb> breadcrumbs; + private PaginatedData<GroupNode> groupsPanel; + private PaginatedData<RapUser> membersPanel; + private PaginatedData<UserPermission> permissionsPanel; + // current group permissions + private List<Permission> permissions; + + public List<GroupBreadcrumb> getBreadcrumbs() { + return breadcrumbs; + } + + public void setBreadcrumbs(List<GroupBreadcrumb> breadcrumbs) { + this.breadcrumbs = breadcrumbs; + } + + public PaginatedData<GroupNode> getGroupsPanel() { + return groupsPanel; + } + + public void setGroupsPanel(PaginatedData<GroupNode> groupsPanel) { + this.groupsPanel = groupsPanel; + } + + public PaginatedData<RapUser> getMembersPanel() { + return membersPanel; + } + + public void setMembersPanel(PaginatedData<RapUser> membersPanel) { + this.membersPanel = membersPanel; + } + + public PaginatedData<UserPermission> getPermissionsPanel() { + return permissionsPanel; + } + + public void setPermissionsPanel(PaginatedData<UserPermission> permissionsPanel) { + this.permissionsPanel = permissionsPanel; + } + + public List<Permission> getPermissions() { + return permissions; + } + + public void setPermissions(List<Permission> permissions) { + this.permissions = permissions; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/Identity.java b/gms/src/main/java/it/inaf/ia2/gms/model/Identity.java new file mode 100644 index 0000000..c7031e5 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/Identity.java @@ -0,0 +1,23 @@ +package it.inaf.ia2.gms.model; + +public class Identity { + + private IdentityType type; + private String email; + + public IdentityType getType() { + return type; + } + + public void setType(IdentityType type) { + this.type = type; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/IdentityType.java b/gms/src/main/java/it/inaf/ia2/gms/model/IdentityType.java new file mode 100644 index 0000000..f2a5655 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/IdentityType.java @@ -0,0 +1,12 @@ +package it.inaf.ia2.gms.model; + +public enum IdentityType { + + EDU_GAIN, + X509, + ORCID, + GOOGLE, + LINKEDIN, + FACEBOOK, + LOCAL_IDP +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/PaginatedData.java b/gms/src/main/java/it/inaf/ia2/gms/model/PaginatedData.java new file mode 100644 index 0000000..5ea07f7 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/PaginatedData.java @@ -0,0 +1,83 @@ +package it.inaf.ia2.gms.model; + +import java.util.ArrayList; +import java.util.List; + +public class PaginatedData<T> { + + // how many page index links have to be shown + private static final int LINKS_SIZE = 5; + + private final List<T> items; + private final int currentPage; + private final List<Integer> links; + private final int totalItems; + private final int pageSize; + private final int totalPages; + private final boolean hasPreviousPages; + private final boolean hasFollowingPages; + + public PaginatedData(List<T> allItems, int page, int pageSize) { + + totalItems = allItems.size(); + this.currentPage = page; + this.pageSize = pageSize; + + totalPages = (int) Math.ceil((double) totalItems / pageSize); + + if (allItems.isEmpty()) { + items = allItems; + } else { + int firstElementIndex = (currentPage - 1) * pageSize; + int lastElementIndex = Math.min(currentPage * pageSize - 1, totalItems - 1); + items = new ArrayList<>(allItems.subList(firstElementIndex, lastElementIndex)); + } + + links = new ArrayList<>(); + int start = currentPage - LINKS_SIZE / 2; + if (start > 1 && start + LINKS_SIZE > totalPages) { + start = totalPages - LINKS_SIZE + 1; + } + if (start < 1) { + start = 1; + } + int i = start; + hasPreviousPages = start > 1; + while (i < start + LINKS_SIZE && i <= totalPages) { + links.add(i++); + } + hasFollowingPages = totalPages > i; + } + + public List<T> getItems() { + return items; + } + + public int getCurrentPage() { + return currentPage; + } + + public List<Integer> getLinks() { + return links; + } + + public int getTotalItems() { + return totalItems; + } + + public int getPageSize() { + return pageSize; + } + + public int getTotalPages() { + return totalPages; + } + + public boolean isHasPreviousPages() { + return hasPreviousPages; + } + + public boolean isHasFollowingPages() { + return hasFollowingPages; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/Permission.java b/gms/src/main/java/it/inaf/ia2/gms/model/Permission.java new file mode 100644 index 0000000..be00ef7 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/Permission.java @@ -0,0 +1,9 @@ +package it.inaf.ia2.gms.model; + +public enum Permission { + + ADMIN, + MANAGE_MEMBERS, + VIEW_MEMBERS, + TRAVERSE +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/RapUser.java b/gms/src/main/java/it/inaf/ia2/gms/model/RapUser.java new file mode 100644 index 0000000..e58f27d --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/RapUser.java @@ -0,0 +1,34 @@ +package it.inaf.ia2.gms.model; + +import java.util.List; + +public class RapUser { + + private String id; + private String displayName; + private List<Identity> identities; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public List<Identity> getIdentities() { + return identities; + } + + public void setIdentities(List<Identity> identities) { + this.identities = identities; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/UserPermission.java b/gms/src/main/java/it/inaf/ia2/gms/model/UserPermission.java new file mode 100644 index 0000000..66ab93a --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/UserPermission.java @@ -0,0 +1,23 @@ +package it.inaf.ia2.gms.model; + +public class UserPermission { + + private RapUser user; + private Permission permission; + + public RapUser getUser() { + return user; + } + + public void setUser(RapUser user) { + this.user = user; + } + + public Permission getPermission() { + return permission; + } + + public void setPermission(Permission permission) { + this.permission = permission; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipRepository.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipRepository.java new file mode 100644 index 0000000..cd33692 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipRepository.java @@ -0,0 +1,16 @@ +package it.inaf.ia2.gms.persistence; + +import it.inaf.ia2.gms.persistence.model.Membership; +import it.inaf.ia2.gms.persistence.model.MembershipId; +import it.inaf.ia2.gms.persistence.model.User; +import java.util.List; +import javax.transaction.Transactional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +@Transactional +public interface MembershipRepository extends JpaRepository<Membership, MembershipId> { + + List<Membership> findBy_user(User user); +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsRepository.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsRepository.java index 9b8b50a..f3e19f1 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsRepository.java +++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsRepository.java @@ -1,8 +1,8 @@ package it.inaf.ia2.gms.persistence; import it.inaf.ia2.gms.persistence.model.User; -import it.inaf.ia2.gms.persistence.model.UserGroup; -import it.inaf.ia2.gms.persistence.model.UserGroupId; +import it.inaf.ia2.gms.persistence.model.UserGroupPermission; +import it.inaf.ia2.gms.persistence.model.UserGroupPermissionId; import java.util.List; import javax.transaction.Transactional; import org.springframework.data.jpa.repository.JpaRepository; @@ -10,7 +10,7 @@ import org.springframework.stereotype.Repository; @Repository @Transactional -public interface PermissionsRepository extends JpaRepository<UserGroup, UserGroupId> { +public interface PermissionsRepository extends JpaRepository<UserGroupPermission, UserGroupPermissionId> { - List<UserGroup> findBy_user(User user); + List<UserGroupPermission> findBy_user(User user); } diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/UserGroupsFromParentSpecification.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/UserGroupsFromParentSpecification.java deleted file mode 100644 index 6a99106..0000000 --- a/gms/src/main/java/it/inaf/ia2/gms/persistence/UserGroupsFromParentSpecification.java +++ /dev/null @@ -1,5 +0,0 @@ -package it.inaf.ia2.gms.persistence; - -public class UserGroupsFromParentSpecification { - -} diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Group.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Group.java index 92cb5d0..504093e 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Group.java +++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Group.java @@ -18,6 +18,7 @@ import javax.persistence.Table; public class Group { @Id + @Column(length = 50) private String id; @Column(nullable = false) @@ -29,10 +30,10 @@ public class Group { @OneToMany(mappedBy = "parentGroup", fetch = FetchType.LAZY) @OrderBy("name ASC") - private List<Group> groupsMembers; + private List<Group> childrenGroups; public Group() { - groupsMembers = new ArrayList<>(); + childrenGroups = new ArrayList<>(); } public String getId() { @@ -59,12 +60,12 @@ public class Group { this.parentGroup = parentGroup; } - public List<Group> getGroupsMembers() { - return groupsMembers; + public List<Group> getChildrenGroups() { + return childrenGroups; } - public void setGroupsMembers(List<Group> groupsMembers) { - this.groupsMembers = groupsMembers; + public void setChildrenGroups(List<Group> childrenGroups) { + this.childrenGroups = childrenGroups; } @Override diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Membership.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Membership.java new file mode 100644 index 0000000..69b282f --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Membership.java @@ -0,0 +1,44 @@ +package it.inaf.ia2.gms.persistence.model; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * Note: if necessary this class could contain also a role (specific for the + * application using the GMS). Currently that is not implemented. + */ +@Entity +@IdClass(MembershipId.class) +@Table(name = "gms_membership") +public class Membership { + + @Id + @ManyToOne + @JoinColumn(name = "user_id", referencedColumnName = "id", columnDefinition = "varchar(50)") + private User _user; + + @Id + @ManyToOne + @JoinColumn(name = "group_id", referencedColumnName = "id", columnDefinition = "varchar(50)") + private Group _group; + + public User getUser() { + return _user; + } + + public void setUser(User _user) { + this._user = _user; + } + + public Group getGroup() { + return _group; + } + + public void setGroup(Group _group) { + this._group = _group; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipId.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipId.java new file mode 100644 index 0000000..c0b1bea --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipId.java @@ -0,0 +1,39 @@ +package it.inaf.ia2.gms.persistence.model; + +import java.io.Serializable; +import java.util.Objects; + +public class MembershipId implements Serializable { + + String _user; + String _group; + + @Override + public int hashCode() { + int hash = 7; + hash = 47 * hash + Objects.hashCode(this._user); + hash = 47 * hash + Objects.hashCode(this._group); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final MembershipId other = (MembershipId) obj; + if (!Objects.equals(this._user, other._user)) { + return false; + } + if (!Objects.equals(this._group, other._group)) { + return false; + } + return true; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/User.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/User.java index 82238da..9d2c6d7 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/User.java +++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/User.java @@ -1,5 +1,6 @@ package it.inaf.ia2.gms.persistence.model; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @@ -9,6 +10,7 @@ import javax.persistence.Table; public class User { @Id + @Column(length = 50) private String id; public String getId() { diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroup.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermission.java similarity index 80% rename from gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroup.java rename to gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermission.java index 8bfb1fa..3eebc85 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroup.java +++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermission.java @@ -1,6 +1,6 @@ package it.inaf.ia2.gms.persistence.model; -import it.inaf.ia2.gms.service.model.Permission; +import it.inaf.ia2.gms.model.Permission; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; @@ -17,23 +17,23 @@ import javax.persistence.Table; * keyword. */ @Entity -@IdClass(UserGroupId.class) -@Table(name = "gms_user_group") -public class UserGroup { +@IdClass(UserGroupPermissionId.class) +@Table(name = "gms_user_group_permission") +public class UserGroupPermission { @Id @ManyToOne - @JoinColumn(name = "user_id", referencedColumnName = "id") + @JoinColumn(name = "user_id", referencedColumnName = "id", columnDefinition = "varchar(50)") private User _user; @Id @ManyToOne - @JoinColumn(name = "group_id", referencedColumnName = "id") + @JoinColumn(name = "group_id", referencedColumnName = "id", columnDefinition = "varchar(50)") private Group _group; @Id @Enumerated(EnumType.STRING) - @Column(name = "permission") + @Column(name = "permission", length = 50) private Permission permission; public User getUser() { @@ -80,7 +80,7 @@ public class UserGroup { if (getClass() != obj.getClass()) { return false; } - final UserGroup other = (UserGroup) obj; + final UserGroupPermission other = (UserGroupPermission) obj; if (!Objects.equals(this._user, other._user)) { return false; } diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupId.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermissionId.java similarity index 85% rename from gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupId.java rename to gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermissionId.java index c546cae..c036bfd 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupId.java +++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermissionId.java @@ -1,10 +1,10 @@ package it.inaf.ia2.gms.persistence.model; -import it.inaf.ia2.gms.service.model.Permission; +import it.inaf.ia2.gms.model.Permission; import java.io.Serializable; import java.util.Objects; -public class UserGroupId implements Serializable { +public class UserGroupPermissionId implements Serializable { String _user; String _group; @@ -30,7 +30,7 @@ public class UserGroupId implements Serializable { if (getClass() != obj.getClass()) { return false; } - final UserGroupId other = (UserGroupId) obj; + final UserGroupPermissionId other = (UserGroupPermissionId) obj; if (!Objects.equals(this._user, other._user)) { return false; } diff --git a/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java b/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java new file mode 100644 index 0000000..a7eff0d --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java @@ -0,0 +1,8 @@ +package it.inaf.ia2.gms.rap; + +import org.springframework.stereotype.Component; + +@Component +public class RapClient { + +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelService.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelService.java new file mode 100644 index 0000000..1673fd6 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelService.java @@ -0,0 +1,56 @@ +package it.inaf.ia2.gms.service; + +import it.inaf.ia2.gms.model.GroupBreadcrumb; +import it.inaf.ia2.gms.model.GroupNode; +import it.inaf.ia2.gms.model.GroupsModelRequest; +import it.inaf.ia2.gms.model.GroupsModelResponse; +import it.inaf.ia2.gms.model.PaginatedData; +import it.inaf.ia2.gms.persistence.model.Group; +import it.inaf.ia2.gms.persistence.model.User; +import java.util.ArrayList; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class GroupsModelService { + + @Autowired + private UsersService usersService; + + @Autowired + private GroupsService groupsService; + + public GroupsModelResponse getGroupsModel(GroupsModelRequest request, String userId) { + + User user = usersService.getUserById(userId); + Group group = groupsService.getGroupById(request.getGroupId()); + + GroupsModelResponse response = new GroupsModelResponse(); + + response.setPage("groups"); + + List<GroupNode> groupNodes = groupsService.getSubgroups(group, user); + + response.setGroupsPanel(new PaginatedData<>(groupNodes, request.getPaginatorPage(), request.getPaginatorPageSize())); + + response.setPermissions(groupsService.getPermissions(group, user)); + response.setBreadcrumbs(buildBreadcrumbs(new ArrayList<>(), group)); + + return response; + } + + private List<GroupBreadcrumb> buildBreadcrumbs(List<GroupBreadcrumb> breadcrumbs, Group group) { + + GroupBreadcrumb bc = new GroupBreadcrumb(); + bc.setGroupId(group.getId()); + bc.setGroupName(group.getName()); + breadcrumbs.add(bc); + + if (group.getParentGroup() == null) { + return breadcrumbs; + } else { + return buildBreadcrumbs(breadcrumbs, group.getParentGroup()); + } + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java index f2affaf..9a25817 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java +++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java @@ -1,20 +1,21 @@ package it.inaf.ia2.gms.service; import it.inaf.ia2.gms.exception.BadRequestException; +import it.inaf.ia2.gms.exception.UnauthorizedException; import it.inaf.ia2.gms.persistence.GroupsRepository; -import it.inaf.ia2.gms.persistence.UsersRepository; +import it.inaf.ia2.gms.persistence.MembershipRepository; import it.inaf.ia2.gms.persistence.model.Group; import it.inaf.ia2.gms.persistence.model.User; -import it.inaf.ia2.gms.persistence.model.UserGroup; +import it.inaf.ia2.gms.persistence.model.UserGroupPermission; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import it.inaf.ia2.gms.persistence.PermissionsRepository; -import it.inaf.ia2.gms.service.model.GroupNode; -import it.inaf.ia2.gms.service.model.Permission; +import it.inaf.ia2.gms.persistence.model.Membership; +import it.inaf.ia2.gms.model.GroupNode; +import it.inaf.ia2.gms.model.Permission; import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -24,15 +25,16 @@ public class GroupsService { public static final String ROOT = "ROOT"; private final GroupsRepository groupsRepository; - private final UsersRepository usersRepository; private final PermissionsRepository permissionsRepository; + private final MembershipRepository membershipRepository; @Autowired public GroupsService(GroupsRepository groupsRepository, - UsersRepository usersRepository, PermissionsRepository permissionsRepository) { + PermissionsRepository permissionsRepository, + MembershipRepository membershipRepository) { this.groupsRepository = groupsRepository; - this.usersRepository = usersRepository; this.permissionsRepository = permissionsRepository; + this.membershipRepository = membershipRepository; createRootIfNecessary(); } @@ -45,90 +47,144 @@ public class GroupsService { } } - public Group addGroup(String parentId, String groupName) { + public Group addGroup(String parentId, String groupName, User user) { + Group parent = getGroupById(parentId); + + if (parent.getChildrenGroups().stream() + .anyMatch(g -> g.getName().equals(groupName))) { + throw new BadRequestException("There is already a group named " + groupName); + } + + if (!getPermissions(parent, user).contains(Permission.ADMIN)) { + throw new UnauthorizedException("Missing admin privileges"); + } + Group group = new Group(); group.setId(UUID.randomUUID().toString()); group.setName(groupName); group.setParentGroup(parent); - parent.getGroupsMembers().add(group); + parent.getChildrenGroups().add(group); group = groupsRepository.save(group); groupsRepository.save(parent); return group; } - public List<Group> getSubgroups(String parentId) { - Group parent = getGroupById(parentId); - return groupsRepository.findByParentGroup(parent); - } + public List<GroupNode> getSubgroups(Group parent, User user) { - public List<GroupNode> getSubgroups(String parentGroupId, String userId) { + List<UserGroupPermission> permissions = getAllPermissions(user); + + Map<String, GroupNode> nodesMap = new HashMap<>(); + + for (Group childGroup : parent.getChildrenGroups()) { + addGroupNodeToMap(childGroup, permissions, nodesMap); + } + + List<GroupNode> nodes = new ArrayList<>(nodesMap.values()); + + // Sort by group name + nodes.sort((n1, n2) -> n1.getGroupName().compareTo(n2.getGroupName())); + + return nodes; + } - User user = usersRepository.findById(userId) - .orElseThrow(() -> new BadRequestException("User " + userId + " not found")); + public List<Permission> getPermissions(Group group, User user) { - Group parent = getGroupById(parentGroupId); + List<UserGroupPermission> permissions = getAllPermissions(user); + Map<String, GroupNode> nodesMap = new HashMap<>(); - List<UserGroup> permissions = permissionsRepository.findBy_user(user); + addGroupNodeToMap(group, permissions, nodesMap); - Map<String, GroupNode> nodes = new LinkedHashMap<>(); + GroupNode groupNode = nodesMap.get(group.getId()); + if (groupNode == null) { + return null; + } + return groupNode.getPermissions(); + } - for (Group childGroup : parent.getGroupsMembers()) { + /** + * Returns all the permissions, including also the calculated ones (the ones + * derived from the memberships). + */ + private List<UserGroupPermission> getAllPermissions(User user) { + + // explicit permissions + List<UserGroupPermission> permissions = permissionsRepository.findBy_user(user); + + List<UserGroupPermission> implicitPermissions = new ArrayList<>(); + for (Membership membership : membershipRepository.findBy_user(user)) { + UserGroupPermission userGroupPermission = new UserGroupPermission(); + userGroupPermission.setUser(user); + userGroupPermission.setGroup(membership.getGroup()); + userGroupPermission.setPermission(Permission.TRAVERSE); + implicitPermissions.add(userGroupPermission); + } - Iterator<UserGroup> ite = permissions.iterator(); - while (ite.hasNext()) { + permissions.addAll(implicitPermissions); + return permissions; + } - UserGroup permission = ite.next(); + private void addGroupNodeToMap(Group group, List<UserGroupPermission> permissions, Map<String, GroupNode> nodesMap) { - boolean isChild = false; - if (permission.getGroup().getId().equals(childGroup.getId()) - || (isChild = isChildOf(permission.getGroup(), parentGroupId))) { + for (UserGroupPermission permission : permissions) { - GroupNode node = nodes.get(childGroup.getId()); - if (node == null) { - node = getGroupNode(childGroup); - } + boolean isChild = false; + if (permission.getGroup().getId().equals(group.getId()) + || (isChild = isChildOf(permission.getGroup(), group)) + || (isParentOf(permission.getGroup(), group))) { - if (isChild) { - // Traversal only - node.getPermissions().add(Permission.READ); - } else { - // Direct permission - node.getPermissions().add(permission.getPermission()); - } + GroupNode node = nodesMap.get(group.getId()); + if (node == null) { + node = getGroupNode(group); + } - nodes.put(childGroup.getId(), node); - ite.remove(); + if (isChild) { + // Traversal only + node.addPermission(Permission.TRAVERSE); + } else { + // Direct permission or permission inherited from parent + node.addPermission(permission.getPermission()); } + + nodesMap.put(group.getId(), node); } } - - // TODO: pagination - return new ArrayList<>(nodes.values()); } private GroupNode getGroupNode(Group group) { GroupNode node = new GroupNode(); node.setGroupId(group.getId()); node.setGroupName(group.getName()); - node.setHasChildren(!group.getGroupsMembers().isEmpty()); + node.setHasChildren(!group.getChildrenGroups().isEmpty()); return node; } - private boolean isChildOf(Group group, String parentGroupId) { + private boolean isChildOf(Group group, Group possibleParent) { Group parent = group.getParentGroup(); if (parent == null) { // ROOT has no parent return false; } - if (parentGroupId.equals(parent.getId())) { + if (possibleParent.getId().equals(parent.getId())) { + return true; + } + // recursive call to parent group + return isChildOf(parent, possibleParent); + } + + private boolean isParentOf(Group group, Group possibleChild) { + Group parent = possibleChild.getParentGroup(); + if (parent == null) { + return false; + } + if (parent.getId().equals(group.getId())) { return true; } // recursive call to parent group - return isChildOf(parent, parentGroupId); + return isParentOf(group, parent); } - private Group getGroupById(String groupId) { + public Group getGroupById(String groupId) { return groupsRepository.findById(groupId) .orElseThrow(() -> new BadRequestException("Group " + groupId + " not found")); } diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/MainModelService.java b/gms/src/main/java/it/inaf/ia2/gms/service/MainModelService.java deleted file mode 100644 index cf11a1a..0000000 --- a/gms/src/main/java/it/inaf/ia2/gms/service/MainModelService.java +++ /dev/null @@ -1,5 +0,0 @@ -package it.inaf.ia2.gms.service; - -public class MainModelService { - -} diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/UsersService.java b/gms/src/main/java/it/inaf/ia2/gms/service/UsersService.java new file mode 100644 index 0000000..3cf6a99 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/service/UsersService.java @@ -0,0 +1,24 @@ +package it.inaf.ia2.gms.service; + +import it.inaf.ia2.gms.exception.BadRequestException; +import it.inaf.ia2.gms.persistence.UsersRepository; +import it.inaf.ia2.gms.persistence.model.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class UsersService { + + private final UsersRepository usersRepository; + + @Autowired + public UsersService(UsersRepository usersRepository) { + this.usersRepository = usersRepository; + } + + public User getUserById(String userId) { + + return usersRepository.findById(userId) + .orElseThrow(() -> new BadRequestException("User " + userId + " not found")); + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/model/GroupNode.java b/gms/src/main/java/it/inaf/ia2/gms/service/model/GroupNode.java deleted file mode 100644 index 920ec3d..0000000 --- a/gms/src/main/java/it/inaf/ia2/gms/service/model/GroupNode.java +++ /dev/null @@ -1,48 +0,0 @@ -package it.inaf.ia2.gms.service.model; - -import java.util.ArrayList; -import java.util.List; - -public class GroupNode { - - private String groupId; - private String groupName; - private List<Permission> permissions; - private boolean hasChildren; - - public GroupNode() { - permissions = new ArrayList<>(); - } - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public String getGroupName() { - return groupName; - } - - public void setGroupName(String groupName) { - this.groupName = groupName; - } - - public List<Permission> getPermissions() { - return permissions; - } - - public void setPermissions(List<Permission> permissions) { - this.permissions = permissions; - } - - public boolean isHasChildren() { - return hasChildren; - } - - public void setHasChildren(boolean hasChildren) { - this.hasChildren = hasChildren; - } -} diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/model/Permission.java b/gms/src/main/java/it/inaf/ia2/gms/service/model/Permission.java deleted file mode 100644 index 899a1b4..0000000 --- a/gms/src/main/java/it/inaf/ia2/gms/service/model/Permission.java +++ /dev/null @@ -1,8 +0,0 @@ -package it.inaf.ia2.gms.service.model; - -public enum Permission { - - ADMIN, - MANAGE_MEMBERS, - READ -} diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsRepositoryTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsRepositoryTest.java deleted file mode 100644 index c5afa9b..0000000 --- a/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsRepositoryTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package it.inaf.ia2.gms.persistence; - -import it.inaf.ia2.gms.persistence.model.Group; -import it.inaf.ia2.gms.persistence.model.User; -import java.util.Arrays; -import static org.junit.Assert.assertEquals; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.test.context.junit4.SpringRunner; - -@DataJpaTest -@AutoConfigureTestDatabase -@RunWith(SpringRunner.class) -public class GroupsRepositoryTest { - - @Autowired - private UsersRepository usersRepository; - - @Autowired - private GroupsRepository groupsRepository; - - @Test - public void testCreateGroup() { - - User user1 = new User(); - user1.setId("user1"); - user1 = usersRepository.save(user1); - User user2 = new User(); - user2.setId("user2"); - user2 = usersRepository.save(user2); - - Group group = new Group(); - group.setId("parent-group"); - group.setName("Parent"); - - // Test cascade persist - Group childGroup = new Group(); - childGroup.setId("child-group"); - childGroup.setName("Child 1"); - - group.setGroupsMembers(Arrays.asList(childGroup)); - - group = groupsRepository.save(group); - - // Test cascade merge - Group childGroup2 = new Group(); - childGroup2.setId("child-group-2"); - childGroup2.setName("Child 2"); - group.getGroupsMembers().add(childGroup2); - groupsRepository.save(group); - - // Reload - group = groupsRepository.findById("parent-group").get(); - - assertEquals("Parent", group.getName()); - assertEquals(2, group.getGroupsMembers().size()); - assertEquals("Child 1", group.getGroupsMembers().get(0).getName()); - assertEquals("Child 2", group.getGroupsMembers().get(1).getName()); - } -} diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java index 89f7f86..fdf493d 100644 --- a/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java +++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java @@ -1,13 +1,16 @@ package it.inaf.ia2.gms.persistence; import it.inaf.ia2.gms.persistence.model.Group; +import it.inaf.ia2.gms.persistence.model.Membership; import it.inaf.ia2.gms.persistence.model.User; -import it.inaf.ia2.gms.persistence.model.UserGroup; +import it.inaf.ia2.gms.persistence.model.UserGroupPermission; import it.inaf.ia2.gms.service.GroupsService; -import it.inaf.ia2.gms.service.model.GroupNode; -import it.inaf.ia2.gms.service.model.Permission; +import it.inaf.ia2.gms.model.GroupNode; +import it.inaf.ia2.gms.model.Permission; import java.util.List; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -29,30 +32,126 @@ public class NestedGroupsIntegrationTest { @Autowired private PermissionsRepository permissionsRepository; + @Autowired + private MembershipRepository membershipRepository; + @Test public void testNestedGroupRetrieval() { - GroupsService groupsService = new GroupsService(groupsRepository, usersRepository, permissionsRepository); + GroupsService groupsService = new GroupsService(groupsRepository, + permissionsRepository, membershipRepository); // Create user User user = new User(); user.setId("user"); user = usersRepository.save(user); + // Test super admin + UserGroupPermission superAdminPermission = new UserGroupPermission(); + superAdminPermission.setUser(user); + superAdminPermission.setGroup(groupsRepository.findById(GroupsService.ROOT).get()); + superAdminPermission.setPermission(Permission.ADMIN); + permissionsRepository.save(superAdminPermission); + // Setup groups - Group lbt = groupsService.addGroup(GroupsService.ROOT, "LBT"); - Group lbtInaf = groupsService.addGroup(lbt.getId(), "INAF"); - Group lbtInafProgram = groupsService.addGroup(lbtInaf.getId(), "P1"); - - // Setup permissions - UserGroup permission = new UserGroup(); - permission.setUser(user); - permission.setGroup(lbtInafProgram); - permission.setPermission(Permission.MANAGE_MEMBERS); - permissionsRepository.save(permission); - - List<GroupNode> groupNodes = groupsService.getSubgroups(GroupsService.ROOT, user.getId()); + Group root = groupsService.getGroupById(GroupsService.ROOT); + Group lbt = groupsService.addGroup(GroupsService.ROOT, "LBT", user); + Group tng = groupsService.addGroup(GroupsService.ROOT, "TNG", user); + Group radio = groupsService.addGroup(GroupsService.ROOT, "Radio", user); + Group lbtInaf = groupsService.addGroup(lbt.getId(), "INAF", user); + Group lbtInafProgram1 = groupsService.addGroup(lbtInaf.getId(), "P1", user); + Group lbtInafProgram2 = groupsService.addGroup(lbtInaf.getId(), "P2", user); + + // Test super admin - level 0 (ROOT) + List<GroupNode> groupNodes = groupNodes = groupsService.getSubgroups(root, user); + assertEquals(3, groupNodes.size()); + GroupNode lbtGN = groupNodes.get(0); + assertEquals("LBT", lbtGN.getGroupName()); + assertEquals(1, lbtGN.getPermissions().size()); + assertEquals(Permission.ADMIN, lbtGN.getPermissions().get(0)); + assertTrue(lbtGN.isHasChildren()); + GroupNode radioGN = groupNodes.get(1); + assertEquals("Radio", radioGN.getGroupName()); + assertEquals(Permission.ADMIN, radioGN.getPermissions().get(0)); + assertFalse(radioGN.isHasChildren()); + GroupNode tngGN = groupNodes.get(2); + assertEquals("TNG", tngGN.getGroupName()); + assertEquals(Permission.ADMIN, tngGN.getPermissions().get(0)); + assertFalse(tngGN.isHasChildren()); + + // Test super admin - level 1 + groupNodes = groupsService.getSubgroups(lbt, user); assertEquals(1, groupNodes.size()); - } + GroupNode INAFGN = groupNodes.get(0); + assertEquals("INAF", INAFGN.getGroupName()); + assertEquals(Permission.ADMIN, INAFGN.getPermissions().get(0)); + assertTrue(INAFGN.isHasChildren()); + + // Test super admin - level 2 + groupNodes = groupsService.getSubgroups(lbtInaf, user); + assertEquals(2, groupNodes.size()); + GroupNode p1 = groupNodes.get(0); + assertEquals("P1", p1.getGroupName()); + assertEquals(1, p1.getPermissions().size()); + assertEquals(Permission.ADMIN, p1.getPermissions().get(0)); + assertFalse(p1.isHasChildren()); + GroupNode p2 = groupNodes.get(1); + assertEquals("P2", p2.getGroupName()); + assertEquals(Permission.ADMIN, p2.getPermissions().get(0)); + assertFalse(p2.isHasChildren()); + + // Setup lower permissions + permissionsRepository.delete(superAdminPermission); + UserGroupPermission p1Permission = new UserGroupPermission(); + p1Permission.setUser(user); + p1Permission.setGroup(lbtInafProgram1); + p1Permission.setPermission(Permission.MANAGE_MEMBERS); + permissionsRepository.save(p1Permission); + + UserGroupPermission lbtPermission = new UserGroupPermission(); + lbtPermission.setUser(user); + lbtPermission.setGroup(lbtInaf); + lbtPermission.setPermission(Permission.VIEW_MEMBERS); + permissionsRepository.save(lbtPermission); + + // Setup membership + Membership membership = new Membership(); + membership.setUser(user); + membership.setGroup(radio); + membershipRepository.save(membership); + + // Check level 0 (ROOT) + groupNodes = groupsService.getSubgroups(root, user); + assertEquals(2, groupNodes.size()); + lbtGN = groupNodes.get(0); + assertEquals("LBT", lbtGN.getGroupName()); + assertEquals(Permission.TRAVERSE, lbtGN.getPermissions().get(0)); + assertTrue(lbtGN.isHasChildren()); + radioGN = groupNodes.get(1); + assertEquals("Radio", radioGN.getGroupName()); + assertEquals(Permission.TRAVERSE, radioGN.getPermissions().get(0)); + assertFalse(radioGN.isHasChildren()); + + // Check level 1 + groupNodes = groupsService.getSubgroups(lbt, user); + assertEquals(1, groupNodes.size()); + INAFGN = groupNodes.get(0); + assertEquals("INAF", INAFGN.getGroupName()); + assertEquals(Permission.VIEW_MEMBERS, INAFGN.getPermissions().get(0)); + assertTrue(INAFGN.isHasChildren()); + + // Check level 2 + groupNodes = groupsService.getSubgroups(lbtInaf, user); + assertEquals(2, groupNodes.size()); + p1 = groupNodes.get(0); + assertEquals("P1", p1.getGroupName()); + assertEquals(1, p1.getPermissions().size()); + assertEquals(Permission.MANAGE_MEMBERS, p1.getPermissions().get(0)); + assertFalse(p1.isHasChildren()); + p2 = groupNodes.get(1); + assertEquals("P2", p2.getGroupName()); + assertEquals(Permission.VIEW_MEMBERS, p2.getPermissions().get(0)); + assertFalse(p2.isHasChildren()); + } } -- GitLab