Java tutorial
/** * 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() { } }