/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.security.decorators; import static org.geoserver.security.decorators.SecurityUtils.*; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.geoserver.catalog.Wrapper; import org.geoserver.security.Response; import org.geoserver.security.SecureCatalogImpl; import org.geoserver.security.WrapperPolicy; import org.geotools.data.Query; import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureIterator; import org.geotools.feature.collection.DecoratingFeatureCollection; import org.geotools.util.logging.Logging; import org.opengis.feature.Feature; import org.opengis.feature.type.FeatureType; import org.opengis.filter.Filter; import org.opengis.filter.sort.SortBy; /** * Secures a feature collection according to the given policy. The implementation assumes * all of the attributes that should not be read have been shaved off already, and similarly, * that the read filters have been applied already in the delegate, and adds control over writes * * @author Andrea Aime - GeoSolutions * * @param <T> * @param <F> */ public class SecuredFeatureCollection<T extends FeatureType, F extends Feature> extends DecoratingFeatureCollection<T, F> { static final Logger LOGGER = Logging.getLogger(SecuredFeatureCollection.class); WrapperPolicy policy; SecuredFeatureCollection(FeatureCollection<T, F> delegate, WrapperPolicy policy) { super(delegate); this.policy = policy; } public Iterator iterator() { return (Iterator) SecuredObjects.secure(delegate.iterator(), policy); } @Override public org.geotools.feature.FeatureIterator<F> features() { return (FeatureIterator) SecuredObjects.secure(delegate.features(), policy); } public FeatureCollection<T, F> sort(SortBy order) { // attributes should have been shaved already final FeatureCollection<T, F> fc = delegate.sort(order); if(fc == null) return null; else return (FeatureCollection) SecuredObjects.secure(fc, policy); } public FeatureCollection<T, F> subCollection(Filter filter) { final FeatureCollection<T, F> fc = delegate.subCollection(filter); if(fc == null) return null; else return (FeatureCollection) SecuredObjects.secure(fc, policy); } @Override public void close(FeatureIterator<F> close) { if(close instanceof Wrapper && ((Wrapper) close).isWrapperFor(FeatureIterator.class)) delegate.close(((Wrapper) close).unwrap(FeatureIterator.class)); else delegate.close(close); } @Override public void close(Iterator<F> close) { if(close instanceof Wrapper && ((Wrapper) close).isWrapperFor(Iterator.class)) delegate.close(((Wrapper) close).unwrap(Iterator.class)); else delegate.close(close); } // --------------------------------------------------------------------- // Write related methods // --------------------------------------------------------------------- public boolean add(F o) { Query writeQuery = getWriteQuery(policy); final Filter filter = writeQuery.getFilter(); if(filter == Filter.EXCLUDE) { throw unsupportedOperation(); } else { if(filter.evaluate(o)) { if(writeQuery.getPropertyNames() == Query.ALL_NAMES) { return delegate.add(o); } else { // TODO: shave off attributes we cannot write LOGGER.log(Level.SEVERE, "Unfinished implementation, we need to shave off " + "the attributes one cannot write!"); return add(o); } } else { return false; } } } public boolean addAll(Collection c) { Query writeQuery = getWriteQuery(policy); final Filter filter = writeQuery.getFilter(); if(filter == Filter.EXCLUDE) { throw unsupportedOperation(); } else { List filtered = filterCollection(c, writeQuery); return addAll(filtered); } } /** * Filters out all features that cannot be modified/removed * @param collection * @param writeQuery * @return */ List filterCollection(Collection collection, Query writeQuery) { // warn about inability to shave off complex features if(writeQuery.getPropertyNames() != Query.ALL_NAMES) { LOGGER.log(Level.SEVERE, "Unfinished implementation, we need to shave off " + "the attributes one cannot write!"); } // filter out anything we cannot write final Filter filter = writeQuery.getFilter(); List filtered = new ArrayList(); for (Object feature : collection) { if(filter.evaluate(feature)) { filtered.add(feature); } } return filtered; } public void clear() { Query writeQuery = getWriteQuery(policy); final Filter filter = writeQuery.getFilter(); if(filter == Filter.EXCLUDE) { throw unsupportedOperation(); } else { delegate.clear(); } } public boolean remove(Object o) { Query writeQuery = getWriteQuery(policy); final Filter filter = writeQuery.getFilter(); if(filter == Filter.EXCLUDE) { throw unsupportedOperation(); } else { if(filter.evaluate(o)) { return delegate.remove(o); } else { return false; } } } public boolean removeAll(Collection c) { Query writeQuery = getWriteQuery(policy); final Filter filter = writeQuery.getFilter(); if(filter == Filter.EXCLUDE) { throw unsupportedOperation(); } else { List filtered = filterCollection(c, writeQuery); return removeAll(filtered); } } public boolean retainAll(Collection c) { // way too inefficient and fancy to implement, besides nothing in GS uses it and // even ContentFeatureCollection does not implement it, so just let it go, we'll // cross this bridge when necessary throw new UnsupportedOperationException("Sorry, not even ContentFeatureCollection implements this one"); } /** * Notifies the caller the requested operation is not supported, using a plain {@link UnsupportedOperationException} * in case we have to conceal the fact the data is actually writable, using an Spring security exception otherwise * to force an authentication from the user */ RuntimeException unsupportedOperation() { String typeName = getID(); if(policy.response == Response.CHALLENGE) { return SecureCatalogImpl.unauthorizedAccess(typeName); } else return new UnsupportedOperationException("Feature type " + typeName + " is read only"); } }