Skip to content
JWTWebServiceController.java 16.7 KiB
Newer Older
package it.inaf.ia2.gms.controller;

Sonia Zorba's avatar
Sonia Zorba committed
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.InvitedRegistrationManager;
import it.inaf.ia2.gms.manager.MembershipManager;
import it.inaf.ia2.gms.manager.PermissionsManager;
import it.inaf.ia2.gms.model.Permission;
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.InvitedRegistration;
import it.inaf.ia2.gms.persistence.model.PermissionEntity;
import it.inaf.ia2.gms.service.GroupNameService;
import it.inaf.ia2.gms.service.GroupsService;
Sonia Zorba's avatar
Sonia Zorba committed
import it.inaf.ia2.gms.service.JoinService;
import it.inaf.ia2.gms.service.PermissionUtils;
import it.inaf.ia2.gms.service.SearchService;
import it.inaf.ia2.rap.data.RapUser;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
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;
Sonia Zorba's avatar
Sonia Zorba committed
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;
Sonia Zorba's avatar
Sonia Zorba committed
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * This class needs some refactoring: it contains all endpoints that used JWT.
 * Now all endpoints accept both a JWT token or a session, so some of them could
 * be removed and others should be moved on dedicated classes. Some endpoints
 * match 2 patters to achieve a smooth transition.
 */
@RestController
public class JWTWebServiceController {

Sonia Zorba's avatar
Sonia Zorba committed
    @Autowired
    private JoinService joinService;

    @Autowired
    private GroupsDAO groupsDAO;

    private GroupsManager groupsManager;
    private GroupsService groupsService;
    @Autowired
    protected GroupNameService groupNameService;
    private MembershipManager membershipManager;
    private PermissionsManager permissionsManager;
    private PermissionsDAO permissionsDAO;
    @Autowired
    private SearchService searchService;

    @Autowired
    private InvitedRegistrationManager invitedRegistrationManager;

    /**
     * This endpoint is compliant with the IVOA GMS standard.
     */
    @GetMapping(value = {"/ws/jwt/search", "/vo/search"}, produces = MediaType.TEXT_PLAIN_VALUE)
    public void getGroups(HttpServletResponse response) throws IOException {
        List<GroupEntity> memberships = membershipManager.getCurrentUserMemberships();
        List<String> names = groupNameService.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 = {"/ws/jwt/search/{group:.+}", "/vo/search/{group:.+}"}, produces = MediaType.TEXT_PLAIN_VALUE)
    public void isMemberOf(@PathVariable("group") String group, HttpServletResponse response) throws IOException {
        List<String> groupNames = groupNameService.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 {
        if (isMember) {
            try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
                pw.println(group);
            }
        }
        // else: empty response (as defined by GMS standard)
    }

    @GetMapping(value = {"/ws/jwt/list/{group:.+}", "/ws/jwt/list", "/list"}, produces = MediaType.TEXT_PLAIN_VALUE)
    public void listGroups(@PathVariable("group") Optional<String> groupNames,
            @RequestParam(value = "recursive", defaultValue = "false") boolean recursive,
            Principal principal, HttpServletResponse response) throws IOException {

        String userId = principal.getName();

        GroupEntity parentGroup = groupNameService.getGroupFromNames(groupNames);
        List<GroupEntity> allSubGroups;
        if (recursive) {
            allSubGroups = groupsDAO.getAllChildren(parentGroup.getPath());
        } else {
            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 : groupNameService.getGroupsNames(visibleSubgroups)) {
                pw.println(groupNameService.getShortGroupName(groupName, groupNames));
    /**
     * Creates a group and its ancestors if they are missing. It doesn't fail if
     * the last group already exists.
     */
    @PostMapping(value = "/ws/jwt/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
    public void createGroup(@PathVariable("group") String groupParam, HttpServletRequest request, HttpServletResponse response) throws IOException {
        List<String> groupNames = groupNameService.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())) {
    @DeleteMapping(value = "/ws/jwt/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
    public void deleteGroup(@PathVariable("group") String groupParam, HttpServletResponse response) {
        GroupEntity group = groupNameService.getGroupFromNames(Optional.of(groupParam));
        groupsDAO.deleteGroup(group);
        response.setStatus(HttpServletResponse.SC_NO_CONTENT);
    }

    @GetMapping(value = {"/ws/jwt/membership/{group:.+}", "/ws/jwt/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
    public void getMembership(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String userId, HttpServletResponse response) throws IOException {
        GroupEntity parent = groupNameService.getGroupFromNames(groupNames);

        List<GroupEntity> groups = membershipManager.getUserGroups(parent, userId);

        try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
            for (String groupName : groupNameService.getGroupsNames(groups)) {
                pw.println(groupNameService.getShortGroupName(groupName, groupNames));
    @PostMapping(value = {"/ws/jwt/membership/{group:.+}", "/ws/jwt/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
    public void addMember(@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;
        }

        GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
        membershipManager.addMember(groupEntity, targetUserId);
    }
    @DeleteMapping(value = {"/ws/jwt/membership/{group:.+}", "/ws/jwt/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
    public void removeMember(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String userId,
            HttpServletRequest request, HttpServletResponse response) throws IOException {

        GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
        membershipManager.removeMember(groupEntity, userId);

        response.setStatus(HttpServletResponse.SC_NO_CONTENT);
    }

    @GetMapping(value = {"/ws/jwt/permission/{group:.+}", "/ws/jwt/permission"}, produces = MediaType.TEXT_PLAIN_VALUE)
    public void getUserPermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") Optional<String> userId, HttpServletRequest request, HttpServletResponse response) throws IOException {
        GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
        if (userId.isPresent()) {
            try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
Sonia Zorba's avatar
Sonia Zorba committed
                for (UserPermission userPermission : searchService.getUserPermission(groupEntity, userId.get(), permissionsManager.getCurrentUserPermissions(groupEntity))) {
                    String group = String.join(".", userPermission.getGroupCompleteName());
                    pw.println(group + " " + userPermission.getPermission());
                }
            }
        } else {
            try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
                for (it.inaf.ia2.gms.model.RapUserPermission up : permissionsManager.getAllPermissions(groupEntity)) {
                    pw.println(up.getUser().getId() + " " + up.getPermission());
                }
    @PostMapping(value = {"/ws/jwt/permission/{group:.+}", "/ws/jwt/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public void addPermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String targetUserId, @RequestParam("permission") Permission permission) throws IOException {
        GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
        permissionsManager.addPermission(groupEntity, targetUserId, permission);
    }
    @PutMapping(value = {"/ws/jwt/permission/{group:.+}", "/ws/jwt/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public void setPermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String targetUserId, @RequestParam("permission") Permission permission) throws IOException {
        GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
        permissionsManager.createOrUpdatePermission(groupEntity, targetUserId, permission);
    @DeleteMapping(value = {"/ws/jwt/permission/{group:.+}", "/ws/jwt/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 = groupNameService.getGroupFromNames(groupNames);
        permissionsManager.removePermission(groupEntity, userId);

        response.setStatus(HttpServletResponse.SC_NO_CONTENT);
    }

    @GetMapping(value = {"/ws/jwt/check-invited-registration", "/check-invited-registration"}, produces = MediaType.TEXT_PLAIN_VALUE)
    public void completeInvitedRegistrationIfNecessary(Principal principal, HttpServletResponse response) throws IOException {

        String userId = principal.getName();

        Set<String> groupIds = new HashSet<>();
        for (InvitedRegistration invitedRegistration : invitedRegistrationManager.completeInvitedRegistrationIfNecessary(userId)) {
            groupIds.addAll(invitedRegistration.getGroupsPermissions().keySet());
        }
        List<GroupEntity> groups = groupsDAO.findGroupsByIds(groupIds);

        if (!groups.isEmpty()) {
            List<String> names = groupNameService.getGroupsNames(groups);
            try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {

                for (String name : names) {
                    pw.println(name);
                }
            }
        }
    }

    @PostMapping(value = {"/ws/jwt/invited-registration", "/invited-registration"}, produces = MediaType.TEXT_PLAIN_VALUE)
    public void addInvitedRegistration(@RequestParam("token_hash") String tokenHash, @RequestParam("email") String email,
            @RequestParam("groups") String groupNamesAndPermissionsParam, HttpServletResponse response) {

        Map<GroupEntity, Permission> groupsPermissions = new HashMap<>();

        for (String param : groupNamesAndPermissionsParam.split("\n")) {
            if (!param.isEmpty()) {
                int lastSpaceIndex = param.lastIndexOf(" ");
                String groupName = param.substring(0, lastSpaceIndex);
                Permission permission = Permission.valueOf(param.substring(lastSpaceIndex + 1));
                GroupEntity groupEntity = groupNameService.getGroupFromNames(Optional.of(groupName));
                groupsPermissions.put(groupEntity, permission);
            }
        }

        invitedRegistrationManager.addInvitedRegistration(tokenHash, email, groupsPermissions);

        response.setStatus(HttpServletResponse.SC_CREATED);
    }

    @GetMapping(value = {"/ws/jwt/email/{group:.+}", "/email/{group:.+}"}, produces = MediaType.TEXT_PLAIN_VALUE)
    public void getEmailOfMembers(@PathVariable("group") String groupNames, @RequestParam("permission") Optional<Permission> permission, HttpServletResponse response) throws IOException {

        GroupEntity groupEntity = groupNameService.getGroupFromNames(Optional.of(groupNames));

        Set<String> selectedUserIds = null;
        if (permission.isPresent()) {
            Permission desiredPermission = permission.get();
            selectedUserIds = new HashSet<>();
            for (PermissionEntity groupsPermission : permissionsDAO.getGroupsPermissions(groupEntity.getId())) {
                if (Permission.includes(groupsPermission.getPermission(), desiredPermission)) {
                    selectedUserIds.add(groupsPermission.getUserId());
                }
            }
        }

        try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
            for (RapUser member : membershipManager.getMembers(groupEntity)) {
                if (selectedUserIds == null || selectedUserIds.contains(member.getId())) {
                    pw.println(member.getPrimaryEmailAddress());
    @PostMapping(value = {"/ws/jwt/join", "/join"}, produces = MediaType.APPLICATION_JSON_VALUE)
Sonia Zorba's avatar
Sonia Zorba committed
    public ResponseEntity<?> join(RapPrincipal principal) {

        String fromUser = principal.getName();
        String toUser = principal.getAlternativeName();

        if (toUser == null) {
            throw new BadRequestException("Missing alternative subject");
        }

        String mergedId = joinService.join(fromUser, toUser);
Sonia Zorba's avatar
Sonia Zorba committed

        Map<String, String> responseBody = new HashMap<>();
        responseBody.put("mergedId", mergedId);
Sonia Zorba's avatar
Sonia Zorba committed
        return ResponseEntity.ok(responseBody);
    }