blob: 7632dc6b37f302a727e67029933ed0c98935227f [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.lenya.cms.ac.usecase.impl;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.parameters.Parameterizable;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.environment.Request;
import org.apache.lenya.ac.AccessControlException;
import org.apache.lenya.ac.Role;
import org.apache.lenya.ac.cache.BuildException;
import org.apache.lenya.ac.cache.CachingException;
import org.apache.lenya.ac.cache.SourceCache;
import org.apache.lenya.cms.ac.PolicyUtil;
import org.apache.lenya.cms.ac.usecase.UsecaseAuthorizer;
import org.apache.lenya.cms.publication.Publication;
import org.apache.lenya.cms.publication.PublicationException;
import org.apache.lenya.cms.publication.PublicationUtil;
/**
* Authorizer for usecases.
* <p>
* Supported parameters via {@link Parameterizable}:
* </p>
* <ul>
* <li> {@link #PARAMETER_CONFIGURATION} - location of the usecase policies file
* (parameterizable for testing purposes) </li>
* </ul>
* @version $Id: UsecaseAuthorizer.java 392449 2006-04-07 23:20:38Z michi $
*/
public class UsecaseAuthorizerImpl extends AbstractLogEnabled implements UsecaseAuthorizer,
Serviceable, Disposable, Parameterizable {
/**
* The name of the pseudo-usecase that governs access to pages.
*/
public static final String VISIT_USECASE = "ac.visit";
protected static final String PARAMETER_CONFIGURATION = "configuration";
protected static final String TYPE = "usecase";
protected static final String USECASE_PARAMETER = "lenya.usecase";
private static final String AC_CONFIGURATION_FILE = "config/access-control/access-control.xml"
.replace('/', File.separatorChar);
private SourceCache cache;
/**
* the configuration URI for this component
*/
private String configurationUri;
private ServiceManager manager;
/**
* Maps publication IDs to their configuration URIs. This is a persistent
* map to avoid unnecessary publication lookups. Whenever an authorization
* request for a new publication is dealt with, the publication's
* configuration URI is stored, to be re-used on later occasions (for the
* lifetime of the component).
*/
private Map pubId2configUri = new HashMap();
/**
* @see org.apache.lenya.cms.ac.usecase.UsecaseAuthorizer#authorizeUsecase(java.lang.String,
* org.apache.lenya.ac.Role[],
* org.apache.lenya.cms.publication.Publication)
*/
public boolean authorizeUsecase(String usecase, Role[] roles, Publication publication)
throws AccessControlException {
return authorizeUsecase(usecase, roles, getConfigurationURI(publication));
}
private boolean authorizeUsecase(String usecase, Role[] roles, String _configurationUri)
throws AccessControlException {
getLogger().debug("Authorizing usecase [" + usecase + "]");
boolean authorized = false;
UsecaseRoles usecaseRoles = getUsecaseRoles(_configurationUri);
if (usecaseRoles == null) {
throw new AccessControlException("Usecase policies configuration not found at ["
+ _configurationUri + "]");
}
if (usecaseRoles.hasRoles(usecase)) {
getLogger().debug("Roles for usecase found.");
List usecaseRoleIds = Arrays.asList(usecaseRoles.getRoles(usecase));
int i = 0;
while (!authorized && i < roles.length) {
authorized = usecaseRoleIds.contains(roles[i].getId());
getLogger()
.debug(
"Authorization for role [" + roles[i].getId() + "] is ["
+ authorized + "]");
i++;
}
} else {
getLogger().debug("No roles for usecase [" + usecase + "] found. Denying access.");
}
return authorized;
}
/**
* @see org.apache.lenya.cms.ac.usecase.UsecaseAuthorizer#isPermitted(java.lang.String,
* org.apache.lenya.cms.publication.Publication,
* org.apache.lenya.ac.Role)
*/
public boolean isPermitted(String usecase, Publication publication, Role role)
throws AccessControlException {
String configUri = getConfigurationURI(publication);
UsecaseRoles usecaseRoles = getUsecaseRoles(configUri);
String[] roles = usecaseRoles.getRoles(usecase);
return Arrays.asList(roles).contains(role.getId());
}
/**
* @see org.apache.lenya.cms.ac.usecase.UsecaseAuthorizer#setPermission(java.lang.String,
* org.apache.lenya.cms.publication.Publication,
* org.apache.lenya.ac.Role, boolean)
*/
public void setPermission(String usecase, Publication publication, Role role, boolean granted)
throws AccessControlException {
String configUri = getConfigurationURI(publication);
if (configUri.startsWith("aggregate-")) {
configUri = configUri.substring("aggregate-".length());
}
UsecaseRoles usecaseRoles = getUsecaseRoles(configUri);
List roles = Arrays.asList(usecaseRoles.getRoles(usecase));
String roleId = role.getId();
if (granted) {
if (!roles.contains(roleId)) {
usecaseRoles.addRole(usecase, roleId);
}
} else {
if (roles.contains(roleId)) {
usecaseRoles.removeRole(usecase, roleId);
}
}
UsecaseRolesBuilder builder = new UsecaseRolesBuilder();
try {
builder.save(usecaseRoles, configUri, this.manager);
} catch (BuildException e) {
throw new AccessControlException(e);
}
}
/**
* This method will substitute VISIT_USECASE if no USECASE_PARAMETER is set,
* so that it can be used to authorize plain page access as well.
* @see org.apache.lenya.ac.Authorizer#authorize(org.apache.cocoon.environment.Request)
*/
public boolean authorize(Request request) throws AccessControlException {
String usecase = request.getParameter(USECASE_PARAMETER);
if (usecase == null || "".equals(usecase)) {
usecase = VISIT_USECASE;
}
boolean authorized = false;
try {
String _configurationUri;
// Check if the service has been parameterized with a
// configuration URI. This can be used for testing purposes etc.
if (getConfigurationURI() != null) {
_configurationUri = getConfigurationURI();
} else {
Publication publication = PublicationUtil.getPublication(this.manager, request);
_configurationUri = getConfigurationURI(publication);
}
Role[] roles = PolicyUtil.getRoles(request);
authorized = authorizeUsecase(usecase, roles, _configurationUri);
} catch (final PublicationException e) {
throw new AccessControlException(e);
} catch (final AccessControlException e) {
throw new AccessControlException(e);
}
return authorized;
}
/**
* Returns the configuration source cache.
* @return A source cache.
*/
private SourceCache getCache() {
return this.cache;
}
/**
* Returns the source URI of the usecase role configuration file for a
* certain publication.
*
* @param publication The publication.
* @return A string representing a URI.
*/
protected String getConfigurationURI(Publication publication) {
String configURI = (String) this.pubId2configUri.get(publication.getId());
if (configURI == null) {
try {
Configuration config = getConfiguration(publication);
Configuration[] authorizerConfigs = config.getChildren("authorizer");
for (int i = 0; i < authorizerConfigs.length; i++) {
if (authorizerConfigs[i].getAttribute("type").equals("usecase")) {
Configuration paraConfig = authorizerConfigs[i].getChild("parameter");
configURI = paraConfig.getAttribute("value");
this.pubId2configUri.put(publication.getId(), configURI);
}
}
} catch (Exception e) {
getLogger().error(e.getMessage(), e);
}
}
return configURI;
}
protected UsecaseRoles getUsecaseRoles(String _configurationUri) throws AccessControlException {
UsecaseRolesBuilder builder = new UsecaseRolesBuilder();
UsecaseRoles usecaseRoles;
try {
usecaseRoles = (UsecaseRoles) getCache().get(_configurationUri, builder);
} catch (CachingException e) {
throw new AccessControlException(e);
}
return usecaseRoles;
}
/**
* @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
*/
public void service(ServiceManager _manager) throws ServiceException {
getLogger().debug("Servicing [" + getClass().getName() + "]");
this.manager = _manager;
this.cache = (SourceCache) _manager.lookup(SourceCache.ROLE);
}
/**
* @see org.apache.avalon.framework.activity.Disposable#dispose()
*/
public void dispose() {
if (getCache() != null) {
this.manager.release(getCache());
}
}
public void parameterize(Parameters parameters) throws ParameterException {
if (parameters.isParameter(PARAMETER_CONFIGURATION)) {
this.configurationUri = parameters.getParameter(PARAMETER_CONFIGURATION);
}
}
private String getConfigurationURI() {
return this.configurationUri;
}
/**
* Retrieves access control configuration of a specific publication.
* @param publication The publication.
* @return Configuration
* @throws AccessControlException when something went wrong.
*/
private Configuration getConfiguration(Publication publication) throws AccessControlException {
File configurationFile = new File(publication.getDirectory(), AC_CONFIGURATION_FILE);
if (configurationFile.isFile()) {
try {
Configuration configuration = new DefaultConfigurationBuilder()
.buildFromFile(configurationFile);
return configuration;
} catch (Exception e) {
throw new AccessControlException(e);
}
} else {
throw new AccessControlException("No such file or directory: " + configurationFile);
}
}
}