org.georchestra.console.ws.backoffice.roles.RolesController.java Source code

Java tutorial

Introduction

Here is the source code for org.georchestra.console.ws.backoffice.roles.RolesController.java

Source

/*
 * Copyright (C) 2009-2017 by the geOrchestra PSC
 *
 * This file is part of geOrchestra.
 *
 * geOrchestra is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * geOrchestra.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.georchestra.console.ws.backoffice.roles;

import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.georchestra.console.dao.AdvancedDelegationDao;
import org.georchestra.console.dao.DelegationDao;
import org.georchestra.console.ds.AccountDao;
import org.georchestra.console.ds.DataServiceException;
import org.georchestra.console.ds.DuplicatedCommonNameException;
import org.georchestra.console.ds.ProtectedUserFilter;
import org.georchestra.console.ds.RoleDao;
import org.georchestra.console.dto.Account;
import org.georchestra.console.dto.Role;
import org.georchestra.console.dto.RoleFactory;
import org.georchestra.console.dto.RoleSchema;
import org.georchestra.console.model.DelegationEntry;
import org.georchestra.console.ws.backoffice.users.UserRule;
import org.georchestra.console.ws.backoffice.utils.RequestUtil;
import org.georchestra.console.ws.backoffice.utils.ResponseUtil;
import org.georchestra.lib.file.FileUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.NameNotFoundException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

/**
 * Web Services to maintain the Roles information.
 *
 * @author Mauricio Pazos
 *
 */

@Controller
public class RolesController {

    private static final Log LOG = LogFactory.getLog(RolesController.class.getName());

    public static final GrantedAuthority ROLE_SUPERUSER = new SimpleGrantedAuthority("ROLE_SUPERUSER");

    private static final String BASE_MAPPING = "/private";
    private static final String BASE_RESOURCE = "roles";
    private static final String REQUEST_MAPPING = BASE_MAPPING + "/" + BASE_RESOURCE;

    private static final String DUPLICATED_COMMON_NAME = "duplicated_common_name";
    private static final String NOT_FOUND = "not_found";
    private static final String USER_NOT_FOUND = "user_not_found";
    private static final String ILLEGAL_CHARACTER = "illegal_character";

    public static final String VIRTUAL_TEMPORARY_ROLE_NAME = "TEMPORARY";
    private static final String VIRTUAL_TEMPORARY_ROLE_DESCRIPTION = "Virtual role that contains all temporary users";

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private AdvancedDelegationDao advancedDelegationDao;
    @Autowired
    private DelegationDao delegationDao;

    private RoleDao roleDao;
    private ProtectedUserFilter filter;

    /**
    * Builds a JSON response in case of error.
    *
    * @param mesg
    *            a descriptive message of the encountered error.
    * @return a string of the response.
    *
    * TODO: This code sounds pretty similar to what is done in
    * ResponseUtil.java:buildResponseMessage() and might deserve
    * a refactor.
    */

    private String buildErrorResponse(String mesg) {
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("success", false);
        map.put("error_message", mesg);
        return new JSONObject(map).toString();
    }

    @Autowired
    public RolesController(RoleDao dao, UserRule userRule) {
        this.roleDao = dao;
        this.filter = new ProtectedUserFilter(userRule.getListUidProtected());
    }

    /**
     * Returns all roles. Each roles will contains its list of users.
     *
     * @throws IOException
     */
    @RequestMapping(value = REQUEST_MAPPING, method = RequestMethod.GET, produces = "application/json; charset=utf-8")
    @PostFilter("hasPermission(filterObject, 'read')")
    @ResponseBody
    public List<Role> findAll() throws DataServiceException {
        List<Role> list = this.roleDao.findAll();
        list.add(this.generateTemporaryRole());
        return list;
    }

    /**
     * Returns the detailed information of the role, with its list of users.
     *
     * <p>
     * If the role identifier is not present in the ldap store an {@link IOException} will be throw.
     * </p>
     * <p>
     * URL Format: [BASE_MAPPING]/roles/{cn}
     * </p>
     * <p>
     * Example: [BASE_MAPPING]/roles/role44
     * </p>
     *
     * @param cn Comon name of role
     * @throws IOException
     */
    @RequestMapping(value = REQUEST_MAPPING
            + "/{cn:.+}", method = RequestMethod.GET, produces = "application/json; charset=utf-8")
    @ResponseBody
    public Role findByCN(@PathVariable String cn) throws DataServiceException {
        Role res;

        if (cn.equals(RolesController.VIRTUAL_TEMPORARY_ROLE_NAME))
            res = this.generateTemporaryRole();
        else
            res = this.roleDao.findByCommonName(cn);
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (!auth.getAuthorities().contains(ROLE_SUPERUSER)) {
            if (!Arrays.asList(this.delegationDao.findOne(auth.getName()).getRoles()).contains(cn))
                throw new AccessDeniedException("Role not under delegation");
            res.getUserList().retainAll(this.advancedDelegationDao.findUsersUnderDelegation(auth.getName()));
        }
        return res;
    }

    private Role generateTemporaryRole() {
        Role res = RoleFactory.create(RolesController.VIRTUAL_TEMPORARY_ROLE_NAME,
                RolesController.VIRTUAL_TEMPORARY_ROLE_DESCRIPTION, false);
        for (Account a : this.accountDao.findByShadowExpire())
            res.addUser(a.getUid());
        return res;
    }

    /**
     *
     * <p>
     * Creates a new role.
     * </p>
     *
     * <pre>
     * <b>Request</b>
     *
     * role data:
     * {
      *   "cn": "Name of the role"
      *   "description": "Description for the role"
      *   }
     * </pre>
     * <pre>
     * <b>Response</b>
     *
     * <b>- Success case</b>
     *
     * {
     *  "cn": "Name of the role",
     *  "description": "Description for the role"
     * }
     * </pre>
     *
     * @param request
     * @param response
     * @throws IOException
     */
    @RequestMapping(value = REQUEST_MAPPING, method = RequestMethod.POST)
    @PreAuthorize("hasRole('SUPERUSER')")
    public void create(HttpServletRequest request, HttpServletResponse response) throws IOException {

        try {
            Role role = createRoleFromRequestBody(request.getInputStream());
            this.roleDao.insert(role);
            RoleResponse roleResponse = new RoleResponse(role, this.filter);
            String jsonResponse = roleResponse.asJsonString();
            ResponseUtil.buildResponse(response, jsonResponse, HttpServletResponse.SC_OK);

        } catch (DuplicatedCommonNameException emailex) {

            String jsonResponse = ResponseUtil.buildResponseMessage(Boolean.FALSE, DUPLICATED_COMMON_NAME);

            ResponseUtil.buildResponse(response, jsonResponse, HttpServletResponse.SC_CONFLICT);

        } catch (DataServiceException dsex) {
            LOG.error(dsex.getMessage());
            ResponseUtil.buildResponse(response, buildErrorResponse(dsex.getMessage()),
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            throw new IOException(dsex);
        } catch (IllegalArgumentException ex) {
            String jsonResponse = ResponseUtil.buildResponseMessage(Boolean.FALSE, ILLEGAL_CHARACTER);
            ResponseUtil.buildResponse(response, jsonResponse, HttpServletResponse.SC_CONFLICT);
        }
    }

    /**
     * Deletes the role.
     *
     * The request format is:
     * <pre>
     * [BASE_MAPPING]/roles/{cn}
     *
     * Where <b>cn</b> is the name of role to delete.
     * </pre>
     *
     * @param response
     * @param cn Common name of role to delete
     * @throws IOException
     */
    @RequestMapping(value = REQUEST_MAPPING + "/{cn:.+}", method = RequestMethod.DELETE)
    @PreAuthorize("hasRole('SUPERUSER')")
    public void delete(HttpServletResponse response, @PathVariable String cn) throws IOException {
        try {

            // Check if this role is part of a delegation
            for (DelegationEntry delegation : this.advancedDelegationDao.findByRole(cn)) {
                delegation.removeRole(cn);
                this.delegationDao.save(delegation);
            }

            this.roleDao.delete(cn);

            ResponseUtil.writeSuccess(response);

        } catch (NameNotFoundException e) {
            LOG.error(e.getMessage());
            ResponseUtil.buildResponse(response, buildErrorResponse(e.getMessage()),
                    HttpServletResponse.SC_NOT_FOUND);
        } catch (DataServiceException e) {
            LOG.error(e.getMessage());
            ResponseUtil.buildResponse(response, buildErrorResponse(e.getMessage()),
                    HttpServletResponse.SC_BAD_REQUEST);

        } catch (Exception e) {
            LOG.error(e.getMessage());
            ResponseUtil.buildResponse(response, buildErrorResponse(e.getMessage()),
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            throw new IOException(e);
        }
    }

    /**
     * Modifies the role using the fields provided in the request body.
     * <p>
     * The fields that are not present in the parameters will remain untouched
     * in the LDAP store.
     * </p>
     * 
     * <pre>
     * The request format is:
     * [BASE_MAPPING]/roles/{cn}
     *
     * Where <b>cn</b> is the name of role to update.
     * </pre>
     * <p>
     * The request body should contains a the fields to modify using the JSON
     * syntax.
     * </p>
     * <p>
     * Example:
     * </p>
     * 
     * <pre>
     * <b>Request</b>
     * [BASE_MAPPING]/roles/users
     *
     * <b>Body request: </b>
     * role data:
     * {
     *   "cn": "newName"
     *   "description": "new Description"
     *   }
     *
     * </pre>
     *
     * @param request
     *            [BASE_MAPPING]/roles/{cn} body request {"cn": value1,
     *            "description": value2 }
     * @param response
     *
     * @throws IOException
     *             if the uid does not exist or fails to access to the LDAP
     *             store.
     */
    @RequestMapping(value = REQUEST_MAPPING + "/{cn:.+}", method = RequestMethod.PUT)
    @PreAuthorize("hasRole('SUPERUSER')")
    public void update(HttpServletRequest request, HttpServletResponse response, @PathVariable String cn)
            throws IOException {

        // searches the role
        Role role;
        try {
            role = this.roleDao.findByCommonName(cn);
        } catch (NameNotFoundException e) {
            ResponseUtil.writeError(response, NOT_FOUND);
            return;
        } catch (DataServiceException e) {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            throw new IOException(e);
        }

        // modifies the role data
        try {
            final Role modified = modifyRole(role, request.getInputStream());

            this.roleDao.update(cn, modified);

            RoleResponse roleResponse = new RoleResponse(role, this.filter);

            String jsonResponse = roleResponse.asJsonString();

            ResponseUtil.buildResponse(response, jsonResponse, HttpServletResponse.SC_OK);

            ResponseUtil.writeSuccess(response);

        } catch (NameNotFoundException e) {

            ResponseUtil.buildResponse(response, ResponseUtil.buildResponseMessage(Boolean.FALSE, NOT_FOUND),
                    HttpServletResponse.SC_NOT_FOUND);

            return;

        } catch (DuplicatedCommonNameException e) {

            String jsonResponse = ResponseUtil.buildResponseMessage(Boolean.FALSE, DUPLICATED_COMMON_NAME);

            ResponseUtil.buildResponse(response, jsonResponse, HttpServletResponse.SC_CONFLICT);

            return;

        } catch (DataServiceException e) {
            LOG.error(e.getMessage());
            ResponseUtil.buildResponse(response, buildErrorResponse(e.getMessage()),
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            throw new IOException(e);
        }
    }

    /**
     * Updates the users of role. This method will add or delete the role of users from the list of roles.
     *
     * @param request   request [BASE_MAPPING]/roles_users body request {"users": [u1,u2,u3], "PUT": [g1,g2], "DELETE":[g3,g4] }
     * @param response
     * @throws IOException
     */
    @RequestMapping(value = BASE_MAPPING + "/roles_users", method = RequestMethod.POST)
    public void updateUsers(HttpServletRequest request, HttpServletResponse response)
            throws AccessDeniedException, IOException, JSONException, DataServiceException {

        JSONObject json = new JSONObject(FileUtils.asString(request.getInputStream()));

        List<String> users = createUserList(json, "users");
        List<String> putRole = createUserList(json, "PUT");
        List<String> deleteRole = createUserList(json, "DELETE");

        // Don't allow modification of ORGADMIN role
        if (putRole.contains("ORGADMIN") || deleteRole.contains("ORGADMIN"))
            throw new IllegalArgumentException("ORGADMIN role cannot be add or delete");

        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (!auth.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_SUPERUSER")))
            this.checkAuthorization(auth.getName(), users, putRole, deleteRole);

        this.roleDao.addUsersInRoles(putRole, users, auth.getName());
        this.roleDao.deleteUsersInRoles(deleteRole, users, auth.getName());

        ResponseUtil.writeSuccess(response);
    }

    public void checkAuthorization(String delegatedAdmin, List<String> users, List<String> putRole,
            List<String> deleteRole) throws AccessDeniedException {
        // Verify authorization
        Set<String> usersUnderDelegation = this.advancedDelegationDao.findUsersUnderDelegation(delegatedAdmin);
        if (!usersUnderDelegation.containsAll(users))
            throw new AccessDeniedException("Some users are not under delegation");
        DelegationEntry delegation = this.delegationDao.findOne(delegatedAdmin);
        if (!Arrays.asList(delegation.getRoles()).containsAll(putRole))
            throw new AccessDeniedException("Some roles are not under delegation (put)");
        if (!Arrays.asList(delegation.getRoles()).containsAll(deleteRole))
            throw new AccessDeniedException("Some roles are not under delegation (delete)");

    }

    private List<String> createUserList(JSONObject json, String arrayKey) throws IOException {

        try {

            List<String> list = new LinkedList<String>();

            JSONArray jsonArray = json.getJSONArray(arrayKey);
            for (int i = 0; i < jsonArray.length(); i++) {

                list.add(jsonArray.getString(i));
            }

            return list;

        } catch (Exception e) {
            LOG.error(e.getMessage());
            throw new IOException(e);
        }
    }

    /**
     * Modifies the original field using the values in the inputStream.
     *
     * @param role role to modify
     * @param inputStream contains the new values
     *
     * @return the {@link Role} modified
     */
    private Role modifyRole(Role role, ServletInputStream inputStream) throws IOException {

        String strRole = FileUtils.asString(inputStream);
        JSONObject json;
        try {
            json = new JSONObject(strRole);
        } catch (JSONException e) {
            LOG.error(e.getMessage());
            throw new IOException(e);
        }

        String cn = RequestUtil.getFieldValue(json, RoleSchema.COMMON_NAME_KEY);
        if (cn != null) {
            role.setName(cn);
        }

        String description = RequestUtil.getFieldValue(json, RoleSchema.DESCRIPTION_KEY);
        if (description != null) {
            role.setDescription(description);
        }

        Boolean isFavorite = RequestUtil.getBooleanFieldValue(json, RoleSchema.FAVORITE_JSON_KEY);
        if (isFavorite != null)
            role.setFavorite(isFavorite);

        return role;
    }

    private Role createRoleFromRequestBody(ServletInputStream is) throws IOException, IllegalArgumentException {
        try {
            String strRole = FileUtils.asString(is);
            JSONObject json = new JSONObject(strRole);

            String commonName = RequestUtil.getFieldValue(json, RoleSchema.COMMON_NAME_KEY);
            if (commonName == null) {
                throw new IllegalArgumentException(RoleSchema.COMMON_NAME_KEY + " is required");
            }

            // Capitalize role name and check format
            commonName = commonName.toUpperCase();
            Pattern p = Pattern.compile("[A-Z0-9_]+");
            if (!p.matcher(commonName).matches())
                throw new IllegalArgumentException(RoleSchema.COMMON_NAME_KEY
                        + " should only contain uppercased letters, digits and underscores");

            String description = RequestUtil.getFieldValue(json, RoleSchema.DESCRIPTION_KEY);
            Boolean isFavorite = RequestUtil.getBooleanFieldValue(json, RoleSchema.FAVORITE_JSON_KEY);

            Role g = RoleFactory.create(commonName, description, isFavorite);

            return g;

        } catch (IllegalArgumentException e) {
            throw e;
        } catch (Exception e) {
            LOG.error(e.getMessage());
            throw new IOException(e);
        }
    }

    /**
     * Method used for testing convenience.
     * @param gd
     */
    public void setRoleDao(RoleDao gd) {
        roleDao = gd;
    }

    /**
     * Method used for testing convenience.
     * @param ad
     */
    public void setAccountDao(AccountDao ad) {
        this.accountDao = ad;
    }

    public AdvancedDelegationDao getAdvancedDelegationDao() {
        return advancedDelegationDao;
    }

    public void setAdvancedDelegationDao(AdvancedDelegationDao advancedDelegationDao) {
        this.advancedDelegationDao = advancedDelegationDao;
    }

    public DelegationDao getDelegationDao() {
        return delegationDao;
    }

    public void setDelegationDao(DelegationDao delegationDao) {
        this.delegationDao = delegationDao;
    }
}