/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.security;
import static org.geoserver.catalog.Predicates.acceptAll;
import static org.geoserver.catalog.Predicates.or;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nonnull;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CatalogFacade;
import org.geoserver.catalog.CatalogFactory;
import org.geoserver.catalog.CatalogInfo;
import org.geoserver.catalog.CatalogVisitor;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.CoverageStoreInfo;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.LayerGroupInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.MapInfo;
import org.geoserver.catalog.NamespaceInfo;
import org.geoserver.catalog.Predicates;
import org.geoserver.catalog.PublishedInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.catalog.ResourcePool;
import org.geoserver.catalog.StoreInfo;
import org.geoserver.catalog.StyleInfo;
import org.geoserver.catalog.ValidationResult;
import org.geoserver.catalog.WMSLayerInfo;
import org.geoserver.catalog.WMSStoreInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.catalog.event.CatalogListener;
import org.geoserver.catalog.impl.AbstractDecorator;
import org.geoserver.catalog.util.CloseableIterator;
import org.geoserver.catalog.util.CloseableIteratorAdapter;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.security.decorators.DecoratingCatalogFactory;
import org.geoserver.security.decorators.SecuredCoverageInfo;
import org.geoserver.security.decorators.SecuredCoverageStoreInfo;
import org.geoserver.security.decorators.SecuredDataStoreInfo;
import org.geoserver.security.decorators.SecuredFeatureTypeInfo;
import org.geoserver.security.decorators.SecuredLayerGroupInfo;
import org.geoserver.security.decorators.SecuredLayerInfo;
import org.geoserver.security.decorators.SecuredWMSLayerInfo;
import org.geoserver.security.impl.DataAccessRuleDAO;
import org.geoserver.security.impl.DefaultResourceAccessManager;
import org.hsqldb.lib.ArrayListIdentity;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.filter.sort.SortBy;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
/**
* Wraps the catalog and applies the security directives provided by a {@link ResourceAccessManager}
* or a {@link DataAccessManager} registered in the Spring application context
*
* @author Andrea Aime - GeoSolutions
*/
public class SecureCatalogImpl extends AbstractDecorator<Catalog> implements Catalog {
/**
* How to behave in case of mixed mode catalog access, hide the resource or challenge
* the user to authenticate. For any direct access (by name, id) do challenge, for any
* "catch all" or "catch group" access, where the single resource was not explicitly requested,
* hide.
*/
public enum MixedModeBehavior { HIDE, CHALLENGE };
protected ResourceAccessManager accessManager;
public SecureCatalogImpl(Catalog catalog) throws Exception {
this(catalog, lookupResourceAccessManager());
}
public String getId() {
return delegate.getId();
}
public ResourceAccessManager getResourceAccessManager() {
return accessManager;
}
static ResourceAccessManager lookupResourceAccessManager() throws Exception {
ResourceAccessManager manager = GeoServerExtensions.bean(ResourceAccessManager.class);
if (manager == null) {
DataAccessManager daManager = lookupDataAccessManager();
if (daManager == null) {
manager = buildDefaultResourceAccessManager();
} else {
manager = new DataAccessManagerAdapter(daManager);
}
}
CatalogFilterAccessManager lwManager = new CatalogFilterAccessManager();
lwManager.setDelegate(manager);
return lwManager;
}
private static DefaultResourceAccessManager buildDefaultResourceAccessManager() {
return new DefaultResourceAccessManager(GeoServerExtensions.bean(DataAccessRuleDAO.class), (Catalog) GeoServerExtensions.bean("rawCatalog"));
}
static DataAccessManager lookupDataAccessManager() throws Exception {
DataAccessManager manager = GeoServerExtensions.bean(DataAccessManager.class);
if (manager != null && manager instanceof DataAccessManagerWrapper) {
((DataAccessManagerWrapper) manager).setDelegate(buildDefaultResourceAccessManager());
}
return manager;
}
public SecureCatalogImpl(Catalog catalog, ResourceAccessManager manager) {
super(catalog);
this.accessManager = manager;
}
// -------------------------------------------------------------------
// SECURED METHODS
// -------------------------------------------------------------------
public CoverageInfo getCoverage(String id) {
return checkAccess(user(), delegate.getCoverage(id), MixedModeBehavior.CHALLENGE);
}
public CoverageInfo getCoverageByName(String ns, String name) {
return checkAccess(user(), delegate.getCoverageByName(ns, name), MixedModeBehavior.CHALLENGE);
}
public CoverageInfo getCoverageByName(NamespaceInfo ns, String name) {
return checkAccess(user(), delegate.getCoverageByName(ns, name), MixedModeBehavior.CHALLENGE);
}
public CoverageInfo getCoverageByName(Name name) {
return checkAccess(user(), delegate.getCoverageByName(name), MixedModeBehavior.CHALLENGE);
}
public CoverageInfo getCoverageByName(String name) {
return checkAccess(user(), delegate.getCoverageByName(name), MixedModeBehavior.CHALLENGE);
}
public List<CoverageInfo> getCoverages() {
return filterResources(user(), delegate.getCoverages());
}
public List<CoverageInfo> getCoveragesByNamespace(NamespaceInfo namespace) {
return filterResources(user(), delegate.getCoveragesByNamespace(namespace));
}
public List<CoverageInfo> getCoveragesByCoverageStore(
CoverageStoreInfo store) {
return filterResources(user(), delegate.getCoveragesByCoverageStore(store));
}
public CoverageInfo getCoverageByCoverageStore(
CoverageStoreInfo coverageStore, String name) {
return checkAccess(user(), delegate.getCoverageByCoverageStore(coverageStore, name), MixedModeBehavior.CHALLENGE);
}
public List<CoverageInfo> getCoveragesByStore(CoverageStoreInfo store) {
return filterResources(user(), delegate.getCoveragesByStore(store));
}
public CoverageStoreInfo getCoverageStore(String id) {
return checkAccess(user(), delegate.getCoverageStore(id), MixedModeBehavior.CHALLENGE);
}
public CoverageStoreInfo getCoverageStoreByName(String name) {
return checkAccess(user(), delegate.getCoverageStoreByName(name), MixedModeBehavior.CHALLENGE);
}
public CoverageStoreInfo getCoverageStoreByName(String workspaceName,
String name) {
return checkAccess(user(), delegate.getCoverageStoreByName(workspaceName,name), MixedModeBehavior.CHALLENGE);
}
public CoverageStoreInfo getCoverageStoreByName(WorkspaceInfo workspace,
String name) {
return checkAccess(user(), delegate.getCoverageStoreByName(workspace,name), MixedModeBehavior.CHALLENGE);
}
public List<CoverageStoreInfo> getCoverageStoresByWorkspace(
String workspaceName) {
return filterStores(user(),delegate.getCoverageStoresByWorkspace(workspaceName));
}
public List<CoverageStoreInfo> getCoverageStoresByWorkspace(
WorkspaceInfo workspace) {
return filterStores(user(),delegate.getCoverageStoresByWorkspace(workspace));
}
public List<CoverageStoreInfo> getCoverageStores() {
return filterStores(user(), delegate.getCoverageStores());
}
public DataStoreInfo getDataStore(String id) {
return checkAccess(user(), delegate.getDataStore(id), MixedModeBehavior.CHALLENGE);
}
public DataStoreInfo getDataStoreByName(String name) {
return checkAccess(user(), delegate.getDataStoreByName(name), MixedModeBehavior.CHALLENGE);
}
public DataStoreInfo getDataStoreByName(String workspaceName, String name) {
return checkAccess(user(), delegate.getDataStoreByName(workspaceName,name), MixedModeBehavior.CHALLENGE);
}
public DataStoreInfo getDataStoreByName(WorkspaceInfo workspace, String name) {
return checkAccess(user(), delegate.getDataStoreByName(workspace,name), MixedModeBehavior.CHALLENGE) ;
}
public List<DataStoreInfo> getDataStoresByWorkspace(String workspaceName) {
return filterStores(user(), delegate.getDataStoresByWorkspace(workspaceName));
}
public List<DataStoreInfo> getDataStoresByWorkspace(WorkspaceInfo workspace) {
return filterStores(user(), delegate.getDataStoresByWorkspace(workspace));
}
public List<DataStoreInfo> getDataStores() {
return filterStores(user(), delegate.getDataStores());
}
public NamespaceInfo getDefaultNamespace() {
return delegate.getDefaultNamespace();
}
public WorkspaceInfo getDefaultWorkspace() {
return delegate.getDefaultWorkspace();
}
public FeatureTypeInfo getFeatureType(String id) {
return checkAccess(user(), delegate.getFeatureType(id), MixedModeBehavior.CHALLENGE);
}
public FeatureTypeInfo getFeatureTypeByName(String ns, String name) {
return checkAccess(user(), delegate.getFeatureTypeByName(ns, name), MixedModeBehavior.CHALLENGE);
}
public FeatureTypeInfo getFeatureTypeByName(NamespaceInfo ns, String name) {
return checkAccess(user(), delegate.getFeatureTypeByName(ns,name), MixedModeBehavior.CHALLENGE);
}
public FeatureTypeInfo getFeatureTypeByName(Name name) {
return checkAccess(user(), delegate.getFeatureTypeByName(name), MixedModeBehavior.CHALLENGE);
}
public FeatureTypeInfo getFeatureTypeByName(String name) {
return checkAccess(user(), delegate.getFeatureTypeByName(name), MixedModeBehavior.CHALLENGE);
}
public List<FeatureTypeInfo> getFeatureTypes() {
return filterResources(user(), delegate.getFeatureTypes());
}
public List<FeatureTypeInfo> getFeatureTypesByNamespace(NamespaceInfo namespace) {
return filterResources(user(), delegate.getFeatureTypesByNamespace(namespace));
}
public FeatureTypeInfo getFeatureTypeByStore(DataStoreInfo dataStore,
String name) {
return checkAccess(user(), delegate.getFeatureTypeByStore(dataStore , name), MixedModeBehavior.CHALLENGE);
}
public FeatureTypeInfo getFeatureTypeByDataStore(DataStoreInfo dataStore,
String name) {
return checkAccess(user(), delegate.getFeatureTypeByDataStore(dataStore , name), MixedModeBehavior.CHALLENGE);
}
public List<FeatureTypeInfo> getFeatureTypesByStore(DataStoreInfo store) {
return filterResources(user(), delegate.getFeatureTypesByStore(store));
}
public List<FeatureTypeInfo> getFeatureTypesByDataStore(DataStoreInfo store) {
return filterResources(user(), delegate.getFeatureTypesByDataStore(store));
}
public LayerInfo getLayer(String id) {
return checkAccess(user(), delegate.getLayer(id), MixedModeBehavior.CHALLENGE);
}
public LayerInfo getLayerByName(String name) {
return checkAccess(user(), delegate.getLayerByName(name), MixedModeBehavior.CHALLENGE);
}
public LayerInfo getLayerByName(Name name) {
return checkAccess(user(), delegate.getLayerByName(name), MixedModeBehavior.CHALLENGE);
}
public LayerGroupInfo getLayerGroup(String id) {
return checkAccess(user(), delegate.getLayerGroup(id), MixedModeBehavior.CHALLENGE);
}
public LayerGroupInfo getLayerGroupByName(String name) {
return checkAccess(user(), delegate.getLayerGroupByName(name), MixedModeBehavior.CHALLENGE);
}
public LayerGroupInfo getLayerGroupByName(String workspaceName, String name) {
return checkAccess(user(), delegate.getLayerGroupByName(workspaceName, name), MixedModeBehavior.CHALLENGE);
}
public LayerGroupInfo getLayerGroupByName(WorkspaceInfo workspace,
String name) {
return checkAccess(user(), delegate.getLayerGroupByName(workspace, name), MixedModeBehavior.CHALLENGE);
}
public List<LayerGroupInfo> getLayerGroups() {
return filterGroups(user(), delegate.getLayerGroups());
}
public List<LayerGroupInfo> getLayerGroupsByWorkspace(String workspaceName) {
return filterGroups(user(), delegate.getLayerGroupsByWorkspace(workspaceName));
}
public List<LayerGroupInfo> getLayerGroupsByWorkspace(
WorkspaceInfo workspace) {
return filterGroups(user(), delegate.getLayerGroupsByWorkspace(workspace));
}
public List<LayerInfo> getLayers() {
return filterLayers(acceptAll());
}
public List<LayerInfo> getLayers(ResourceInfo resource) {
return filterLayers(Predicates.equal("resource.id", resource.getId()));
}
public List<LayerInfo> getLayers(StyleInfo style) {
String id = style.getId();
Filter filter = or(Predicates.equal("defaultStyle.id", id),
Predicates.equal("styles.id", id));
return filterLayers(filter);
}
private List<LayerInfo> filterLayers(final Filter filter) {
CloseableIterator<LayerInfo> iterator;
iterator = list(LayerInfo.class, filter);
try {
return ImmutableList.copyOf(iterator);
} finally {
iterator.close();
}
}
public NamespaceInfo getNamespace(String id) {
return checkAccess(user(), delegate.getNamespace(id), MixedModeBehavior.CHALLENGE);
}
public NamespaceInfo getNamespaceByPrefix(String prefix) {
return checkAccess(user(), delegate.getNamespaceByPrefix(prefix), MixedModeBehavior.CHALLENGE);
}
public NamespaceInfo getNamespaceByURI(String uri) {
return checkAccess(user(), delegate.getNamespaceByURI(uri), MixedModeBehavior.CHALLENGE);
}
public List<NamespaceInfo> getNamespaces() {
return filterNamespaces(user(), delegate.getNamespaces());
}
public <T extends ResourceInfo> T getResource(String id, Class<T> clazz) {
return checkAccess(user(), delegate.getResource(id, clazz), MixedModeBehavior.CHALLENGE);
}
public <T extends ResourceInfo> T getResourceByName(Name name, Class<T> clazz) {
return checkAccess(user(), delegate.getResourceByName(name, clazz), MixedModeBehavior.CHALLENGE);
}
public <T extends ResourceInfo> T getResourceByName(String name, Class<T> clazz) {
return checkAccess(user(), delegate.getResourceByName(name, clazz), MixedModeBehavior.CHALLENGE);
}
public <T extends ResourceInfo> T getResourceByName(NamespaceInfo ns,
String name, Class<T> clazz) {
return checkAccess(user(), delegate.getResourceByName(ns, name, clazz), MixedModeBehavior.CHALLENGE) ;
}
public <T extends ResourceInfo> T getResourceByName(String ns, String name, Class<T> clazz) {
return checkAccess(user(), delegate.getResourceByName(ns, name, clazz), MixedModeBehavior.CHALLENGE);
}
public <T extends ResourceInfo> List<T> getResources(Class<T> clazz) {
return filterResources(user(), delegate.getResources(clazz));
}
public <T extends ResourceInfo> List<T> getResourcesByNamespace(NamespaceInfo namespace,
Class<T> clazz) {
return filterResources(user(), delegate.getResourcesByNamespace(namespace, clazz));
}
public <T extends ResourceInfo> List<T> getResourcesByNamespace(
String namespace, Class<T> clazz) {
return filterResources(user(), delegate.getResourcesByNamespace(namespace, clazz));
}
public <T extends ResourceInfo> T getResourceByStore(StoreInfo store,
String name, Class<T> clazz) {
return checkAccess(user(), delegate.getResourceByStore(store, name, clazz), MixedModeBehavior.CHALLENGE);
}
public <T extends ResourceInfo> List<T> getResourcesByStore(
StoreInfo store, Class<T> clazz) {
return filterResources(user(), delegate.getResourcesByStore(store, clazz));
}
public <T extends StoreInfo> T getStore(String id, Class<T> clazz) {
return checkAccess(user(), delegate.getStore(id, clazz), MixedModeBehavior.CHALLENGE);
}
public <T extends StoreInfo> T getStoreByName(String name, Class<T> clazz) {
return checkAccess(user(), delegate.getStoreByName(name, clazz), MixedModeBehavior.CHALLENGE);
}
public <T extends StoreInfo> T getStoreByName(String workspaceName,
String name, Class<T> clazz) {
return checkAccess(user(), delegate.getStoreByName(workspaceName, name, clazz), MixedModeBehavior.CHALLENGE);
}
public <T extends StoreInfo> T getStoreByName(WorkspaceInfo workspace,
String name, Class<T> clazz) {
return checkAccess(user(), delegate.getStoreByName(workspace, name, clazz), MixedModeBehavior.CHALLENGE);
}
public <T extends StoreInfo> List<T> getStores(Class<T> clazz) {
return filterStores(user(), delegate.getStores(clazz));
}
public <T extends StoreInfo> List<T> getStoresByWorkspace(
String workspaceName, Class<T> clazz) {
return filterStores(user(), delegate.getStoresByWorkspace(workspaceName , clazz));
}
public <T extends StoreInfo> List<T> getStoresByWorkspace(WorkspaceInfo workspace,
Class<T> clazz) {
return filterStores(user(), delegate.getStoresByWorkspace(workspace, clazz));
}
public WorkspaceInfo getWorkspace(String id) {
return checkAccess(user(), delegate.getWorkspace(id), MixedModeBehavior.CHALLENGE);
}
public WorkspaceInfo getWorkspaceByName(String name) {
return checkAccess(user(), delegate.getWorkspaceByName(name), MixedModeBehavior.CHALLENGE);
}
public List<WorkspaceInfo> getWorkspaces() {
return filterWorkspaces(user(), delegate.getWorkspaces());
}
// -------------------------------------------------------------------
// Security support method
// -------------------------------------------------------------------
protected static Authentication user() {
return SecurityContextHolder.getContext().getAuthentication();
}
@SuppressWarnings("unchecked")
protected <T extends CatalogInfo> T checkAccess(Authentication user, T info, MixedModeBehavior mixedModeBehavior) {
if (info instanceof WorkspaceInfo) {
return (T) checkAccess(user, (WorkspaceInfo) info, mixedModeBehavior);
}
if (info instanceof NamespaceInfo) {
return (T) checkAccess(user, (NamespaceInfo) info, mixedModeBehavior);
}
if (info instanceof StoreInfo) {
return (T) checkAccess(user, (StoreInfo) info, mixedModeBehavior);
}
if (info instanceof ResourceInfo) {
return (T) checkAccess(user, (ResourceInfo) info, mixedModeBehavior);
}
if (info instanceof LayerInfo) {
return (T) checkAccess(user, (LayerInfo) info, mixedModeBehavior, Collections.emptyList());
}
if (info instanceof LayerGroupInfo) {
return (T) checkAccess(user, (LayerGroupInfo) info, mixedModeBehavior, Collections.emptyList());
}
return info;
}
@SuppressWarnings("unchecked")
protected <T extends PublishedInfo> T checkAccess(Authentication user, T info, MixedModeBehavior mixedModeBehavior, List<LayerGroupInfo> containers) {
if (info instanceof LayerInfo) {
return (T) checkAccess(user, (LayerInfo) info, mixedModeBehavior, containers);
}
if (info instanceof LayerGroupInfo) {
return (T) checkAccess(user, (LayerGroupInfo) info, mixedModeBehavior, containers);
}
return info;
}
/**
* Given a {@link FeatureTypeInfo} and a user, returns it back if the user
* can access it in write mode, makes it read only if the user can access it
* in read only mode, returns null otherwise
*
*/
protected <T extends ResourceInfo> T checkAccess(Authentication user,
T info, MixedModeBehavior mixedModeBehavior) {
// handle null case
if (info == null)
return null;
// first off, handle the case where the user cannot even read the data
WrapperPolicy policy = buildWrapperPolicy(user, info, info.getName(), mixedModeBehavior);
// handle the modes that do not require wrapping
if(policy.level == AccessLevel.HIDDEN)
return null;
else if(policy.level == AccessLevel.READ_WRITE && policy.getLimits() == null)
return info;
// otherwise we are in a mixed case where the user can read but not write, or
// cannot read but is allowed by the operation mode to access the metadata
if(info instanceof FeatureTypeInfo) {
return (T) new SecuredFeatureTypeInfo((FeatureTypeInfo) info, policy);
} else if(info instanceof CoverageInfo) {
return (T) new SecuredCoverageInfo((CoverageInfo) info, policy);
} else if(info instanceof WMSLayerInfo) {
return (T) new SecuredWMSLayerInfo((WMSLayerInfo) info, policy);
} else {
throw new RuntimeException("Unknown resource type " + info.getClass());
}
}
/**
* Given a {@link StyleInfo} and a user, returns it back if the user can access it.
*
* @return <code>null</code> if the user can't acess the style, otherwise the original style.
*/
protected StyleInfo checkAccess(Authentication user, StyleInfo style, MixedModeBehavior mixedModeBehavior) {
if (style == null)
return null;
WrapperPolicy policy = buildWrapperPolicy(user, style, style.getName(), mixedModeBehavior);
// if we don't need to hide it, then we can return it as is since it
// can only provide metadata.
if(policy.level == AccessLevel.HIDDEN)
return null;
else
return style;
}
/**
* Given a store and a user, returns it back if the user can access its
* workspace in read mode, null otherwise
*
*/
protected <T extends StoreInfo> T checkAccess(Authentication user, T store, MixedModeBehavior mixedModeBehavior) {
if (store == null)
return null;
WrapperPolicy policy = buildWrapperPolicy(user, store.getWorkspace(), store.getName(), mixedModeBehavior);
// handle the modes that do not require wrapping
if(policy.level == AccessLevel.HIDDEN)
return null;
else if(policy.level == AccessLevel.READ_WRITE ||
(policy.level == AccessLevel.READ_ONLY && store instanceof CoverageStoreInfo))
return store;
// otherwise we are in a mixed case where the user can read but not
// write, or
// cannot read but is allowed by the operation mode to access the
// metadata
if (store instanceof DataStoreInfo) {
return (T) new SecuredDataStoreInfo((DataStoreInfo) store, policy);
} else if(store instanceof CoverageStoreInfo) {
return (T) new SecuredCoverageStoreInfo((CoverageStoreInfo) store, policy);
} else if (store instanceof WMSStoreInfo) {
// TODO: implement WMSStoreInfo wrappring if necessary
return store;
} else {
throw new RuntimeException("Unknown store type " + store.getClass());
}
}
/**
* Given a layer and a user, returns it back if the user can access it, null
* otherwise
*
*/
protected LayerInfo checkAccess(Authentication user, LayerInfo layer, MixedModeBehavior mixedModeBehavior, List<LayerGroupInfo> containers) {
if (layer == null)
return null;
// first off, handle the case where the user cannot even read the data
WrapperPolicy policy = buildWrapperPolicy(user, layer, layer.getName(), mixedModeBehavior, containers);
// handle the modes that do not require wrapping
if(policy.level == AccessLevel.HIDDEN)
return null;
else if(policy.level == AccessLevel.READ_WRITE && policy.getLimits() == null)
return layer;
// otherwise we are in a mixed case where the user can read but not write, or
// cannot read but is allowed by the operation mode to access the metadata
return new SecuredLayerInfo(layer, policy);
}
/**
* Given a layer group and a user, returns it back if the user can access
* it, null otherwise
*
*/
protected LayerGroupInfo checkAccess(Authentication user, LayerGroupInfo group, MixedModeBehavior mixedModeBehavior, List<LayerGroupInfo> containers) {
if (group == null)
return null;
//first check the layer group itself
WrapperPolicy policy = buildWrapperPolicy(user, group, group.getName(), mixedModeBehavior, containers);
if(policy.level == AccessLevel.HIDDEN) {
return null;
}
LayerInfo rootLayer = group.getRootLayer();
if (LayerGroupInfo.Mode.EO.equals(group.getMode())) {
// if the root cannot be used, blow up with an error in mixed mode
rootLayer = checkAccess(user, rootLayer, MixedModeBehavior.CHALLENGE);
if (rootLayer == null) {
return null;
}
}
List<LayerGroupInfo> extendedContainers = new ArrayList<LayerGroupInfo>(containers);
extendedContainers.add(group);
final List<PublishedInfo> layers = group.getLayers();
final List<StyleInfo> styles = group.getStyles();
final List<StyleInfo> selectedStyles = new ArrayList<>(layers.size());
ArrayList<PublishedInfo> wrapped = new ArrayList<>(layers.size());
for (int i = 0; i < layers.size(); i++) {
PublishedInfo layer = layers.get(i);
StyleInfo style = (styles != null && styles.size() > i) ? styles.get(i) : null;
// for nested layers, hide in mixed mode, the inner layers were not explicitly requested
PublishedInfo checked = checkAccess(user, layer, MixedModeBehavior.HIDE, extendedContainers);
if (checked != null) {
wrapped.add(checked);
selectedStyles.add(style);
}
}
// always wrap layergroups (secured layers could be added later)
return new SecuredLayerGroupInfo(group, rootLayer, wrapped, selectedStyles);
}
/**
* Given a namespace and user, returns it back if the user can access it,
* null otherwise
*
*/
protected <T extends NamespaceInfo> T checkAccess(Authentication user, T ns, MixedModeBehavior mixedModeBehavior) {
if(ns == null)
return null;
// route the security check thru the associated workspace info
WorkspaceInfo ws = delegate.getWorkspaceByName(ns.getPrefix());
if(ws == null) {
// temporary workaround, build a fake workspace, as we're probably
// in between a change of workspace/namespace name
ws = delegate.getFactory().createWorkspace();
ws.setName(ns.getPrefix());
}
WorkspaceInfo info = checkAccess(user, ws, mixedModeBehavior);
if (info == null)
return null;
else
return ns;
}
/**
* Given a workspace and user, returns it back if the user can access it,
* null otherwise
*
*/
protected <T extends WorkspaceInfo> T checkAccess(Authentication user, T ws, MixedModeBehavior mixedModeBehavior) {
if (ws == null)
return null;
WrapperPolicy policy = buildWrapperPolicy(user, ws, ws.getName(), mixedModeBehavior);
// if we don't need to hide it, then we can return it as is since it
// can only provide metadata.
if(policy.level == AccessLevel.HIDDEN)
return null;
else
return ws;
}
/**
* Check how an access manager responds to a user accessing a catalog object and return the result.
* @param accessManager the access manager to ask
* @param user the user accessing the object
* @param info the catalog object being accessed
* @return the combination of access level and response policy to apply to the request
*/
WrapperPolicy buildWrapperPolicy(@Nonnull ResourceAccessManager accessManager, Authentication user, @Nonnull CatalogInfo info, MixedModeBehavior mixedModeBehavior) {
Assert.notNull(info);
if (info instanceof NamespaceInfo) {
// route the security check thru the associated workspace info
WorkspaceInfo ws = delegate.getWorkspaceByName(((NamespaceInfo) info).getPrefix());
if (ws == null) {
// temporary workaround, build a fake workspace, as we're
// probably
// in between a change of workspace/namespace name
ws = delegate.getFactory().createWorkspace();
ws.setName(((NamespaceInfo) info).getPrefix());
}
return buildWrapperPolicy(accessManager, user, ws, ws.getName(), mixedModeBehavior);
}
if (info instanceof WorkspaceInfo) {
return buildWrapperPolicy(accessManager, user, info, ((WorkspaceInfo) info).getName(), mixedModeBehavior);
}
if (info instanceof StoreInfo) {
return buildWrapperPolicy(accessManager, user, ((StoreInfo) info).getWorkspace(),
((StoreInfo) info).getName(), mixedModeBehavior);
}
if (info instanceof ResourceInfo) {
return buildWrapperPolicy(accessManager, user, info, ((ResourceInfo) info).getName(), mixedModeBehavior);
}
if (info instanceof LayerInfo) {
return buildWrapperPolicy(accessManager, user, info, ((LayerInfo) info).getName(), mixedModeBehavior);
}
if (info instanceof LayerGroupInfo) {
// return the most restrictive policy that's not HIDDEN, or the
// first HIDDEN one
WrapperPolicy mostRestrictive = WrapperPolicy.readWrite(null);
for (PublishedInfo layer : ((LayerGroupInfo) info).getLayers()) {
WrapperPolicy policy = buildWrapperPolicy(accessManager, user, layer, layer.getName(), mixedModeBehavior);
if (AccessLevel.HIDDEN.equals(policy.getAccessLevel())) {
return policy;
}
int comparison = policy.compareTo(mostRestrictive);
boolean thisOneIsMoreRestrictive = comparison < 0;
if (thisOneIsMoreRestrictive) {
mostRestrictive = policy;
}
}
return mostRestrictive;
} else if(info instanceof StyleInfo){
return buildWrapperPolicy(accessManager, user, info, ((StyleInfo) info).getName(), mixedModeBehavior);
}
throw new IllegalArgumentException("Can't build wrapper policy for objects of type "
+ info.getClass().getName());
}
protected WrapperPolicy buildWrapperPolicy(Authentication user, @Nonnull CatalogInfo info, MixedModeBehavior mixedModeBehavior) {
return buildWrapperPolicy(accessManager, user, info, mixedModeBehavior);
}
/**
* Factors out the policy that decides what access level the current user
* has to a specific resource considering the read/write access, the security
* mode, and the filtering status
* @param user
* @param canRead
* @param canWrite
* @param resourceName
*
*/
public WrapperPolicy buildWrapperPolicy(Authentication user,
CatalogInfo info, String resourceName, MixedModeBehavior mixedModeBehavior) {
return SecureCatalogImpl.buildWrapperPolicy(accessManager, user, info, resourceName, mixedModeBehavior, Collections.emptyList());
}
/**
* Factors out the policy that decides what access level the current user
* has to a specific resource considering the read/write access, the security
* mode, and the filtering status
* @param user
* @param canRead
* @param canWrite
* @param resourceName
*
*/
public WrapperPolicy buildWrapperPolicy(Authentication user,
CatalogInfo info, String resourceName, MixedModeBehavior mixedModeBehavior, List<LayerGroupInfo> containers) {
return SecureCatalogImpl.buildWrapperPolicy(accessManager, user, info, resourceName, mixedModeBehavior, containers);
}
static WrapperPolicy buildWrapperPolicy(ResourceAccessManager accessManager,
Authentication user, CatalogInfo info, String resourceName, MixedModeBehavior mixedModeBehavior) {
return buildWrapperPolicy(accessManager, user, info, resourceName, mixedModeBehavior, Collections.emptyList());
}
static WrapperPolicy buildWrapperPolicy(ResourceAccessManager accessManager,
Authentication user, CatalogInfo info, String resourceName, MixedModeBehavior mixedModeBehavior, List<LayerGroupInfo> containers) {
boolean canRead = true;
boolean canWrite = true;
AccessLimits limits;
if(info instanceof WorkspaceInfo) {
// unsure here... shall we disallow writing? Only catalog and config
// related code should be playing with stores directly, so it's more of a
// matter if you can admin a workspace or not
limits = accessManager.getAccessLimits(user, (WorkspaceInfo) info);
WorkspaceAccessLimits wl = (WorkspaceAccessLimits) limits;
if(wl != null) {
if (wl.isAdminable()) {
canRead = canWrite = true;
}
else {
canRead = wl.isReadable();
canWrite = wl.isWritable();
}
}
if (AdminRequest.get() != null) {
//admin request
if (wl == null || !wl.isAdminable()) {
canRead = canWrite = false;
}
}
} else if(info instanceof LayerInfo || info instanceof ResourceInfo) {
DataAccessLimits dl;
WorkspaceAccessLimits wl;
if(info instanceof LayerInfo) {
dl = accessManager.getAccessLimits(user, (LayerInfo) info, containers);
wl = accessManager.getAccessLimits(user,
((LayerInfo)info).getResource().getStore().getWorkspace());
} else {
dl = accessManager.getAccessLimits(user, (ResourceInfo) info);
wl = accessManager.getAccessLimits(user,
((ResourceInfo)info).getStore().getWorkspace());
}
if(dl != null) {
canRead = dl.getReadFilter() != Filter.EXCLUDE;
if(dl instanceof VectorAccessLimits) {
canWrite = ((VectorAccessLimits) dl).getWriteFilter() != Filter.EXCLUDE;
} else {
canWrite = false;
}
}
limits = dl;
if (AdminRequest.get() != null) {
if (wl != null && !wl.isAdminable()) {
canRead = false;
}
}
} else if (info instanceof StyleInfo || info instanceof LayerGroupInfo) {
WorkspaceInfo ws = null;
if (info instanceof StyleInfo) {
limits = accessManager.getAccessLimits(user, (StyleInfo) info);
ws = ((StyleInfo)info).getWorkspace();
}
else {
limits = accessManager.getAccessLimits(user, (LayerGroupInfo) info, containers);
ws = ((LayerGroupInfo)info).getWorkspace();
}
if (limits != null) {
canRead = false;
}
if (ws != null && AdminRequest.get() != null) {
WorkspaceAccessLimits wl = accessManager.getAccessLimits(user, ws);
if (wl != null) {
if (!wl.isAdminable()) {
canRead = false;
}
}
}
}else {
throw new IllegalArgumentException("Can't build the wrapper policy for objects " +
"other than workspace, layer or resource: " + info);
}
final CatalogMode mode = limits != null ? limits.getMode() : CatalogMode.HIDE;
if (!canRead) {
// if in hide mode, we just hide the resource
if (mode == CatalogMode.HIDE) {
return WrapperPolicy.hide(limits);
} else if (mode == CatalogMode.MIXED) {
// if request is a get capabilities and mixed, we hide again
if(mixedModeBehavior == MixedModeBehavior.HIDE) {
return WrapperPolicy.hide(limits);
} else {
// otherwise challenge the user for credentials
throw unauthorizedAccess(resourceName);
}
} else {
// for challenge mode we agree to show freely only the metadata, every
// other access will trigger a security exception
return WrapperPolicy.metadata(limits);
}
} else if (!canWrite) {
if (mode == CatalogMode.HIDE) {
return WrapperPolicy.readOnlyHide(limits);
} else {
return WrapperPolicy.readOnlyChallenge(limits);
}
}
return WrapperPolicy.readWrite(limits);
}
public static RuntimeException unauthorizedAccess(String resourceName) {
// not hide, and not filtering out a list, this
// is an unauthorized direct resource access, complain
Authentication user = user();
if (user == null || user.getAuthorities().size() == 0)
return new InsufficientAuthenticationException("Cannot access "
+ resourceName + " as anonymous");
else
return new AccessDeniedException("Cannot access "
+ resourceName + " with the current privileges");
}
public static RuntimeException unauthorizedAccess() {
// not hide, and not filtering out a list, this
// is an unauthorized direct resource access, complain
Authentication user = user();
if (user == null || user.getAuthorities().size() == 0)
return new InsufficientAuthenticationException("Operation unallowed with the current privileges");
else
return new AccessDeniedException("Operation unallowed with the current privileges");
}
/**
* Given a list of resources, returns a copy of it containing only the
* resources the user can access
*
* @param user
* @param resources
*
*
*/
protected <T extends ResourceInfo> List<T> filterResources(Authentication user,
List<T> resources) {
List<T> result = new ArrayList<T>();
for (T original : resources) {
T secured = checkAccess(user, original, MixedModeBehavior.HIDE);
if (secured != null)
result.add(secured);
}
return result;
}
/**
* Given a list of stores, returns a copy of it containing only the
* resources the user can access
*
* @param user
* @param resources
*
*
*/
protected <T extends StoreInfo> List<T> filterStores(Authentication user, List<T> resources) {
List<T> result = new ArrayList<T>();
for (T original : resources) {
T secured = checkAccess(user, original, MixedModeBehavior.HIDE);
if (secured != null)
result.add(secured);
}
return result;
}
/**
* Given a list of layer groups, returns a copy of it containing only the
* groups the user can access
*
* @param user
* @param groups
*
*
*/
protected List<LayerGroupInfo> filterGroups(Authentication user, List<LayerGroupInfo> groups) {
List<LayerGroupInfo> result = new ArrayList<LayerGroupInfo>();
for (LayerGroupInfo original : groups) {
LayerGroupInfo secured = checkAccess(user, original, MixedModeBehavior.HIDE);
if (secured != null)
result.add(secured);
}
return result;
}
/**
* Given a list of layers, returns a copy of it containing only the layers
* the user can access
*
* @param user
* @param layers
*
*
*/
protected List<LayerInfo> filterLayers(Authentication user, List<LayerInfo> layers) {
List<LayerInfo> result = new ArrayList<LayerInfo>();
for (LayerInfo original : layers) {
LayerInfo secured = checkAccess(user, original, MixedModeBehavior.HIDE);
if (secured != null)
result.add(secured);
}
return result;
}
/**
* Given a list of styles, returns a copy of it containing only the styles the user can access.
*
*/
protected List<StyleInfo> filterStyles(Authentication user, List<StyleInfo> styles) {
List<StyleInfo> result = new ArrayList<StyleInfo>();
for (StyleInfo original : styles) {
StyleInfo secured = checkAccess(user, original, MixedModeBehavior.HIDE);
if (secured != null)
result.add(secured);
}
return result;
}
/**
* Given a list of namespaces, returns a copy of it containing only the
* namespaces the user can access
*
* @param user
* @param namespaces
*
*
*/
protected <T extends NamespaceInfo> List<T> filterNamespaces(Authentication user,
List<T> namespaces) {
List<T> result = new ArrayList<T>();
for (T original : namespaces) {
T secured = checkAccess(user, original, MixedModeBehavior.HIDE);
if (secured != null)
result.add(secured);
}
return result;
}
/**
* Given a list of workspaces, returns a copy of it containing only the
* workspaces the user can access
*
* @param user
* @param namespaces
*
*
*/
protected <T extends WorkspaceInfo> List<T> filterWorkspaces(Authentication user,
List<T> workspaces) {
List<T> result = new ArrayList<T>();
for (T original : workspaces) {
T secured = checkAccess(user, original, MixedModeBehavior.HIDE);
if (secured != null)
result.add(secured);
}
return result;
}
// -------------------------------------------------------------------
// Unwrappers, used to make sure the lower level does not get hit by
// read only wrappers
// -------------------------------------------------------------------
static LayerGroupInfo unwrap(LayerGroupInfo layerGroup) {
if(layerGroup instanceof SecuredLayerGroupInfo)
return ((SecuredLayerGroupInfo) layerGroup).unwrap(LayerGroupInfo.class);
return layerGroup;
}
static LayerInfo unwrap(LayerInfo layer) {
if(layer instanceof SecuredLayerInfo)
return ((SecuredLayerInfo) layer).unwrap(LayerInfo.class);
return layer;
}
static ResourceInfo unwrap(ResourceInfo info) {
if(info instanceof SecuredFeatureTypeInfo)
return ((SecuredFeatureTypeInfo) info).unwrap(ResourceInfo.class);
if (info instanceof SecuredCoverageInfo)
return ((SecuredCoverageInfo) info).unwrap(ResourceInfo.class);
if (info instanceof SecuredWMSLayerInfo)
return ((SecuredWMSLayerInfo) info).unwrap(ResourceInfo.class);
return info;
}
static StoreInfo unwrap(StoreInfo info) {
if(info instanceof SecuredDataStoreInfo)
return ((SecuredDataStoreInfo) info).unwrap(StoreInfo.class);
return info;
}
public static Object unwrap( Object obj ) {
if ( obj instanceof LayerGroupInfo ) {
return unwrap((LayerGroupInfo)obj);
}
if ( obj instanceof LayerInfo ) {
return unwrap((LayerInfo)obj);
}
if ( obj instanceof ResourceInfo ) {
return unwrap((ResourceInfo)obj);
}
if ( obj instanceof StoreInfo ) {
return unwrap((StoreInfo)obj);
}
if ( obj instanceof SecureCatalogImpl ) {
return ((SecureCatalogImpl)obj).delegate;
}
return obj;
}
// -------------------------------------------------------------------
// PURE DELEGATING METHODS
// (MapInfo being here since its role in the grand scheme of things
// is still undefined)
// -------------------------------------------------------------------
public MapInfo getMap(String id) {
return delegate.getMap(id);
}
public MapInfo getMapByName(String name) {
return delegate.getMapByName(name);
}
public List<MapInfo> getMaps() {
return delegate.getMaps();
}
public void add(LayerGroupInfo layerGroup) {
delegate.add(unwrap(layerGroup));
}
public ValidationResult validate(LayerGroupInfo layerGroup, boolean isNew) {
return delegate.validate(unwrap(layerGroup), isNew);
}
public LayerGroupInfo detach(LayerGroupInfo layerGroup) {
return delegate.detach(layerGroup);
}
public void add(LayerInfo layer) {
delegate.add(unwrap(layer));
}
public ValidationResult validate(LayerInfo layer, boolean isNew) {
return delegate.validate(unwrap(layer), isNew);
}
public LayerInfo detach(LayerInfo layer) {
return delegate.detach(layer);
}
public void add(MapInfo map) {
delegate.add(map);
}
public MapInfo detach(MapInfo map) {
return delegate.detach(map);
}
public void add(NamespaceInfo namespace) {
delegate.add(namespace);
}
public ValidationResult validate(NamespaceInfo namespace, boolean isNew) {
return delegate.validate(namespace, isNew);
}
public NamespaceInfo detach(NamespaceInfo namespace) {
return delegate.detach(namespace);
}
public void add(ResourceInfo resource) {
delegate.add(unwrap(resource));
}
public ValidationResult validate(ResourceInfo resource, boolean isNew) {
return delegate.validate(unwrap(resource), isNew);
}
public <T extends ResourceInfo> T detach(T resource) {
return delegate.detach(resource);
}
public void add(StoreInfo store) {
delegate.add(unwrap(store));
}
public ValidationResult validate(StoreInfo store, boolean isNew) {
return delegate.validate(unwrap(store), isNew);
}
public <T extends StoreInfo> T detach(T store) {
return delegate.detach(store);
}
public void add(StyleInfo style) {
delegate.add(style);
}
public ValidationResult validate(StyleInfo style, boolean isNew) {
return delegate.validate(style, isNew);
}
public StyleInfo detach(StyleInfo style) {
return delegate.detach(style);
}
public void add(WorkspaceInfo workspace) {
delegate.add(workspace);
}
public ValidationResult validate(WorkspaceInfo workspace, boolean isNew) {
return delegate.validate(workspace, isNew);
}
public WorkspaceInfo detach(WorkspaceInfo workspace) {
return delegate.detach(workspace);
}
public void addListener(CatalogListener listener) {
delegate.addListener(listener);
}
public void dispose() {
delegate.dispose();
}
public CatalogFacade getFacade() {
return new SecureCatalogFacade(this, delegate.getFacade());
}
public CatalogFactory getFactory() {
return new DecoratingCatalogFactory (delegate.getFactory()) {
@Override
public LayerGroupInfo createLayerGroup() {
//always wrap layergroups (secured layers could be added later)
return new SecuredLayerGroupInfo(delegate.createLayerGroup(), null, new ArrayList<>(), new ArrayList<>());
}
};
}
public Collection<CatalogListener> getListeners() {
return delegate.getListeners();
}
public void fireAdded(CatalogInfo object) {
delegate.fireAdded(object);
}
public void fireModified(CatalogInfo object, List<String> propertyNames, List oldValues,
List newValues) {
delegate.fireModified(object, propertyNames, oldValues, newValues);
}
public void firePostModified(CatalogInfo object, List<String> propertyNames, List oldValues,
List newValues) {
delegate.firePostModified(object, propertyNames, oldValues, newValues);
}
public void fireRemoved(CatalogInfo object) {
delegate.fireRemoved(object);
}
// TODO: why is resource pool being exposed???
public ResourcePool getResourcePool() {
return delegate.getResourcePool();
}
public StyleInfo getStyle(String id) {
return delegate.getStyle(id);
}
public StyleInfo getStyleByName(String name) {
return checkAccess(user(), delegate.getStyleByName(name), MixedModeBehavior.CHALLENGE);
}
public StyleInfo getStyleByName(String workspaceName, String name) {
return checkAccess(user(), delegate.getStyleByName(workspaceName, name), MixedModeBehavior.CHALLENGE);
}
public StyleInfo getStyleByName(WorkspaceInfo workspace, String name) {
return checkAccess(user(), delegate.getStyleByName(workspace, name), MixedModeBehavior.CHALLENGE);
}
public List<StyleInfo> getStyles() {
return filterStyles(user(),delegate.getStyles());
}
public List<StyleInfo> getStylesByWorkspace(String workspaceName) {
return filterStyles(user(),delegate.getStylesByWorkspace(workspaceName));
}
public List<StyleInfo> getStylesByWorkspace(WorkspaceInfo workspace) {
return filterStyles(user(),delegate.getStylesByWorkspace(workspace));
}
public void remove(LayerGroupInfo layerGroup) {
delegate.remove(unwrap(layerGroup));
}
public void remove(LayerInfo layer) {
delegate.remove(unwrap(layer));
}
public void remove(MapInfo map) {
delegate.remove(map);
}
public void remove(NamespaceInfo namespace) {
delegate.remove(namespace);
}
public void remove(ResourceInfo resource) {
delegate.remove(unwrap(resource));
}
public void remove(StoreInfo store) {
delegate.remove(unwrap(store));
}
public void remove(StyleInfo style) {
delegate.remove(style);
}
public void remove(WorkspaceInfo workspace) {
delegate.remove(workspace);
}
public void removeListener(CatalogListener listener) {
delegate.removeListener(listener);
}
public void save(LayerGroupInfo layerGroup) {
delegate.save(unwrap(layerGroup));
}
public void save(LayerInfo layer) {
delegate.save(unwrap(layer));
}
public void save(MapInfo map) {
delegate.save(map);
}
public void save(NamespaceInfo namespace) {
delegate.save(namespace);
}
public void save(ResourceInfo resource) {
delegate.save(unwrap(resource));
}
public void save(StoreInfo store) {
delegate.save(unwrap(store));
}
public void save(StyleInfo style) {
delegate.save(style);
}
public void save(WorkspaceInfo workspace) {
delegate.save(workspace);
}
public void setDefaultNamespace(NamespaceInfo defaultNamespace) {
delegate.setDefaultNamespace(defaultNamespace);
}
public void setDefaultWorkspace(WorkspaceInfo workspace) {
delegate.setDefaultWorkspace(workspace);
}
public void setResourcePool(ResourcePool resourcePool) {
delegate.setResourcePool(resourcePool);
}
public GeoServerResourceLoader getResourceLoader() {
return delegate.getResourceLoader();
}
public void setResourceLoader(GeoServerResourceLoader resourceLoader) {
delegate.setResourceLoader(resourceLoader);
}
public void accept(CatalogVisitor visitor) {
delegate.accept(visitor);
}
public DataStoreInfo getDefaultDataStore(WorkspaceInfo workspace) {
return checkAccess(user(), delegate.getDefaultDataStore(workspace), MixedModeBehavior.CHALLENGE);
}
public void setDefaultDataStore(WorkspaceInfo workspace, DataStoreInfo defaultStore) {
delegate.setDefaultDataStore(workspace, defaultStore);
}
@Override
public <T extends CatalogInfo> int count(Class<T> of, Filter filter) {
Filter securityFilter = securityFilter(of, filter);
final int count = delegate.count(of, securityFilter);
return count;
}
@Override
public <T extends CatalogInfo> T get(Class<T> type, Filter filter)
throws IllegalArgumentException {
Filter securityFilter = securityFilter(type, filter);
T result = delegate.get(type, securityFilter);
return result;
}
@Override
public <T extends CatalogInfo> CloseableIterator<T> list(Class<T> of, Filter filter) {
return list(of, filter, null, null, (SortBy) null);
}
@Override
public <T extends CatalogInfo> CloseableIterator<T> list(Class<T> of, Filter filter,
Integer offset, Integer count, SortBy sortBy) {
Filter securityFilter = securityFilter(of, filter);
CloseableIterator<T> filtered;
filtered = delegate.list(of, securityFilter, offset, count, sortBy);
// create secured decorators on-demand. Assume this method is used only for listing, not
// for accessing a single resource by name/id, thus use hide policy for mixed mode
final Function<T, T> securityWrapper = securityWrapper(of, MixedModeBehavior.HIDE);
final CloseableIterator<T> filteredWrapped;
filteredWrapped = CloseableIteratorAdapter.transform(filtered, securityWrapper);
// wrap the iterator in a notNull filter to ensure any filtered
// layers (result is null) don't get passed on from the securityWrapper
// Function. When the AccessLevel is HIDDEN and a layer gets filtered
// out via a CatalogFilter - for example, this can happen with a
// LocalWorkspaceCatalogFilter and a virtual service request
return CloseableIteratorAdapter.filter(filteredWrapped,
com.google.common.base.Predicates.<T> notNull());
}
public <T extends CatalogInfo> CloseableIterator<T> list(Class<T> of, Filter filter, Integer offset, Integer count,
SortBy... sortBy) {
Filter securityFilter = securityFilter(of, filter);
CloseableIterator<T> filtered;
// HACK here, go straigth to the facade of the delegate to get a method supporting sortby[]
filtered = delegate.getFacade().list(of, securityFilter, offset, count, sortBy);
// create secured decorators on-demand. Assume this method is used only for listing, not
// for accessing a single resource by name/id, thus use hide policy for mixed mode
final Function<T, T> securityWrapper = securityWrapper(of, MixedModeBehavior.HIDE);
final CloseableIterator<T> filteredWrapped;
filteredWrapped = CloseableIteratorAdapter.transform(filtered, securityWrapper);
// wrap the iterator in a notNull filter to ensure any filtered
// layers (result is null) don't get passed on from the securityWrapper
// Function. When the AccessLevel is HIDDEN and a layer gets filtered
// out via a CatalogFilter - for example, this can happen with a
// LocalWorkspaceCatalogFilter and a virtual service request
return CloseableIteratorAdapter.filter(filteredWrapped,
com.google.common.base.Predicates.<T> notNull());
}
/**
* @return a Function that applies a security wrapper over the catalog object given to it as
* input
* @see #checkAccess(Authentication, CatalogInfo)
*/
private <T extends CatalogInfo> Function<T, T> securityWrapper(final Class<T> forClass, MixedModeBehavior mixedModeBehavior) {
final Authentication user = user();
return new Function<T, T>() {
@Override
public T apply(T input) {
T checked = checkAccess(user, input, mixedModeBehavior);
return checked;
}
};
}
/**
* Returns a predicate that checks whether the current user has access to a given object of type
* {@code infoType}.
* <p>
* IMPLEMENTATION NOTE: the predicate returned evaluates in-process and hence can't be encoded
* to the catalog's native query language, if any. It calls
* {@link #buildWrapperPolicy(Authentication, CatalogInfo)} to check if the returned access
* level is not "hidden" on a case by case basis. Perhaps, the check for whether a given
* resource is accessible to the current user can be encoded as a "well known" predicate that
* uses one or a combination of the property equals/isnull/contains/exists verbs in the
* {@link Predicates} utility. I (GR), at the time of writing, don't know how to do that, so any
* help would be much appreciated. Nonetheless, this predicate is meant to be "and'ed" with any
* other predicate this catalog wrapper is called with, giving the Catalog backend a chance to
* at least encode the "well known" part of the resulting filter, and separate out the
* in-process evaluation of access credentials from the construction of the security wrapper for
* each object.
*
* @return a catalog Predicate that evaluates if an object of the required type is accessible to
* the given user
*/
private <T extends CatalogInfo> Filter securityFilter(final Class<T> infoType,
final Filter filter) {
final Authentication user = user();
if (isAdmin(user)) {
// no need to check for credentials if user is _the_ administrator
return filter;
}
if (MapInfo.class.isAssignableFrom(infoType)) {
// these kind of objects are not secured
return filter;
}
Filter securityFilter = this.accessManager.getSecurityFilter(user, infoType);
// create a filter combined with the security credentials check
return Predicates.and(filter, securityFilter);
}
/**
* Checks if the current user is authenticated and is the administrator.
* Protected to allow overriding in tests.
*/
protected boolean isAdmin(Authentication authentication) {
return GeoServerExtensions.bean(GeoServerSecurityManager.class).
checkAuthenticationForAdminRole(authentication);
}
public void removeListeners(Class listenerClass) {
delegate.removeListeners(listenerClass);
}
}