org.apache.marmotta.platform.security.services.SecurityServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.marmotta.platform.security.services.SecurityServiceImpl.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.marmotta.platform.security.services;

import org.apache.marmotta.platform.security.api.SecurityService;
import org.apache.marmotta.platform.security.model.SecurityConstraint;
import org.apache.marmotta.platform.security.util.SubnetInfo;
import com.google.common.collect.Lists;
import org.apache.marmotta.platform.core.api.config.ConfigurationService;
import org.apache.marmotta.platform.core.events.ConfigurationChangedEvent;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.slf4j.Logger;
import sun.net.util.IPAddressUtil;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import static org.apache.marmotta.platform.security.model.HTTPMethods.parse;

/**
 * Security Service default implementartion
 * 
 * @author Sebastian Schaffert
 */
@ApplicationScoped
public class SecurityServiceImpl implements SecurityService {

    @Inject
    private Logger log;

    @Inject
    private ConfigurationService configurationService;

    private boolean profileLoading = false;

    private List<SecurityConstraint> constraints;

    @PostConstruct
    public void initialise() {
        log.info("Initialising Security Service;  Access control is {}.",
                configurationService.getBooleanConfiguration("security.enabled", true) ? "enabled" : "disabled");
        initSecurityConstraints();
    }

    /**
     * Parse the security configuration contained in the configuration file into a list of SecurityConstraints, ordered
     * by priority. This list will be evaluated for each request to the system.
     */
    private void initSecurityConstraints() {
        constraints = new ArrayList<SecurityConstraint>();

        if (configurationService.getBooleanConfiguration("security.enabled", true)) {

            for (String type : Lists.newArrayList("permission", "restriction")) {
                // determine the names of constraints that are configured
                Set<String> configNames = new HashSet<String>();
                for (String key : configurationService.listConfigurationKeys("security." + type)) {
                    String[] components = key.split("\\.");
                    if (components.length > 2) {
                        configNames.add(components[2]);
                    }
                }

                for (String configName : configNames) {
                    String keyPrefix = "security." + type + "." + configName;

                    String pattern = configurationService.getStringConfiguration(keyPrefix + ".pattern");
                    boolean enabled = configurationService.getBooleanConfiguration(keyPrefix + ".enabled", true);
                    int priority = configurationService.getIntConfiguration(keyPrefix + ".priority", 1);
                    List<String> methods = configurationService.getListConfiguration(keyPrefix + ".methods");
                    List<String> hosts = configurationService.getListConfiguration(keyPrefix + ".host");
                    List<String> roles = configurationService.getListConfiguration(keyPrefix + ".roles");

                    SecurityConstraint constraint = new SecurityConstraint(
                            SecurityConstraint.Type.valueOf(type.toUpperCase()), configName, pattern, enabled,
                            priority);
                    constraint.getRoles().addAll(roles);

                    for (String method : methods) {
                        constraint.getMethods().add(parse(method));
                    }

                    constraint.setHostPatterns(parseHostAddresses(hosts));
                    constraints.add(constraint);

                }
            }

            Collections.sort(constraints);

            if (log.isInfoEnabled()) {
                log.info("The following security constraints have been configured:");
                for (SecurityConstraint constraint : constraints) {
                    log.info("-- {}", constraint.toString());
                }

            }
        }
    }

    /**
     * Parse host patterns into subnet information. A host pattern has one of the following forms:
     * <ul>
     *     <li>LOCAL, meaning all local interfaces</li>
     *     <li>x.x.x.x/yy, meaning an IPv4 CIDR address with netmask (number of bits significant for the network, max 32), e.g. 192.168.100.0/24 </li>
     *     <li>xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/yy, meaning an IPv6 CIDR address with netmask (prefix length, max 128)</li>
     * </ul>
     * @param hostPatternStrings
     * @return
     */
    private Set<SubnetInfo> parseHostAddresses(List<String> hostPatternStrings) {
        HashSet<SubnetInfo> hostPatterns = new HashSet<SubnetInfo>();
        for (String host : hostPatternStrings) {
            try {
                // reserved name: LOCAL maps to all local addresses
                if ("LOCAL".equalsIgnoreCase(host)) {
                    try {
                        Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces();
                        while (ifs.hasMoreElements()) {
                            NetworkInterface iface = ifs.nextElement();
                            Enumeration<InetAddress> addrs = iface.getInetAddresses();
                            while (addrs.hasMoreElements()) {
                                InetAddress addr = addrs.nextElement();

                                try {
                                    hostPatterns.add(SubnetInfo.getSubnetInfo(addr));
                                } catch (UnknownHostException e) {
                                    log.warn("could not parse interface address: {}", e.getMessage());
                                }

                            }
                        }
                    } catch (SocketException ex) {
                        log.warn("could not determine local IP addresses, will use 127.0.0.1/24");

                        try {
                            hostPatterns.add(SubnetInfo.getSubnetInfo("127.0.0.1/24")); // IPv4
                            hostPatterns.add(SubnetInfo.getSubnetInfo("::1/128")); // IPv6
                        } catch (UnknownHostException e) {
                            log.error("could not parse localhost address: {}", e.getMessage());
                        }
                    }
                } else if (host.matches("^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+\\./[0-9]+$")) {
                    // CIDR notation
                    try {
                        hostPatterns.add(SubnetInfo.getSubnetInfo(host));
                    } catch (UnknownHostException e) {
                        log.warn("could not parse host specification '{}': {}", host, e.getMessage());
                    }

                } else if (host.matches("^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$")) {
                    // IP address
                    try {
                        hostPatterns.add(SubnetInfo.getSubnetInfo(host + "/32"));
                    } catch (UnknownHostException e) {
                        log.warn("could not parse host specification '{}': {}", host, e.getMessage());
                    }

                } else if (IPAddressUtil.isIPv6LiteralAddress(host)) {
                    // IPv6 address
                    try {
                        hostPatterns.add(SubnetInfo.getSubnetInfo(host));
                    } catch (UnknownHostException e) {
                        log.warn("could not parse host specification '{}': {}", host, e.getMessage());
                    }

                } else {
                    log.warn(
                            "invalid host name specification: {}; please use either CIDR u.v.w.x/zz notation or the keyword LOCAL",
                            host);
                }
            } catch (IllegalArgumentException ex) {
                log.warn("illegal host specification for security constraint {}; not in CIDR notation!", host);
            }
        }
        return hostPatterns;
    }

    /**
     * React to a change in the security configuration, e.g. when the profile is changed.
     * @param event
     */
    public void configurationChangedEvent(@Observes ConfigurationChangedEvent event) {
        if (profileLoading)
            return;

        boolean load = false, init = false;
        for (String key : event.getKeys()) {
            if ("security.profile".equals(key)) {
                load = true;
            } else if (key.startsWith("security")) {
                init = true;
            }
        }

        if (load) {
            loadSecurityProfile(configurationService.getStringConfiguration("security.profile"));
        }
        if (init) {
            log.info("Access Control Filter reloading. Access control is {}.",
                    configurationService.getBooleanConfiguration("security.enabled", true) ? "enabled"
                            : "disabled");
            initSecurityConstraints();
        }
    }

    /**
     * Check whether access is granted for the given request. Returns true if the security system grants access.
     * Returns false if access is denied; in this case, the caller may decide to sent an authorization request to
     * the client.
     *
     * @param request
     * @return true in case the active security constraints grant access, false otherwise
     */
    @Override
    public boolean grantAccess(HttpServletRequest request) {
        if (configurationService.getBooleanConfiguration("security.enabled", true)) {
            if (!configurationService.getBooleanConfiguration("security.configured")) {
                loadSecurityProfile(configurationService.getStringConfiguration("security.profile"));
            }

            for (SecurityConstraint constraint : constraints) {
                if (constraint.matches(request)) {
                    if (constraint.getType() == SecurityConstraint.Type.PERMISSION) {
                        log.debug("access to {} granted; {}", request.getRequestURL(), constraint);
                        return true;
                    } else {
                        log.debug("access to {} denied; {}", request.getRequestURL(), constraint);
                        return false;
                    }
                }
            }

            log.debug("access to {} denied; no rule matched", request.getRequestURL());
            return false;
        } else
            return true;
    }

    /**
     * Load a pre-configured security profile from the classpath. When calling this method, the service will
     * look for files called security-profile.<name>.properties and replace all existing security constraints by
     * the new security constraints.
     *
     * @param profile
     */
    @Override
    public void loadSecurityProfile(String profile) {
        profileLoading = true;

        LinkedHashSet<String> profiles = new LinkedHashSet<String>();
        Configuration securityConfig = loadProfile(profile, profiles);
        if (securityConfig != null) {
            // remove all configuration keys that define permissions or restrictions
            for (String type : Lists.newArrayList("permission", "restriction")) {
                // determine the names of constraints that are configured
                for (String key : configurationService.listConfigurationKeys("security." + type)) {
                    configurationService.removeConfiguration(key);
                }
            }

            for (Iterator<String> keys = securityConfig.getKeys(); keys.hasNext();) {
                String key = keys.next();

                configurationService.setConfigurationWithoutEvent(key, securityConfig.getProperty(key));
            }

            configurationService.setConfigurationWithoutEvent("security.configured", true);
        }

        profileLoading = false;

        initSecurityConstraints();
    }

    private Configuration loadProfile(String profile, LinkedHashSet<String> profiles) {
        URL securityConfigUrl = this.getClass().getClassLoader()
                .getResource("security-profile." + profile + ".properties");
        if (securityConfigUrl != null) {
            try {
                Configuration securityConfig = null;
                securityConfig = new PropertiesConfiguration(securityConfigUrl);

                if (securityConfig.containsKey("security.profile.base")) {
                    final String baseP = securityConfig.getString("security.profile.base");
                    if (profiles.contains(baseP)) {
                        log.warn("Cycle in security configuration detected: {} -> {}", profiles, baseP);
                        return securityConfig;
                    } else {
                        profiles.add(baseP);
                        final Configuration baseProfile = loadProfile(baseP, profiles);

                        for (Iterator<String> keys = securityConfig.getKeys(); keys.hasNext();) {
                            String key = keys.next();

                            baseProfile.setProperty(key, securityConfig.getProperty(key));
                        }
                        return baseProfile;
                    }
                } else {
                    return securityConfig;
                }
            } catch (ConfigurationException e) {
                log.error("error parsing security-profile.{}.properties file at {}: {}",
                        new Object[] { profile, securityConfigUrl, e.getMessage() });
            }

        }
        return null;
    }

    @Override
    public List<SecurityConstraint> listSecurityConstraints() {
        return constraints;
    }

    /**
     * Does nothing, just ensures the security service is properly initialised.
     * TODO: this is a workaround and should be fixed differently.
     */
    @Override
    public void ping() {

    }
}