Newer
Older
import it.inaf.ia2.gms.authn.RapPrincipal;
import it.inaf.ia2.gms.exception.BadRequestException;
import it.inaf.ia2.gms.manager.GroupsManager;
import it.inaf.ia2.gms.manager.MembershipManager;
import it.inaf.ia2.gms.manager.PermissionsManager;
import it.inaf.ia2.gms.model.Permission;
Sonia Zorba
committed
import it.inaf.ia2.gms.model.response.UserPermission;
import it.inaf.ia2.gms.persistence.GroupsDAO;
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.PermissionUtils;
Sonia Zorba
committed
import it.inaf.ia2.gms.service.SearchService;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Web service called by other web applications using JWT (delegation).
*/
@RestController
@RequestMapping("/ws/jwt")
public class JWTWebServiceController {
@Autowired
private JoinService joinService;
@Autowired
private GroupsDAO groupsDAO;
private GroupsManager groupsManager;
private GroupsService groupsService;
private MembershipManager membershipManager;
private PermissionsManager permissionsManager;
private PermissionsDAO permissionsDAO;
Sonia Zorba
committed
@Autowired
private SearchService searchService;
/**
* This endpoint is compliant with the IVOA GMS standard.
*/
@GetMapping(value = "/search", produces = MediaType.TEXT_PLAIN_VALUE)
public void getGroups(HttpServletResponse response) throws IOException {
List<GroupEntity> memberships = membershipManager.getCurrentUserMemberships();
List<String> names = getGroupsNames(memberships);
try (PrintWriter pw = new PrintWriter(response.getOutputStream())) {
for (String name : names) {
pw.println(name);
/**
* This endpoint is compliant with the IVOA GMS standard. Warning: for
* supporting the groups of groups (with dots inside) the path variable must
* be defined adding ".+", otherwise Spring will think it is a file
* extension (thanks https://stackoverflow.com/a/16333149/771431)
*/
@GetMapping(value = "/search/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
public void isMemberOf(@PathVariable("group") String group, HttpServletResponse response) throws IOException {
List<String> groupNames = extractGroupNames(group);
boolean isMember = membershipManager.isCurrentUserMemberOf("ROOT");
if (!isMember) {
String parentPath = ""; // starting from ROOT
for (String groupName : groupNames) {
Optional<GroupEntity> optionalGroup = groupsDAO.findGroupByParentAndName(parentPath, groupName);
if (optionalGroup.isPresent()) {
GroupEntity groupEntity = optionalGroup.get();
parentPath = groupEntity.getPath();
isMember = membershipManager.isCurrentUserMemberOf(groupEntity.getId());
if (isMember) {
break;
}
} else {
break;
}
}
}
try (PrintWriter pw = new PrintWriter(response.getOutputStream())) {
pw.println(group);
}
}
// 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)) {
pw.println(getShortGroupName(groupName, group));
/**
* Creates a group and its ancestors if they are missing. It doesn't fail if
* the last group already exists.
*/
@PostMapping(value = "/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
public void createGroup(@PathVariable("group") String groupParam, HttpServletRequest request, HttpServletResponse response) throws IOException {
List<String> groupNames = extractGroupNames(groupParam);
String leafParam = request.getParameter("leaf");
boolean leaf = leafParam == null ? false : Boolean.valueOf(leafParam);
GroupEntity group = groupsManager.getRoot();
for (int i = 0; i < groupNames.size(); i++) {
String name = groupNames.get(i);
Optional<GroupEntity> optGroup = groupsService.findGroupByParentAndName(group, name);
if (optGroup.isPresent()) {
group = optGroup.get();
} else {
group = groupsManager.createGroup(group, name, i == groupNames.size() - 1 ? leaf : false);
}
}
response.setStatus(HttpServletResponse.SC_CREATED);
try (PrintWriter pw = new PrintWriter(response.getOutputStream())) {
pw.println(groupParam);
@DeleteMapping(value = "/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
public void deleteGroup(@PathVariable("group") String groupParam, HttpServletResponse response) {
GroupEntity group = getGroupFromNames(extractGroupNames(groupParam));
groupsDAO.deleteGroupById(group.getId());
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
@GetMapping(value = {"/membership/{group:.+}", "/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void getMembership(@PathVariable("group") Optional<String> group, @RequestParam("user_id") String userId, HttpServletResponse response) throws IOException {
GroupEntity parent = getGroupFromNames(extractGroupNames(group));
List<GroupEntity> groups = membershipManager.getUserGroups(parent, userId);
try (PrintWriter pw = new PrintWriter(response.getOutputStream())) {
for (String groupName : getGroupsNames(groups)) {
pw.println(getShortGroupName(groupName, group));
}
}
}
@PostMapping(value = {"/membership/{group:.+}", "/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void addMember(@PathVariable("group") Optional<String> group, HttpServletRequest request, HttpServletResponse response) throws IOException {
String targetUserId = request.getParameter("user_id");
if (targetUserId == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing user_id parameter");
return;
}
GroupEntity groupEntity = getGroupFromNames(extractGroupNames(group));
membershipManager.addMember(groupEntity, targetUserId);
}
@DeleteMapping(value = {"/membership/{group:.+}", "/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void removeMember(@PathVariable("group") Optional<String> group, @RequestParam("user_id") String userId,
HttpServletRequest request, HttpServletResponse response) throws IOException {
GroupEntity groupEntity = getGroupFromNames(extractGroupNames(group));
membershipManager.removeMember(groupEntity, userId);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
Sonia Zorba
committed
@GetMapping(value = "/permission", produces = MediaType.TEXT_PLAIN_VALUE)
public void getUserPermission(@RequestParam("user_id") String userId, HttpServletRequest request, HttpServletResponse response) throws IOException {
try (PrintWriter pw = new PrintWriter(response.getOutputStream())) {
for (UserPermission userPermission : searchService.getUserPermission(userId, permissionsManager.getCurrentUserPermissions(getRoot()))) {
String group = String.join(".", userPermission.getGroupCompleteName());
pw.println(group + " " + userPermission.getPermission());
}
}
}
@PostMapping(value = {"/permission/{group:.+}", "/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void addPermission(@PathVariable("group") Optional<String> groupNames, HttpServletRequest request, HttpServletResponse response) throws IOException {
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;
}
GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
permissionsManager.addPermission(groupEntity, targetUserId, Permission.valueOf(permissionParam));
@DeleteMapping(value = {"/permission/{group:.+}", "/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void removePermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String userId,
HttpServletRequest request, HttpServletResponse response) throws IOException {
GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
permissionsManager.removePermission(groupEntity, userId);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
private GroupEntity getGroupFromNames(List<String> groupNames) {
if (groupNames.isEmpty()) {
return getRoot();
}
return getGroupFromNamesAndIndex(groupNames, groupNames.size() - 1);
}
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 BadRequestException("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. TODO: probably this logic is duplicated inside GroupNameService.
* Check this.
*/
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++) {
char c = groupStr.charAt(i);
// dot is the group separator and it must be escaped if used inside
// group names
if (c == '.' && groupStr.charAt(i - 1) != '\\') {
names.add(currentName.replace("\\.", "."));
currentName = "";
} else {
currentName += c;
}
}
names.add(currentName);
return names;
}
private Set<String> getAllIdentifiers(List<GroupEntity> groups) {
Set<String> allIdentifiers = new HashSet<>();
for (GroupEntity group : groups) {
if (!"".equals(group.getPath())) {
String[] ids = group.getPath().split("\\.");
for (String id : ids) {
allIdentifiers.add(id);
}
}
}
return allIdentifiers;
}
private String getGroupCompleteName(GroupEntity group, Map<String, String> idNameMap) {
if ("ROOT".equals(group.getId())) {
return group.getName();
}
List<String> names = new ArrayList<>();
for (String groupId : group.getPath().split("\\.")) {
String groupName = idNameMap.get(groupId);
// Dot inside names is considered a special character (because it is
// used to separate the group from its parents), so we use a
// backslash to escape it (client apps need to be aware of this).
groupName = groupName.replace("\\.", "\\\\.");
names.add(groupName);
}
return String.join(".", names);
}
private String getShortGroupName(String completeGroupName, Optional<String> groupPrefix) {
if (groupPrefix.isPresent()) {
return completeGroupName.substring(groupPrefix.get().length() + 1);
}
return completeGroupName;
}
@PostMapping(value = "/join", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> join(RapPrincipal principal) {
String fromUser = principal.getName();
String toUser = principal.getAlternativeName();
if (toUser == null) {
throw new BadRequestException("Missing alternative subject");
}
joinService.join(fromUser, toUser);
Map<String, String> responseBody = new HashMap<>();
responseBody.put("mergedId", fromUser);
return ResponseEntity.ok(responseBody);
}