org.geoserver.rest.security.AbstractAclController.java Source code

Java tutorial

Introduction

Here is the source code for org.geoserver.rest.security.AbstractAclController.java

Source

/* (c) 2017 Open Source Geospatial Foundation - all rights reserved
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geoserver.rest.security;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.rest.ResourceNotFoundException;
import org.geoserver.rest.RestException;
import org.geoserver.rest.util.MediaTypeExtensions;
import org.geoserver.security.GeoServerSecurityManager;
import org.geoserver.security.impl.AbstractAccessRuleDAO;
import org.geoserver.security.impl.DataAccessRule;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

public abstract class AbstractAclController<DAO extends AbstractAccessRuleDAO<Comparable<?>>> {

    public static final String ANY = "*";

    DAO ruleDAO;

    AbstractAclController(DAO ruleDAO) {
        this.ruleDAO = ruleDAO;
    }

    GeoServerSecurityManager getManager() {
        return GeoServerExtensions.bean(GeoServerSecurityManager.class);
    }

    @GetMapping(produces = { MediaType.APPLICATION_JSON_VALUE, MediaTypeExtensions.TEXT_JSON_VALUE,
            MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE })
    @ResponseBody
    public RuleMap rulesGet() throws IOException {
        checkUserIsAdmin();

        try {
            return getMap();
        } catch (Exception e) {
            throw createRestException(e);
        }
    }

    @PostMapping(consumes = { MediaType.APPLICATION_JSON_VALUE, MediaTypeExtensions.TEXT_JSON_VALUE,
            MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE })
    public void rulesPost(@RequestBody RuleMap map) throws IOException {
        checkUserIsAdmin();

        try {
            postMap(map);
        } catch (Exception e) {
            throw createRestException(e);
        }
    }

    @PutMapping(consumes = { MediaType.APPLICATION_JSON_VALUE, MediaTypeExtensions.TEXT_JSON_VALUE,
            MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE })
    public void rulesPut(@RequestBody RuleMap map) throws IOException {
        checkUserIsAdmin();

        try {
            putMap(map);
        } catch (Exception e) {
            throw createRestException(e);
        }
    }

    @DeleteMapping(path = "/**")
    public void rulesDelete(HttpServletRequest request) throws UnsupportedEncodingException {
        checkUserIsAdmin();

        String thePath = request.getPathInfo();
        String ruleString = thePath.substring(getBasePath().length() + 1);
        ruleString = URLDecoder.decode(ruleString, "utf-8");

        String msg = validateRuleKey(ruleString);
        if (msg != null)
            throw new RestException(msg, HttpStatus.UNPROCESSABLE_ENTITY);

        Comparable<?> rule = null;
        for (Comparable<?> ruleCandidate : ruleDAO.getRules()) {
            if (ruleString.equals(keyFor(ruleCandidate))) {
                rule = ruleCandidate;
                break;
            }
        }

        if (rule == null) {
            throw new ResourceNotFoundException("Rule not found: " + ruleString);
        }

        try {
            ruleDAO.removeRule(rule);
            ruleDAO.storeRules();

        } catch (Exception e) {
            throw createRestException(e);
        }
    }

    /**
     * Returns the base path of the ACL resource
     * 
     * @return
     */
    protected abstract String getBasePath();

    protected void checkUserIsAdmin() {
        if (!getManager().checkAuthenticationForAdminRole()) {
            throw new RestException("Amdinistrative priveleges required", HttpStatus.FORBIDDEN);
        }
    }

    /**
     * Adds a rule to a map
     * 
     * @param rule
     * @param map
     */
    protected abstract void addRuleToMap(Comparable rule, Map<String, String> map);

    public RuleMap<String, String> getMap() throws Exception {
        RuleMap<String, String> result = new RuleMap<>();
        for (Comparable<?> rule : ruleDAO.getRules()) {
            addRuleToMap(rule, result);
        }
        return result;
    }

    /**
     * Calculate the the intersection of the keys
     *
     * @param map
     */
    protected Set<Object> intersection(Map map) {

        Set<Object> result = new HashSet<>();

        Set<Object> ruleKeys = new HashSet<>();
        for (Comparable<?> rule : ruleDAO.getRules()) {
            ruleKeys.add(keyFor(rule));
        }

        if (ruleKeys.isEmpty() || map.isEmpty())
            return result;

        for (Object key : ruleKeys) {
            if (map.containsKey(key))
                result.add(key);
        }
        return result;
    }

    /**
     * Calculate the keys not contained in the rule data access object
     *
     * @param map
     */
    protected Set<Object> nonExistingKeys(Map map) {

        List<Comparable<?>> rules = ruleDAO.getRules();

        if (rules.isEmpty())
            return map.keySet();

        Set<Object> result = new HashSet<>();
        Set<Object> ruleKeys = new HashSet<>();
        for (Comparable<?> rule : rules) {
            ruleKeys.add(keyFor(rule));
        }
        for (Object key : map.keySet()) {
            if (!ruleKeys.contains(key))
                result.add(key);
        }
        return result;
    }

    /**
     * Returns the key string for a rule
     * 
     * @param rule
     *
     */
    protected abstract String keyFor(Comparable<?> rule);

    /**
     * Validate a rule, return an error message or <code>null</code> if the rule is ok
     * 
     * @param ruleKey ,ruleValue
     */
    protected String validateRule(String ruleKey, String ruleValue) {
        return validateRuleKey(ruleKey);
        // TODO
        // roles are not validated at the moment
    }

    /**
     * Validates the string representation of a rule key. Return an error message or <code>null</code> if the rule is ok
     * 
     * @param ruleKey
     *
     */
    protected abstract String validateRuleKey(String ruleKey);

    /**
     * Convert an {@link Entry} to a rule object
     * 
     * @param entry
     *
     */
    protected abstract Comparable convertEntryToRule(Entry<String, String> entry);

    /**
     * Validates the string representation of rule keys and values
     * 
     * @param ruleMap
     */
    protected void validateMap(Map<String, String> ruleMap) {
        for (Entry<String, String> entry : ruleMap.entrySet()) {
            String msg = validateRule(entry.getKey(), entry.getValue());
            if (msg != null) {
                throw new RestException(msg, HttpStatus.UNPROCESSABLE_ENTITY);
            }
        }
    }

    protected void postMap(Map map) throws Exception {

        validateMap(map);

        Set<Object> commonKeys = intersection(map);

        if (!commonKeys.isEmpty()) {
            String msg = "Already existing rules: " + StringUtils.join(commonKeys.iterator(), ",");
            throw new RestException(msg, HttpStatus.CONFLICT);
        }

        for (Object entry : map.entrySet()) {
            Comparable rule = convertEntryToRule((Entry<String, String>) entry);
            ruleDAO.addRule(rule);
        }
        ruleDAO.storeRules();

    }

    protected void putMap(Map map) throws Exception {
        validateMap(map);
        Set<Object> nonExisting = nonExistingKeys(map);

        if (!nonExisting.isEmpty()) {
            String msg = "Unknown rules: " + StringUtils.join(nonExisting.iterator(), ",");
            throw new RestException(msg, HttpStatus.CONFLICT);
        }

        for (Object entry : map.entrySet()) {
            Comparable rule = convertEntryToRule((Entry<String, String>) entry);
            // TODO, will not work for REST
            ruleDAO.removeRule(rule);
            ruleDAO.addRule(rule);
        }
        ruleDAO.storeRules();
    }

    /**
     * Parses a comma separated list of roles into a set of strings, with special handling for the {@link DataAccessRule#ANY} role
     * 
     * @param roleCsv Comma separated list of roles.
     */
    protected Set<String> parseRoles(String roleCsv) {
        // regexp: treat extra spaces as separators, ignore extra commas
        // "a,,b, ,, c" --> ["a","b","c"]
        String[] rolesArray = roleCsv.split("[\\s,]+");
        Set<String> roles = new HashSet<>(rolesArray.length);
        roles.addAll(Arrays.asList(rolesArray));

        // if any of the roles is * we just remove all of the others
        for (String role : roles) {
            if (ANY.equals(role))
                return Collections.singleton("*");
        }

        return roles;
    }

    protected RestException createRestException(Exception ex) {
        if (ex instanceof RestException) {
            return (RestException) ex; // do nothing
        } else {
            return new RestException("", HttpStatus.INTERNAL_SERVER_ERROR, ex);
        }
    }
}