/*
* 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.jackrabbit.core.security.authorization;
import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.PrivilegeDefinition;
import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.AccessDeniedException;
import javax.jcr.NamespaceException;
import javax.jcr.RepositoryException;
import javax.jcr.security.AccessControlException;
import javax.jcr.security.Privilege;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* <code>PrivilegeManager</code>...
*/
public final class PrivilegeManagerImpl implements PrivilegeManager, PrivilegeRegistry.Listener {
/**
* logger instance
*/
private static final Logger log = LoggerFactory.getLogger(PrivilegeManagerImpl.class);
private static final Privilege[] EMPTY_ARRAY = new Privilege[0];
/**
* The privilege registry
*/
private final PrivilegeRegistry registry;
/**
* The name resolver used to determine the correct privilege
* {@link javax.jcr.security.Privilege#getName() name} depending on the sessions namespace
* mappings.
*/
private final NameResolver resolver;
/**
* Per instance map containing the namespace aware representation of
* the registered privileges.
*/
private final Map<Name, Privilege> cache;
public PrivilegeManagerImpl(PrivilegeRegistry registry, NameResolver nameResolver) {
this.registry = registry;
this.resolver = nameResolver;
this.cache = new HashMap<Name, Privilege>();
// listen to privilege registration (due to weak references no explicit
// stop-listening call required
registry.addListener(this);
}
/**
* Disposes this privilege manager
*/
public void dispose() {
registry.removeListener(this);
}
//---------------------------------------------------< PrivilegeManager >---
/**
* @see PrivilegeManager#getRegisteredPrivileges()
*/
public Privilege[] getRegisteredPrivileges() throws RepositoryException {
PrivilegeDefinition[] allDefs = registry.getAll();
if (allDefs.length != cache.size()) {
synchronized (cache) {
for (PrivilegeDefinition def : allDefs) {
if (!cache.containsKey(def.getName())) {
cache.put(def.getName(), new PrivilegeImpl(def));
}
}
}
}
return cache.values().toArray(new Privilege[allDefs.length]);
}
/**
* @see PrivilegeManager#getPrivilege(String)
*/
public Privilege getPrivilege(String privilegeName) throws AccessControlException, RepositoryException {
Name name = resolver.getQName(privilegeName);
return getPrivilege(name);
}
/**
* Register a new custom privilege with the specified characteristics.
* <p>
* The current implementation has the following limitations and constraints:
*
* <ul>
* <li>the name may not be in use by another privilege</li>
* <li>the namespace URI must be a valid, registered namespace excluding
* those namespaces marked as being reserved</li>
* <li>an aggregate custom privilege is valid if all declared aggregate
* names can be resolved to registered privileges and if there exists
* no registered privilege with the same aggregated privileges.</li>
* </ul>
* <p>
* <strong>Please note</strong><br>
* Custom privilege(s) will not be enforced for any kind of repository
* operations. Those are exclusively covered by the built-in privileges.
* This also implies that the {@link Permission}s are not affected by
* custom privileges.
* <p>
* Applications making use of the custom privilege(s) are in charge of
* asserting whether the privileges are granted/denied according to their
* application specific needs.
*
* @param privilegeName The name of the new custom privilege.
* @param isAbstract Boolean flag indicating if the privilege is abstract.
* @param declaredAggregateNames An array of privilege names referring to
* registered privileges being aggregated by this new custom privilege.
* In case of a non aggregate privilege an empty array should be passed.
* @return the new privilege.
* @throws AccessDeniedException If the session this manager has been created
* lacks rep:privilegeManagement privilege.
* @throws RepositoryException If the privilege could not be registered due
* to constraint violations or if persisting the custom privilege fails.
* @see PrivilegeManager#registerPrivilege(String, boolean, String[])
*/
public Privilege registerPrivilege(String privilegeName, boolean isAbstract,
String[] declaredAggregateNames)
throws AccessDeniedException, RepositoryException {
if (resolver instanceof SessionImpl) {
SessionImpl sImpl = (SessionImpl) resolver;
sImpl.getAccessManager().checkRepositoryPermission(Permission.PRIVILEGE_MNGMT);
} else {
// cannot evaluate
throw new AccessDeniedException("Registering privileges is not allowed for the editing session.");
}
Name name = resolver.getQName(privilegeName);
Set<Name> daNames;
if (declaredAggregateNames == null || declaredAggregateNames.length == 0) {
daNames = Collections.emptySet();
} else {
daNames = new HashSet<Name>(declaredAggregateNames.length);
for (String declaredAggregateName : declaredAggregateNames) {
daNames.add(resolver.getQName(declaredAggregateName));
}
}
registry.registerDefinition(name, isAbstract, daNames);
return getPrivilege(privilegeName);
}
//-----------------------------< implementation specific public methods >---
/**
* @param privileges An array of privileges.
* @return The bits of the privileges contained in the specified
* array.
* @throws AccessControlException If the specified array is null, empty
* or if it contains an unregistered privilege.
*/
public PrivilegeBits getBits(Privilege... privileges) throws AccessControlException {
if (privileges == null || privileges.length == 0) {
throw new AccessControlException("Privilege array is empty or null.");
}
PrivilegeDefinition[] defs = new PrivilegeDefinition[privileges.length];
for (int i = 0; i < privileges.length; i++) {
Privilege p = privileges[i];
if (p instanceof PrivilegeImpl) {
defs[i] = ((PrivilegeImpl) p).definition;
} else {
String name = (p == null) ? "null" : p.getName();
throw new AccessControlException("Unknown privilege '" + name + "'.");
}
}
return registry.getBits(defs);
}
/**
* @param privilegeNames An array of privilege names.
* @return The bits of the privileges contained in the specified
* array.
* @throws AccessControlException If the specified array is null or if it
* contains the name of an unregistered privilege.
*/
public PrivilegeBits getBits(Name... privilegeNames) throws RepositoryException {
if (privilegeNames == null) {
throw new AccessControlException("Privilege name array is null.");
}
return registry.getBits(privilegeNames);
}
/**
* Returns an array of registered <code>Privilege</code>s. If the specified
* <code>bits</code> represent a single registered privilege the returned array
* contains a single element. Otherwise the returned array contains the
* individual registered privileges that are combined in the given
* <code>bits</code>. If <code>bits</code> does not match to any registered
* privilege an empty array will be returned.
*
* @param bits Privilege bits as obtained from {@link #getBits(Privilege...)}.
* @return Array of <code>Privilege</code>s that are presented by the given
* <code>bits</code> or an empty array if <code>bits</code> cannot be
* resolved to registered <code>Privilege</code>s.
* @see #getBits(Privilege...)
*/
public Set<Privilege> getPrivileges(PrivilegeBits bits) {
Name[] names = registry.getNames(bits);
if (names.length == 0) {
return Collections.emptySet();
} else {
Set<Privilege> privs = new HashSet<Privilege>(names.length);
for (Name n : names) {
try {
privs.add(getPrivilege(n));
} catch (RepositoryException e) {
log.error("Internal error: invalid privilege name " + n.toString());
}
}
return privs;
}
}
//------------------------------------------------------------< private >---
/**
* @param name
* @return The privilege with the specified name.
* @throws AccessControlException
* @throws RepositoryException
*/
private Privilege getPrivilege(Name name) throws AccessControlException, RepositoryException {
Privilege privilege;
synchronized (cache) {
if (cache.containsKey(name)) {
privilege = cache.get(name);
} else {
PrivilegeDefinition def = registry.get(name);
if (def != null) {
privilege = new PrivilegeImpl(def);
cache.put(name, privilege);
} else {
throw new AccessControlException("Unknown privilege " + resolver.getJCRName(name));
}
}
}
return privilege;
}
//-----------------------------------------< PrivilegeRegistry.Listener >---
/**
* @see PrivilegeRegistry.Listener#privilegesRegistered(java.util.Set)
* @param privilegeNames
*/
public void privilegesRegistered(Set<Name> privilegeNames) {
// force recalculation of jcr:all privilege
synchronized (cache) {
cache.remove(NameConstants.JCR_ALL);
}
}
//----------------------------------------------------------< Privilege >---
/**
* Simple wrapper used to provide an public representation of the
* registered internal privileges properly exposing the JCR name.
*/
private class PrivilegeImpl implements Privilege {
private final PrivilegeDefinition definition;
private final Privilege[] declaredAggregates;
private final Privilege[] aggregates;
private PrivilegeImpl(PrivilegeDefinition definition) throws RepositoryException {
this.definition = definition;
Set<Name> set = definition.getDeclaredAggregateNames();
Name[] declAggrNames = set.toArray(new Name[set.size()]);
if (declAggrNames.length == 0) {
declaredAggregates = EMPTY_ARRAY;
aggregates = EMPTY_ARRAY;
} else {
declaredAggregates = new Privilege[declAggrNames.length];
for (int i = 0; i < declAggrNames.length; i++) {
declaredAggregates[i] = getPrivilege(declAggrNames[i]);
}
Set<Privilege> aggr = new HashSet<Privilege>();
for (Privilege decl : declaredAggregates) {
aggr.add(decl);
if (decl.isAggregate()) {
aggr.addAll(Arrays.asList(decl.getAggregatePrivileges()));
}
}
aggregates = aggr.toArray(new Privilege[aggr.size()]);
}
}
/**
* @see Privilege#getName()
*/
public String getName() {
try {
return resolver.getJCRName(definition.getName());
} catch (NamespaceException e) {
// should not occur -> return internal name representation.
return definition.getName().toString();
}
}
/**
* @see Privilege#isAbstract()
*/
public boolean isAbstract() {
return definition.isAbstract();
}
/**
* @see Privilege#isAggregate()
*/
public boolean isAggregate() {
return declaredAggregates.length > 0;
}
/**
* @see Privilege#getDeclaredAggregatePrivileges()
*/
public Privilege[] getDeclaredAggregatePrivileges() {
return declaredAggregates;
}
/**
* @see Privilege#getAggregatePrivileges()
*/
public Privilege[] getAggregatePrivileges() {
return aggregates;
}
//---------------------------------------------------------< Object >---
@Override
public String toString() {
return getName();
}
@Override
public int hashCode() {
return definition.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof PrivilegeImpl) {
PrivilegeImpl other = (PrivilegeImpl) obj;
return definition.equals(other.definition);
}
return false;
}
}
}