diff --git a/.gitignore b/.gitignore index bbcf791ced5118b541f8b20271adab8f911cf860..43b76678cf8f698ee48e5dd2adf504eeef882372 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,5 @@ nbactions.xml /gms-client/gms-client-lib/target/ /gms-client/gms-cli/target/ /gms/node/ + +nb-configuration.xml diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java index f4009775c4dd85a2e4a0ead3699be66cd2b3a208..ad153276b0643fb28a7917e8f25a62423ed93dd0 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java +++ b/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java @@ -2,10 +2,19 @@ package it.inaf.ia2.gms.controller; import it.inaf.ia2.gms.authn.RapPrincipal; import it.inaf.ia2.gms.exception.BadRequestException; +import it.inaf.ia2.gms.exception.UnauthorizedException; +import it.inaf.ia2.gms.model.Permission; import it.inaf.ia2.gms.persistence.GroupsDAO; +import it.inaf.ia2.gms.persistence.LoggingDAO; import it.inaf.ia2.gms.persistence.MembershipsDAO; +import it.inaf.ia2.gms.persistence.PermissionsDAO; import it.inaf.ia2.gms.persistence.model.GroupEntity; +import it.inaf.ia2.gms.persistence.model.PermissionEntity; +import it.inaf.ia2.gms.service.GroupsService; import it.inaf.ia2.gms.service.JoinService; +import it.inaf.ia2.gms.service.MembersService; +import it.inaf.ia2.gms.service.PermissionUtils; +import it.inaf.ia2.gms.service.PermissionsService; import java.io.IOException; import java.io.PrintWriter; import java.security.Principal; @@ -16,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; @@ -42,6 +52,21 @@ public class JWTWebServiceController { @Autowired private GroupsDAO groupsDAO; + @Autowired + private GroupsService groupsService; + + @Autowired + private MembersService membersService; + + @Autowired + private PermissionsService permissionsService; + + @Autowired + private PermissionsDAO permissionsDAO; + + @Autowired + private LoggingDAO loggingDAO; + /** * This endpoint is compliant with the IVOA GMS standard. */ @@ -50,18 +75,12 @@ public class JWTWebServiceController { List<GroupEntity> memberships = membershipsDAO.getUserMemberships(principal.getName()); - // We need to return the complete group name, so it is necessary to load - // all the parents too. - Map<String, String> idNameMap = new HashMap<>(); - Set<String> allIdentifiers = getAllIdentifiers(memberships); - for (GroupEntity group : groupsDAO.findGroupsByIds(allIdentifiers)) { - idNameMap.put(group.getId(), group.getName()); - } + List<String> names = getGroupsNames(memberships); try (PrintWriter pw = new PrintWriter(response.getOutputStream())) { - for (GroupEntity group : memberships) { - pw.println(getGroupCompleteName(group, idNameMap)); + for (String name : names) { + pw.println(name); } } } @@ -105,8 +124,171 @@ public class JWTWebServiceController { // else: empty response (as defined by GMS standard) } + @GetMapping(value = {"/list/{group:.+}", "/list"}, produces = MediaType.TEXT_PLAIN_VALUE) + public void listGroups(@PathVariable("group") Optional<String> group, Principal principal, HttpServletResponse response) throws IOException { + + String userId = principal.getName(); + + List<String> groupNames = extractGroupNames(group); + GroupEntity parentGroup = getGroupFromNames(groupNames); + + List<GroupEntity> allSubGroups = groupsDAO.getDirectSubGroups(parentGroup.getPath()); + + // Select only the groups visible to the user + List<PermissionEntity> permissions = permissionsDAO.findUserPermissions(userId); + List<GroupEntity> visibleSubgroups = new ArrayList<>(); + + for (GroupEntity subgroup : allSubGroups) { + PermissionUtils.getGroupPermission(subgroup, permissions).ifPresent(permission -> { + visibleSubgroups.add(subgroup); + }); + } + + try (PrintWriter pw = new PrintWriter(response.getOutputStream())) { + for (String groupName : getGroupsNames(visibleSubgroups)) { + if (group.isPresent()) { + String shortName = groupName.substring(group.get().length() + 1); + pw.println(shortName); + } else { + pw.println(groupName); + } + } + } + } + + @PostMapping(value = "/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE) + public void createGroup(@PathVariable("group") String group, Principal principal, HttpServletRequest request, HttpServletResponse response) throws IOException { + + String userId = principal.getName(); + + List<String> groupNames = extractGroupNames(group); + GroupEntity parent = getParentFromNames(groupNames); + + String newGroupName = groupNames.get(groupNames.size() - 1); + + if (permissionsService.getUserPermissionForGroup(parent, userId) != Permission.ADMIN) { + loggingDAO.logAction("Unauthorized create group request, group_name=" + newGroupName); + throw new UnauthorizedException("Missing admin permission"); + } + + String leafParam = request.getParameter("leaf"); + boolean leaf = leafParam == null ? false : Boolean.valueOf(leafParam); + + groupsService.addGroup(parent, newGroupName, leaf); + loggingDAO.logAction("Added group: parent_path=" + parent.getPath() + ", group_name=" + newGroupName); + + response.setStatus(HttpServletResponse.SC_CREATED); + try (PrintWriter pw = new PrintWriter(response.getOutputStream())) { + pw.println(group); + } + } + + @PostMapping(value = {"/membership/{group:.+}", "/membership"}, produces = MediaType.TEXT_PLAIN_VALUE) + public void addMember(@PathVariable("group") Optional<String> group, Principal principal, HttpServletRequest request, HttpServletResponse response) throws IOException { + + GroupEntity groupEntity = getGroupFromNames(extractGroupNames(group)); + + String userId = principal.getName(); + membersService.verifyUserCanManageMembers(groupEntity, userId); + + String targetUserId = request.getParameter("user_id"); + if (targetUserId == null) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing user_id parameter"); + return; + } + membersService.addMember(groupEntity.getId(), targetUserId); + loggingDAO.logAction("Added member, group_id=" + groupEntity.getId() + ", user_id=" + targetUserId); + } + + @PostMapping(value = {"/permission/{group:.+}", "/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE) + public void addPermission(@PathVariable("group") Optional<String> groupNames, Principal principal, HttpServletRequest request, HttpServletResponse response) throws IOException { + + GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames)); + + String userId = principal.getName(); + permissionsService.verifyUserCanManagePermissions(groupEntity, userId); + + String targetUserId = request.getParameter("user_id"); + if (targetUserId == null) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing user_id parameter"); + return; + } + String permissionParam = request.getParameter("permission"); + if (permissionParam == null) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing permission parameter"); + return; + } + Permission permission = Permission.valueOf(permissionParam); + permissionsService.addPermission(groupEntity, targetUserId, permission); + loggingDAO.logAction("Permission added, group_id=" + groupEntity.getId() + ", user_id=" + + targetUserId + ", permission=" + permission); + } + + private GroupEntity getGroupFromNames(List<String> groupNames) { + if (groupNames.isEmpty()) { + return getRoot(); + } + return getGroupFromNamesAndIndex(groupNames, groupNames.size() - 1); + } + + private GroupEntity getParentFromNames(List<String> groupNames) { + if (groupNames.size() == 1) { + return getRoot(); + } + return getGroupFromNamesAndIndex(groupNames, groupNames.size() - 2); + } + + private GroupEntity getGroupFromNamesAndIndex(List<String> groupNames, int index) { + String parentPath = ""; // starting from ROOT + GroupEntity group = null; + for (int i = 0; i < index + 1; i++) { + String groupName = groupNames.get(i); + group = groupsDAO.findGroupByParentAndName(parentPath, groupName) + .orElseThrow(() -> new IllegalArgumentException("Unable to find group " + groupName)); + parentPath = group.getPath(); + } + if (group == null) { + throw new IllegalStateException(); + } + return group; + } + + private GroupEntity getRoot() { + return groupsDAO.findGroupById("ROOT") + .orElseThrow(() -> new IllegalStateException("Missing root group")); + } + + /** + * Returns the list of the group complete names, given a list of GroupEntity + * objects. + */ + private List<String> getGroupsNames(List<GroupEntity> groups) { + + // We need to return the complete group name, so it is necessary to load + // all the parents too. + Map<String, String> idNameMap = new HashMap<>(); + Set<String> allIdentifiers = getAllIdentifiers(groups); + for (GroupEntity group : groupsDAO.findGroupsByIds(allIdentifiers)) { + idNameMap.put(group.getId(), group.getName()); + } + + List<String> names = new ArrayList<>(); + for (GroupEntity group : groups) { + names.add(getGroupCompleteName(group, idNameMap)); + } + return names; + } + + private List<String> extractGroupNames(Optional<String> group) { + return extractGroupNames(group.orElse(null)); + } + private List<String> extractGroupNames(String groupStr) { + if (groupStr == null || groupStr.isEmpty()) { + return new ArrayList<>(); + } + List<String> names = new ArrayList<>(); String currentName = ""; for (int i = 0; i < groupStr.length(); i++) { diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/MembersController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/MembersController.java index 39d2e7eafd25bab4f965ebf65dc65441854ff0dc..31a6f1ebe22064ded1ee60a0c2a0612aa53ea607 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/MembersController.java +++ b/gms/src/main/java/it/inaf/ia2/gms/controller/MembersController.java @@ -64,9 +64,10 @@ public class MembersController { public ResponseEntity<PaginatedData<RapUser>> addMember(@Valid @RequestBody AddMemberRequest request) { GroupEntity group = groupsService.getGroupById(request.getGroupId()); - Permission currentUserPermission = verifyCurrentUserCanManageMembers(group); + Permission currentUserPermission = membersService.verifyUserCanManageMembers(group, session.getUserId()); membersService.addMember(request.getGroupId(), request.getUserId()); + loggingDAO.logAction("Added member, group_id=" + group.getId() + ", user_id=" + request.getUserId()); if (currentUserPermission == Permission.MANAGE_MEMBERS) { // Automatically assign the VIEW_MEMBERS permission ("Add collaborator" feature) permissionsService.addPermission(group, request.getUserId(), Permission.VIEW_MEMBERS); @@ -86,7 +87,7 @@ public class MembersController { public ResponseEntity<PaginatedData<RapUser>> removeMember(@Valid RemoveMemberRequest request) { GroupEntity group = groupsService.getGroupById(request.getGroupId()); - Permission currentUserPermission = verifyCurrentUserCanManageMembers(group); + Permission currentUserPermission = membersService.verifyUserCanManageMembers(group, session.getUserId()); membersService.removeMember(group.getId(), request.getUserId()); loggingDAO.logAction("Member removed, group_id=" + group.getId() + ", user_id=" + request.getUserId()); @@ -110,14 +111,6 @@ public class MembersController { return ResponseEntity.ok(getMembersPanel(request)); } - private Permission verifyCurrentUserCanManageMembers(GroupEntity group) { - Permission currentNodePermission = permissionsService.getUserPermissionForGroup(group, session.getUserId()); - if (currentNodePermission != Permission.ADMIN && currentNodePermission != Permission.MANAGE_MEMBERS) { - throw new UnauthorizedException("Missing admin or manage members permissions"); - } - return currentNodePermission; - } - private PaginatedData<RapUser> getMembersPanel(MemberRequest request) { List<RapUser> members = membersService.getMembers(request.getGroupId()); return new PaginatedData<>(members, request.getPaginatorPage(), request.getPaginatorPageSize()); diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java index 5fb97f817c5641743b2aa72aa05679abd4db68c8..7cb9658268b7c1e470c8b08d27ca486945a507bd 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java +++ b/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java @@ -64,7 +64,7 @@ public class PermissionsController { public ResponseEntity<Map<String, Permission>> getUserPermission(@RequestParam("groupId") String groupId, @RequestParam("userId") String userId) { GroupEntity group = groupsService.getGroupById(groupId); - verifyCurrentUserCanManagePermissions(group); + permissionsService.verifyUserCanManagePermissions(group, session.getUserId()); Permission permission = permissionsService.getUserPermissionForGroup(group, userId); if (permission == null) { @@ -80,7 +80,7 @@ public class PermissionsController { public ResponseEntity<PaginatedData<UserPermission>> addPermission(@Valid @RequestBody AddPermissionRequest request) { GroupEntity group = groupsService.getGroupById(request.getGroupId()); - verifyCurrentUserCanManagePermissions(group); + permissionsService.verifyUserCanManagePermissions(group, session.getUserId()); permissionsService.addPermission(group, request.getUserId(), request.getPermission()); loggingDAO.logAction("Permission added, group_id=" + request.getGroupId() + ", user_id=" @@ -93,7 +93,7 @@ public class PermissionsController { public ResponseEntity<PaginatedData<UserPermission>> deletePermission(@Valid MemberRequest request) { GroupEntity group = groupsService.getGroupById(request.getGroupId()); - verifyCurrentUserCanManagePermissions(group); + permissionsService.verifyUserCanManagePermissions(group, session.getUserId()); permissionsService.removePermission(group, request.getUserId()); loggingDAO.logAction("Permission removed, group_id=" + request.getGroupId() + ", user_id=" + request.getUserId()); @@ -101,14 +101,6 @@ public class PermissionsController { return ResponseEntity.ok(getPermissionsPanel(group, request)); } - private void verifyCurrentUserCanManagePermissions(GroupEntity group) { - Permission currentNodePermissions = permissionsService.getUserPermissionForGroup(group, session.getUserId()); - if (currentNodePermissions != Permission.ADMIN) { - loggingDAO.logAction("Unauthorized attempt to manage permissions"); - throw new UnauthorizedException("Only admin users can handle permissions"); - } - } - private PaginatedData<UserPermission> getPermissionsPanel(GroupEntity group, PaginatedModelRequest request) { List<UserPermission> permissions = permissionsService.getAllPermissions(group); return new PaginatedData<>(permissions, request.getPaginatorPage(), request.getPaginatorPageSize()); 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 d4d0d3f4971302e7a8eb5b6970ce92c54b3ff4aa..b13b1de5eb82782eb85650bfb63ec82705587db7 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 @@ -8,7 +8,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; -import org.apache.commons.codec.binary.Base64; +import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.ParameterizedTypeReference; @@ -41,19 +41,18 @@ public class RapClient { @Value("${security.oauth2.client.scope}") private String scope; - /* Use basic auth instead of JWT when asking for users */ - @Value("${rap.ws.basic-auth}") - private boolean basicAuth; + @Autowired + private HttpServletRequest request; - private final SessionData sessionData; + @Autowired(required = false) + private SessionData sessionData; private final RestTemplate rapRestTemplate; private final RestTemplate refreshTokenRestTemplate; @Autowired - public RapClient(SessionData sessionData, RestTemplate rapRestTemplate) { - this.sessionData = sessionData; + public RapClient(RestTemplate rapRestTemplate) { this.rapRestTemplate = rapRestTemplate; this.refreshTokenRestTemplate = new RestTemplate(); } @@ -94,6 +93,10 @@ public class RapClient { try { return function.apply(getEntity(body)); } catch (HttpClientErrorException.Unauthorized ex) { + if (sessionData == null) { + // we can't refresh the token without a session + throw ex; + } refreshToken(); return function.apply(getEntity(body)); } @@ -103,13 +106,11 @@ public class RapClient { HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - - if (basicAuth) { - String auth = clientId + ":" + clientSecret; - String encodedAuth = Base64.encodeBase64String(auth.getBytes()); - headers.add("Authorization", "Basic " + encodedAuth); - } else { + if (sessionData != null) { headers.add("Authorization", "Bearer " + sessionData.getAccessToken()); + } else { + // from JWT web service + headers.add("Authorization", request.getHeader("Authorization")); } return new HttpEntity<>(body, headers); 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 index 4adfa775e74464b3eb755267b473d9ecb96da33e..f46ce267033a67fc1a31cf13b3e443c2794c9910 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/service/MembersService.java +++ b/gms/src/main/java/it/inaf/ia2/gms/service/MembersService.java @@ -1,7 +1,10 @@ package it.inaf.ia2.gms.service; +import it.inaf.ia2.gms.exception.UnauthorizedException; +import it.inaf.ia2.gms.model.Permission; import it.inaf.ia2.gms.model.RapUser; import it.inaf.ia2.gms.persistence.MembershipsDAO; +import it.inaf.ia2.gms.persistence.model.GroupEntity; import it.inaf.ia2.gms.persistence.model.MembershipEntity; import it.inaf.ia2.gms.rap.RapClient; import java.util.List; @@ -16,6 +19,9 @@ public class MembersService { @Autowired private MembershipsDAO membershipsDAO; + @Autowired + private PermissionsService permissionsService; + @Autowired private RapClient rapClient; @@ -42,4 +48,12 @@ public class MembersService { public void removeMember(String groupId, String userId) { membershipsDAO.removeMembership(groupId, userId); } + + public Permission verifyUserCanManageMembers(GroupEntity group, String userId) { + Permission currentNodePermission = permissionsService.getUserPermissionForGroup(group, userId); + if (currentNodePermission != Permission.ADMIN && currentNodePermission != Permission.MANAGE_MEMBERS) { + throw new UnauthorizedException("Missing admin or manage members permissions"); + } + return currentNodePermission; + } } 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 index 99128493e8680fd60154b889e884b0e0d34c4ae5..3e36f300f3e543f934e73ec4fee14ea48e32faa6 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java +++ b/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java @@ -1,8 +1,10 @@ package it.inaf.ia2.gms.service; +import it.inaf.ia2.gms.exception.UnauthorizedException; 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.LoggingDAO; import it.inaf.ia2.gms.persistence.model.PermissionEntity; import it.inaf.ia2.gms.persistence.PermissionsDAO; import it.inaf.ia2.gms.persistence.model.GroupEntity; @@ -22,11 +24,13 @@ public class PermissionsService { private final PermissionsDAO permissionsDAO; private final RapClient rapClient; + private final LoggingDAO loggingDAO; @Autowired - public PermissionsService(PermissionsDAO permissionsDAO, RapClient rapClient) { + public PermissionsService(PermissionsDAO permissionsDAO, RapClient rapClient, LoggingDAO loggingDAO) { this.permissionsDAO = permissionsDAO; this.rapClient = rapClient; + this.loggingDAO = loggingDAO; } public List<UserPermission> getAllPermissions(GroupEntity group) { @@ -55,6 +59,14 @@ public class PermissionsService { return result; } + public void verifyUserCanManagePermissions(GroupEntity group, String userId) { + Permission currentNodePermissions = getUserPermissionForGroup(group, userId); + if (currentNodePermissions != Permission.ADMIN) { + loggingDAO.logAction("Unauthorized attempt to manage permissions"); + throw new UnauthorizedException("Only admin users can handle permissions"); + } + } + public Permission getUserPermissionForGroup(GroupEntity group, String userId) { List<PermissionEntity> permissions = permissionsDAO.findUserPermissions(userId, group.getPath()); return PermissionUtils.getGroupPermission(group, permissions).orElse(null); diff --git a/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceIntegrationTest.java b/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceIntegrationTest.java index c296be93e38b123f8a6a917d7ccd449cc8af1b36..5860154e3f1b0e45a42c39d2cfe4137b50f15b7e 100644 --- a/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceIntegrationTest.java +++ b/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceIntegrationTest.java @@ -5,6 +5,7 @@ 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.GroupsDAO; +import it.inaf.ia2.gms.persistence.LoggingDAO; import it.inaf.ia2.gms.persistence.PermissionsDAO; import it.inaf.ia2.gms.persistence.model.GroupEntity; import it.inaf.ia2.gms.persistence.model.PermissionEntity; @@ -31,6 +32,9 @@ public class PermissionsServiceIntegrationTest { @MockBean private RapClient rapClient; + @MockBean + private LoggingDAO loggingDAO; + @Autowired private DataSource dataSource; @@ -45,7 +49,7 @@ public class PermissionsServiceIntegrationTest { rapUser.setId(USER_ID); when(rapClient.getUsers(any())).thenReturn(Collections.singletonList(rapUser)); - PermissionsService permissionsService = new PermissionsService(permissionsDAO, rapClient); + PermissionsService permissionsService = new PermissionsService(permissionsDAO, rapClient, loggingDAO); // Create root GroupEntity root = new GroupEntity();