diff --git a/.gitignore b/.gitignore index ed9e3601e1bb5d2f0dbe5895f389c6de55e741eb..005e1f7506c3522250d5697069561c933b1eaf5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store node_modules gms-ui/dist +gms-ui/node/ # local env files .env.local @@ -53,3 +54,4 @@ nbactions.xml ### VS Code ### .vscode/ +/gms-ui/target/ diff --git a/gms-ui/.env.production b/gms-ui/.env.production index 8ef8cb1189f911185c87a9642d76f0ff54822644..e86ff0cf8400a8ad9c810068c5173b54aba64879 100644 --- a/gms-ui/.env.production +++ b/gms-ui/.env.production @@ -1 +1,2 @@ VUE_APP_API_CLIENT = 'server' +BASE_API_URL = 'http://localhost:8081/' diff --git a/gms-ui/src/api/server/index.js b/gms-ui/src/api/server/index.js index c44f2b8549d7172e3a4f7d62b9d307f4c013a697..c78982a0fab1d573a51d7dfcf6b18ed1fd853d7f 100644 --- a/gms-ui/src/api/server/index.js +++ b/gms-ui/src/api/server/index.js @@ -1,9 +1,15 @@ -//import axios from 'axios' +const BASE_API_URL = "http://localhost:8081/" export default { - fetchPosts () { - return null;//axios - //.get('https://jsonplaceholder.typicode.com/posts') - //.then(response => response.data) + fetchMainModel () { + return fetch(BASE_API_URL + 'groups?groupId=ROOT&tab=groups&paginatorPageSize=20&paginatorPage=1', { + method: 'GET', + mode: 'cors', + cache: 'no-cache', + credentials: 'include', + headers: { + 'Content-Type': 'application/json' + } + }); } -} +}; diff --git a/gms/pom.xml b/gms/pom.xml index 3042e6941fcfacddc3e329665964f2d0f679b177..a24ffb03a81f243768e50fea3ffe7ebfad53950a 100644 --- a/gms/pom.xml +++ b/gms/pom.xml @@ -67,6 +67,27 @@ <build> <plugins> + <plugin> + <artifactId>maven-resources-plugin</artifactId> + <version>3.1.0</version> + <executions> + <execution> + <id>copy-resources</id> + <phase>prepare-package</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${basedir}/target/classes/static</outputDirectory> + <resources> + <resource> + <directory>../gms-ui/dist</directory> + </resource> + </resources> + </configuration> + </execution> + </executions> + </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/OAuth2Config.java b/gms/src/main/java/it/inaf/ia2/gms/authn/OAuth2Config.java index 4fd68b5fab327aed3e21955187bd6d26f6eed3d3..81e0ec10c396f26d9699d821da0a1cca732ee40e 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/authn/OAuth2Config.java +++ b/gms/src/main/java/it/inaf/ia2/gms/authn/OAuth2Config.java @@ -15,6 +15,7 @@ import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService; import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; +import org.springframework.web.client.RestTemplate; /** * Extending the AuthorizationServerEndpointsConfiguration disables the Spring @@ -63,4 +64,9 @@ public class OAuth2Config extends AuthorizationServerEndpointsConfiguration { return new DefaultUserInfoRestTemplateFactory(customizers, details, oauth2ClientContext); } + + @Bean + public RestTemplate rapRestTemplate() { + return new RestTemplate(); + } } 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 index 5a82085919f2548f299634a278583492fb18e3bc..92ddbc8e4cae2d7546ec2abacbd5ed6ad2d08265 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java +++ b/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java @@ -20,13 +20,13 @@ public class SessionData { private UsersRepository usersRepository; private String userId; + private String accessToken; @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); + accessToken = (String) authn.getAttributes().get("access_token"); if (!usersRepository.findById(userId).isPresent()) { User user = new User(); @@ -38,4 +38,8 @@ public class SessionData { public String getUserId() { return userId; } + + public String getAccessToken() { + return accessToken; + } } 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 index c1fb66276be3c6347c745c46acf31eecef1cccfd..4ee7683fd7b167f1ba269aa677aa5f8bce0cda05 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java +++ b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java @@ -35,7 +35,7 @@ public class GroupsController { private GroupsService groupsService; @GetMapping(value = "/groups", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public GroupsModelResponse getGroupsModelResponse(@Valid @RequestBody GroupsModelRequest groupsModelRequest) { + public GroupsModelResponse getGroupsModelResponse(@Valid GroupsModelRequest groupsModelRequest) { return groupsModelService.getGroupsModel(groupsModelRequest, session.getUserId()); } 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 index 9127d87de239fccf58baa82318ffd5e6fc03fc1d..79e22f42680ad960288957a29d34b603c290b7d1 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java +++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java @@ -1,8 +1,17 @@ package it.inaf.ia2.gms.model; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + public class GroupsModelRequest extends BaseModelRequest { + @NotNull private String groupId; + + @NotNull + private String tab; + + @Min(1) private int paginatorPage; private int paginatorPageSize; @@ -14,6 +23,14 @@ public class GroupsModelRequest extends BaseModelRequest { this.groupId = groupId; } + public String getTab() { + return tab; + } + + public void setTab(String tab) { + this.tab = tab; + } + public int getPaginatorPage() { return paginatorPage; } 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 index f2a565557eaa455d20d603e54f4fc4fed60fd7bd..fe0c6f6f0f61dc80e6d3d5d0199b6f66c8f999e0 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/model/IdentityType.java +++ b/gms/src/main/java/it/inaf/ia2/gms/model/IdentityType.java @@ -2,11 +2,21 @@ package it.inaf.ia2.gms.model; public enum IdentityType { - EDU_GAIN, - X509, - ORCID, - GOOGLE, - LINKEDIN, - FACEBOOK, - LOCAL_IDP + EDU_GAIN("EduGAIN"), + X509("X.509"), + ORCID("OrcID"), + GOOGLE("Google"), + LINKEDIN("LinkedIn"), + FACEBOOK("Facebook"), + LOCAL_IDP("LocalIdP"); + + private final String value; + + IdentityType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } 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 index cd336928e77e34081c72e5101f1403aa792cc27e..44bd7df9128ae0afef56e2bb9baa2323960e2f9c 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipRepository.java +++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipRepository.java @@ -1,5 +1,6 @@ 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.MembershipId; import it.inaf.ia2.gms.persistence.model.User; @@ -13,4 +14,6 @@ import org.springframework.stereotype.Repository; public interface MembershipRepository extends JpaRepository<Membership, MembershipId> { List<Membership> findBy_user(User user); + + List<Membership> findBy_group(Group group); } 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 f3e19f1a5cfb956db75c6cc867b7390632f4c54d..d43f293ffced76d1415f7c16c27a1c3720579d43 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,5 +1,6 @@ package it.inaf.ia2.gms.persistence; +import it.inaf.ia2.gms.persistence.model.Group; import it.inaf.ia2.gms.persistence.model.User; import it.inaf.ia2.gms.persistence.model.UserGroupPermission; import it.inaf.ia2.gms.persistence.model.UserGroupPermissionId; @@ -13,4 +14,6 @@ import org.springframework.stereotype.Repository; public interface PermissionsRepository extends JpaRepository<UserGroupPermission, UserGroupPermissionId> { List<UserGroupPermission> findBy_user(User user); + + List<UserGroupPermission> findBy_group(Group group); } 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 index a7eff0d84c6d24acb7256eb98e00ed88338a4fd9..0dba6d865a535ae6d4674c9b528e76c0b8438e22 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java +++ b/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java @@ -1,8 +1,50 @@ package it.inaf.ia2.gms.rap; +import it.inaf.ia2.gms.authn.SessionData; +import it.inaf.ia2.gms.model.RapUser; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; @Component public class RapClient { + @Value("${rap.ws-url}") + private String rapBaseUrl; + + @Autowired + private SessionData sessionData; + + @Autowired + private RestTemplate rapRestTemplate; + + public List<RapUser> getUsers(Set<String> identifiers) { + + String url = rapBaseUrl + "/user?identifiers=" + String.join(",", identifiers); + return rapRestTemplate.exchange(url, HttpMethod.GET, getEntity(), new ParameterizedTypeReference<List<RapUser>>() { + }).getBody(); + } + + private HttpEntity<?> getEntity() { + return getEntity(null); + } + + private <T> HttpEntity<T> getEntity(T body) { + + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + + headers.add("Authorization", "Bearer " + sessionData.getAccessToken()); + + return new HttpEntity<>(body, headers); + } } 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 index 1673fd6eafc1707d249cf883b22d74269c7f695a..813e1710d7c7255e677976542ba218c363bc3abe 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelService.java +++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelService.java @@ -5,9 +5,13 @@ 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.model.Permission; +import it.inaf.ia2.gms.model.RapUser; +import it.inaf.ia2.gms.model.UserPermission; import it.inaf.ia2.gms.persistence.model.Group; import it.inaf.ia2.gms.persistence.model.User; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -21,6 +25,12 @@ public class GroupsModelService { @Autowired private GroupsService groupsService; + @Autowired + private MembersService membersService; + + @Autowired + private PermissionsService permissionsService; + public GroupsModelResponse getGroupsModel(GroupsModelRequest request, String userId) { User user = usersService.getUserById(userId); @@ -30,12 +40,31 @@ public class GroupsModelService { response.setPage("groups"); - List<GroupNode> groupNodes = groupsService.getSubgroups(group, user); + response.setBreadcrumbs(buildBreadcrumbs(new ArrayList<>(), group)); - response.setGroupsPanel(new PaginatedData<>(groupNodes, request.getPaginatorPage(), request.getPaginatorPageSize())); + List<Permission> currentNodePermissions = groupsService.getPermissions(group, user); + response.setPermissions(currentNodePermissions); - response.setPermissions(groupsService.getPermissions(group, user)); - response.setBreadcrumbs(buildBreadcrumbs(new ArrayList<>(), group)); + switch (request.getTab()) { + case "groups": + List<GroupNode> groupNodes = groupsService.getSubgroups(group, user); + response.setGroupsPanel(new PaginatedData<>(groupNodes, request.getPaginatorPage(), request.getPaginatorPageSize())); + break; + case "members": + if (currentNodePermissions.contains(Permission.ADMIN) + || currentNodePermissions.contains(Permission.MANAGE_MEMBERS) + || currentNodePermissions.contains(Permission.VIEW_MEMBERS)) { + List<RapUser> members = membersService.getMembers(group); + response.setMembersPanel(new PaginatedData<>(members, request.getPaginatorPage(), request.getPaginatorPageSize())); + } + break; + case "permissions": + if (currentNodePermissions.contains(Permission.ADMIN)) { + List<UserPermission> permissions = permissionsService.getUserPermissions(group); + response.setPermissionsPanel(new PaginatedData<>(permissions, request.getPaginatorPage(), request.getPaginatorPageSize())); + } + break; + } return response; } @@ -48,6 +77,7 @@ public class GroupsModelService { breadcrumbs.add(bc); if (group.getParentGroup() == null) { + Collections.reverse(breadcrumbs); return breadcrumbs; } else { return buildBreadcrumbs(breadcrumbs, group.getParentGroup()); diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/MembersService.java b/gms/src/main/java/it/inaf/ia2/gms/service/MembersService.java new file mode 100644 index 0000000000000000000000000000000000000000..5e1b5a045465ebc54dd9d61c8b52fdc74b96f859 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/service/MembersService.java @@ -0,0 +1,32 @@ +package it.inaf.ia2.gms.service; + +import it.inaf.ia2.gms.model.RapUser; +import it.inaf.ia2.gms.persistence.MembershipRepository; +import it.inaf.ia2.gms.persistence.model.Group; +import it.inaf.ia2.gms.persistence.model.Membership; +import it.inaf.ia2.gms.rap.RapClient; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class MembersService { + + @Autowired + private MembershipRepository membershipRepository; + + @Autowired + private RapClient rapClient; + + public List<RapUser> getMembers(Group group) { + List<Membership> memberships = membershipRepository.findBy_group(group); + + Set<String> userIdentifiers = memberships.stream() + .map(m -> m.getUser().getId()) + .collect(Collectors.toSet()); + + return rapClient.getUsers(userIdentifiers); + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java b/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java new file mode 100644 index 0000000000000000000000000000000000000000..7f7d3e353a05e606c06e468b4e79cc5e8621e020 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java @@ -0,0 +1,52 @@ +package it.inaf.ia2.gms.service; + +import it.inaf.ia2.gms.model.RapUser; +import it.inaf.ia2.gms.model.UserPermission; +import it.inaf.ia2.gms.persistence.PermissionsRepository; +import it.inaf.ia2.gms.persistence.model.Group; +import it.inaf.ia2.gms.persistence.model.UserGroupPermission; +import it.inaf.ia2.gms.rap.RapClient; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class PermissionsService { + + @Autowired + private PermissionsRepository permissionsRepository; + + @Autowired + private RapClient rapClient; + + public List<UserPermission> getUserPermissions(Group group) { + + List<UserGroupPermission> permissions = permissionsRepository.findBy_group(group); + + Set<String> userIdentifiers = permissions.stream() + .map(p -> p.getUser().getId()) + .collect(Collectors.toSet()); + + Map<String, RapUser> users = rapClient.getUsers(userIdentifiers).stream() + .collect(Collectors.toMap(RapUser::getId, Function.identity())); + + List<UserPermission> result = new ArrayList<>(); + + for (UserGroupPermission p : permissions) { + RapUser rapUser = users.get(p.getUser().getId()); + if (rapUser != null) { + UserPermission permission = new UserPermission(); + permission.setPermission(p.getPermission()); + permission.setUser(rapUser); + result.add(permission); + } + } + + return result; + } +} diff --git a/gms/src/main/resources/application.properties b/gms/src/main/resources/application.properties index 0893bb1b3af8130c37c64193bf4a8fa1316664e8..683fc405c5dd42a69843d8142b875ee784a0967b 100644 --- a/gms/src/main/resources/application.properties +++ b/gms/src/main/resources/application.properties @@ -16,3 +16,5 @@ spring.datasource.url=jdbc:mysql://localhost:3306/gms?useJDBCCompliantTimezoneSh spring.datasource.username=gms spring.datasource.password=gms #spring.jpa.open-in-view=false + +rap.ws-url=http://localhost/rap-ia2/ws \ No newline at end of file diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fc12cfc31f0fc9fd2fe6cfec51903af3bc77d2e9 --- /dev/null +++ b/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsControllerTest.java @@ -0,0 +1,64 @@ +package it.inaf.ia2.gms.controller; + +import it.inaf.ia2.gms.authn.SessionData; +import it.inaf.ia2.gms.model.GroupsModelRequest; +import it.inaf.ia2.gms.model.GroupsModelResponse; +import it.inaf.ia2.gms.service.GroupsModelService; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import static org.mockito.ArgumentMatchers.any; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +@RunWith(MockitoJUnitRunner.class) +public class GroupsControllerTest { + + @Mock + private SessionData session; + + @Mock + private GroupsModelService groupsModelService; + + @InjectMocks + private GroupsController controller; + + @Autowired + private MockMvc mockMvc; + + @Before + public void init() { + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Test + public void testGetGroups() throws Exception { + + GroupsModelResponse response = new GroupsModelResponse(); + + when(groupsModelService.getGroupsModel(any(), any())) + .thenReturn(response); + + mockMvc.perform(get("/groups?groupId=ROOT&tab=groups&paginatorPageSize=20&paginatorPage=1")) + .andExpect(status().isOk()); + + ArgumentCaptor<GroupsModelRequest> requestCaptor = ArgumentCaptor.forClass(GroupsModelRequest.class); + verify(groupsModelService).getGroupsModel(requestCaptor.capture(), any()); + GroupsModelRequest request = requestCaptor.getValue(); + + assertEquals("ROOT", request.getGroupId()); + assertEquals("groups", request.getTab()); + assertEquals(1, request.getPaginatorPage()); + assertEquals(20, request.getPaginatorPageSize()); + } +}