blob: b935195679914589e09600c5990f9b67e9c169a8 [file] [log] [blame]
/*
* 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.geronimo.web.security;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.security.jacc.PolicyConfiguration;
import javax.security.jacc.PolicyContextException;
import javax.security.jacc.WebResourcePermission;
import javax.security.jacc.WebRoleRefPermission;
import javax.security.jacc.WebUserDataPermission;
import org.apache.geronimo.security.jacc.ComponentPermissions;
import org.apache.geronimo.web.info.SecurityConstraintInfo;
import org.apache.geronimo.web.info.SecurityRoleRefInfo;
import org.apache.geronimo.web.info.ServletInfo;
import org.apache.geronimo.web.info.WebAppInfo;
import org.apache.geronimo.web.info.WebResourceCollectionInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @version $Rev$ $Date$
*/
public class SpecSecurityBuilder {
private static final Logger logger = LoggerFactory.getLogger(SpecSecurityBuilder.class);
private final Set<String> securityRoles = new HashSet<String>();
private final Map<String, URLPattern> uncheckedPatterns = new HashMap<String, URLPattern>();
private final Map<UncheckedItem, HTTPMethods> uncheckedResourcePatterns = new HashMap<UncheckedItem, HTTPMethods>();
private final Map<UncheckedItem, HTTPMethods> uncheckedUserPatterns = new HashMap<UncheckedItem, HTTPMethods>();
private final Map<String, URLPattern> excludedPatterns = new HashMap<String, URLPattern>();
private final Map<String, Map<String, URLPattern>> rolesPatterns = new HashMap<String, Map<String, URLPattern>>();
private final Set<URLPattern> allSet = new HashSet<URLPattern>();
private final Map<String, URLPattern> allMap = new HashMap<String, URLPattern>(); //uncheckedPatterns union excludedPatterns union rolesPatterns.
private final RecordingPolicyConfiguration policyConfiguration = new RecordingPolicyConfiguration(true);
private WebAppInfo webAppInfo;
public SpecSecurityBuilder(WebAppInfo webAppInfo) {
this.webAppInfo = webAppInfo;
}
public ComponentPermissions buildSpecSecurityConfig() {
securityRoles.addAll(webAppInfo.securityRoles);
try {
for (ServletInfo servlet : webAppInfo.servlets) {
processRoleRefPermissions(servlet);
}
//add the role-ref permissions for unmapped jsps
addUnmappedJSPPermissions();
analyzeSecurityConstraints(webAppInfo.securityConstraints);
removeExcludedDups();
return buildComponentPermissions();
} catch (PolicyContextException e) {
throw new IllegalStateException("Should not happen", e);
}
}
private void analyzeSecurityConstraints(List<SecurityConstraintInfo> securityConstraints) {
for (SecurityConstraintInfo securityConstraint : securityConstraints) {
Map<String, URLPattern> currentPatterns = null;
Set<String> roleNames = null;
if (securityConstraint.authConstraint != null) {
if (securityConstraint.authConstraint.roleNames.size() == 0) {
currentPatterns = excludedPatterns;
} else {
roleNames = new HashSet<String>(securityConstraint.authConstraint.roleNames);
if(roleNames.remove("*")) {
roleNames.addAll(securityRoles);
}
}
} else {
currentPatterns = uncheckedPatterns;
}
String transport = securityConstraint.userDataConstraint == null ? "NONE" : securityConstraint.userDataConstraint;
boolean isRolebasedPatten = (currentPatterns == null);
for (WebResourceCollectionInfo webResourceCollection : securityConstraint.webResourceCollections) {
//Calculate HTTP methods list
for (String urlPattern : webResourceCollection.urlPatterns) {
if (isRolebasedPatten) {
for (String roleName : roleNames) {
Map<String, URLPattern> currentRolePatterns = rolesPatterns.get(roleName);
if (currentRolePatterns == null) {
currentRolePatterns = new HashMap<String, URLPattern>();
rolesPatterns.put(roleName, currentRolePatterns);
}
analyzeURLPattern(urlPattern, webResourceCollection.httpMethods, webResourceCollection.omission, transport, currentRolePatterns);
}
} else {
analyzeURLPattern(urlPattern, webResourceCollection.httpMethods, webResourceCollection.omission, transport, currentPatterns);
}
URLPattern allPattern = allMap.get(urlPattern);
if (allPattern == null) {
allPattern = new URLPattern(urlPattern, webResourceCollection.httpMethods, webResourceCollection.omission);
allSet.add(allPattern);
allMap.put(urlPattern, allPattern);
} else {
allPattern.addMethods(webResourceCollection.httpMethods, webResourceCollection.omission);
}
}
}
}
}
private void analyzeURLPattern(String urlPattern, Set<String> httpMethods, boolean omission, String transport, Map<String, URLPattern> currentPatterns) {
URLPattern pattern = currentPatterns.get(urlPattern);
if (pattern == null) {
pattern = new URLPattern(urlPattern, httpMethods, omission);
currentPatterns.put(urlPattern, pattern);
} else {
pattern.addMethods(httpMethods, omission);
}
pattern.setTransport(transport);
}
private void removeExcludedDups() {
for (Map.Entry<String, URLPattern> excluded : excludedPatterns.entrySet()) {
String url = excluded.getKey();
URLPattern pattern = excluded.getValue();
removeExcluded(url, pattern, uncheckedPatterns);
for (Map<String, URLPattern> rolePatterns : rolesPatterns.values()) {
removeExcluded(url, pattern, rolePatterns);
}
}
}
private void removeExcluded(String url, URLPattern pattern, Map<String, URLPattern> patterns) {
URLPattern testPattern = patterns.get(url);
if (testPattern != null) {
if (!testPattern.removeMethods(pattern)) {
patterns.remove(url);
}
}
}
private ComponentPermissions buildComponentPermissions() throws PolicyContextException {
for (URLPattern pattern : excludedPatterns.values()) {
String name = pattern.getQualifiedPattern(allSet);
String actions = pattern.getMethods();
policyConfiguration.addToExcludedPolicy(new WebResourcePermission(name, actions));
policyConfiguration.addToExcludedPolicy(new WebUserDataPermission(name, actions));
}
for (Map.Entry<String, Map<String, URLPattern>> entry : rolesPatterns.entrySet()) {
Set<URLPattern> currentRolePatterns = new HashSet<URLPattern>(entry.getValue().values());
for (URLPattern pattern : entry.getValue().values()) {
String name = pattern.getQualifiedPattern(currentRolePatterns);
String actions = pattern.getMethods();
WebResourcePermission permission = new WebResourcePermission(name, actions);
policyConfiguration.addToRole(entry.getKey(), permission);
HTTPMethods methods = pattern.getHTTPMethods();
int transportType = pattern.getTransport();
addOrUpdatePattern(uncheckedUserPatterns, name, methods, transportType);
}
}
for (URLPattern pattern : uncheckedPatterns.values()) {
String name = pattern.getQualifiedPattern(allSet);
HTTPMethods methods = pattern.getHTTPMethods();
addOrUpdatePattern(uncheckedResourcePatterns, name, methods, URLPattern.NA);
int transportType = pattern.getTransport();
addOrUpdatePattern(uncheckedUserPatterns, name, methods, transportType);
}
/**
* A <code>WebResourcePermission</code> and a <code>WebUserDataPermission</code> must be instantiated for
* each <tt>url-pattern</tt> in the deployment descriptor and the default pattern "/", that is not combined
* by the <tt>web-resource-collection</tt> elements of the deployment descriptor with ever HTTP method
* value. The permission objects must be contructed using the qualified pattern as their name and with
* actions defined by the subset of the HTTP methods that do not occur in combination with the pattern.
* The resulting permissions that must be added to the unchecked policy statements by calling the
* <code>addToUncheckedPolcy</code> method on the <code>PolicyConfiguration</code> object.
*/
for (URLPattern pattern : allSet) {
String name = pattern.getQualifiedPattern(allSet);
HTTPMethods methods = pattern.getComplementedHTTPMethods();
if (methods.isNone()) {
continue;
}
addOrUpdatePattern(uncheckedResourcePatterns, name, methods, URLPattern.NA);
addOrUpdatePattern(uncheckedUserPatterns, name, methods, URLPattern.NA);
}
if (!allMap.containsKey("/")) {
URLPattern pattern = new URLPattern("/", Collections.<String> emptySet(), false);
String name = pattern.getQualifiedPattern(allSet);
HTTPMethods methods = pattern.getComplementedHTTPMethods();
addOrUpdatePattern(uncheckedResourcePatterns, name, methods, URLPattern.NA);
addOrUpdatePattern(uncheckedUserPatterns, name, methods, URLPattern.NA);
}
//Create the uncheckedPermissions for WebResourcePermissions
for (UncheckedItem item : uncheckedResourcePatterns.keySet()) {
HTTPMethods methods = uncheckedResourcePatterns.get(item);
String actions = URLPattern.getMethodsWithTransport(methods, item.getTransportType());
policyConfiguration.addToUncheckedPolicy(new WebResourcePermission(item.getName(), actions));
}
//Create the uncheckedPermissions for WebUserDataPermissions
for (UncheckedItem item : uncheckedUserPatterns.keySet()) {
HTTPMethods methods = uncheckedUserPatterns.get(item);
String actions = URLPattern.getMethodsWithTransport(methods, item.getTransportType());
policyConfiguration.addToUncheckedPolicy(new WebUserDataPermission(item.getName(), actions));
}
return policyConfiguration.getComponentPermissions();
}
private void addOrUpdatePattern(Map<UncheckedItem, HTTPMethods> patternMap, String name, HTTPMethods actions, int transportType) {
UncheckedItem item = new UncheckedItem(name, transportType);
HTTPMethods existingActions = patternMap.get(item);
if (existingActions != null) {
patternMap.put(item, existingActions.add(actions));
} else {
patternMap.put(item, new HTTPMethods(actions, false));
}
}
protected void processRoleRefPermissions(ServletInfo servlet) throws PolicyContextException {
String servletName = servlet.servletName.trim();
//WebRoleRefPermissions
Set<String> unmappedRoles = new HashSet<String>(securityRoles);
for (SecurityRoleRefInfo securityRoleRef : servlet.securityRoleRefs) {
//jacc 3.1.3.2
/* The name of the WebRoleRefPermission must be the servlet-name in whose
* context the security-role-ref is defined. The actions of the WebRoleRefPermission
* must be the value of the role-name (that is the reference), appearing in the security-role-ref.
* The deployment tools must call the addToRole method on the PolicyConfiguration object to add the
* WebRoleRefPermission object resulting from the translation to the role
* identified in the role-link appearing in the security-role-ref.
*/
policyConfiguration.addToRole(securityRoleRef.roleLink, new WebRoleRefPermission(servletName, securityRoleRef.roleName));
unmappedRoles.remove(securityRoleRef.roleName);
}
for (String roleName : unmappedRoles) {
policyConfiguration.addToRole(roleName, new WebRoleRefPermission(servletName, roleName));
}
}
protected void addUnmappedJSPPermissions() throws PolicyContextException {
for (String roleName : securityRoles) {
policyConfiguration.addToRole(roleName, new WebRoleRefPermission("", roleName));
}
}
public void clear() {
securityRoles.clear();
uncheckedPatterns.clear();
uncheckedResourcePatterns.clear();
uncheckedUserPatterns.clear();
excludedPatterns.clear();
rolesPatterns.clear();
allSet.clear();
allMap.clear();
}
private static class RecordingPolicyConfiguration implements PolicyConfiguration {
private final PermissionCollection excludedPermissions = new Permissions();
private final PermissionCollection uncheckedPermissions = new Permissions();
private final Map<String, PermissionCollection> rolePermissions = new HashMap<String, PermissionCollection>();
private final StringBuilder audit;
private RecordingPolicyConfiguration(boolean audit) {
if (audit) {
this.audit = new StringBuilder();
} else {
this.audit = null;
}
}
public String getContextID() throws PolicyContextException {
return null;
}
public void addToRole(String roleName, PermissionCollection permissions) {
throw new IllegalStateException("not implemented");
}
public void addToRole(String roleName, Permission permission) throws PolicyContextException {
if (audit != null) {
audit.append("Role: ").append(roleName).append(" -> ").append(permission).append('\n');
}
PermissionCollection permissionsForRole = rolePermissions.get(roleName);
if (permissionsForRole == null) {
permissionsForRole = new Permissions();
rolePermissions.put(roleName, permissionsForRole);
}
permissionsForRole.add(permission);
}
public void addToUncheckedPolicy(PermissionCollection permissions) {
throw new IllegalStateException("not implemented");
}
public void addToUncheckedPolicy(Permission permission) throws PolicyContextException {
if (audit != null) {
audit.append("Unchecked -> ").append(permission).append('\n');
}
uncheckedPermissions.add(permission);
}
public void addToExcludedPolicy(PermissionCollection permissions) {
throw new IllegalStateException("not implemented");
}
public void addToExcludedPolicy(Permission permission) throws PolicyContextException {
if (audit != null) {
audit.append("Excluded -> ").append(permission).append('\n');
}
excludedPermissions.add(permission);
}
public void removeRole(String roleName) throws PolicyContextException {
throw new IllegalStateException("not implemented");
}
public void removeUncheckedPolicy() throws PolicyContextException {
throw new IllegalStateException("not implemented");
}
public void removeExcludedPolicy() throws PolicyContextException {
throw new IllegalStateException("not implemented");
}
public void linkConfiguration(PolicyConfiguration link) throws PolicyContextException {
throw new IllegalStateException("not implemented");
}
public void delete() throws PolicyContextException {
throw new IllegalStateException("not implemented");
}
public void commit() throws PolicyContextException {
throw new IllegalStateException("not implemented");
}
public boolean inService() throws PolicyContextException {
throw new IllegalStateException("not implemented");
}
public ComponentPermissions getComponentPermissions() {
return new ComponentPermissions(excludedPermissions, uncheckedPermissions, rolePermissions);
}
public String getAudit() {
if (audit == null) {
return "no audit kept";
}
return audit.toString();
}
}
}