/* (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.impl; import static org.easymock.EasyMock.anyObject; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createNiceMock; import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Properties; import org.easymock.Capture; import org.easymock.EasyMock; import org.easymock.IAnswer; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CatalogInfo; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.CoverageStoreInfo; import org.geoserver.catalog.DataStoreInfo; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerGroupHelper; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerGroupInfo.Mode; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.catalog.PublishedInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.StoreInfo; import org.geoserver.catalog.StyleInfo; import org.geoserver.catalog.WMSLayerInfo; import org.geoserver.catalog.WMSStoreInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.catalog.util.CloseableIterator; import org.geoserver.catalog.util.CloseableIteratorAdapter; import org.geoserver.ows.Dispatcher; import org.geoserver.ows.Request; import org.geoserver.platform.GeoServerExtensionsHelper; import org.geoserver.security.ResourceAccessManager; import org.geoserver.security.ResourceAccessManagerWrapper; import org.geoserver.security.SecureCatalogImpl; import org.geotools.data.DataStore; import org.geotools.data.FeatureStore; import org.geotools.factory.Hints; import org.junit.After; import org.junit.Before; import org.opengis.filter.Filter; import org.opengis.filter.sort.SortBy; import org.opengis.util.ProgressListener; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; public abstract class AbstractAuthorizationTest extends SecureObjectsTest { private static final String NULL_STRING = (String) null; protected Authentication rwUser; protected Authentication roUser; protected Authentication anonymous; protected Authentication milUser; protected TestingAuthenticationToken root; protected Catalog catalog; protected WorkspaceInfo toppWs; protected WorkspaceInfo nurcWs; protected LayerInfo statesLayer; protected LayerInfo landmarksLayer; protected LayerInfo basesLayer; protected LayerInfo arcGridLayer; protected LayerInfo roadsLayer; protected FeatureTypeInfo states; protected CoverageInfo arcGrid; protected FeatureTypeInfo roads; protected FeatureTypeInfo landmarks; protected FeatureTypeInfo bases; protected DataStoreInfo statesStore; protected DataStoreInfo roadsStore; protected CoverageStoreInfo arcGridStore; protected StyleInfo pointStyle; protected StyleInfo lineStyle; protected LayerGroupInfo layerGroupGlobal; protected LayerGroupInfo layerGroupTopp; protected LayerGroupInfo layerGroupWithSomeLockedLayer; protected List<LayerInfo> layers; protected List<FeatureTypeInfo> featureTypes; protected List<CoverageInfo> coverages; protected List<WorkspaceInfo> workspaces; protected SecureCatalogImpl sc; protected LayerInfo cascadedLayer; protected LayerInfo forestsLayer; protected WMSLayerInfo cascaded; protected List<WMSLayerInfo> wmsLayers; protected LayerGroupInfo namedTreeA; protected LayerGroupInfo containerTreeB; protected LayerGroupInfo singleGroupC; protected LayerGroupInfo wsContainerD; protected LayerGroupInfo nestedContainerE; protected LayerInfo citiesLayer; protected FeatureTypeInfo cities; protected List<LayerGroupInfo> layerGroups; @Before public void setUp() throws Exception { rwUser = new TestingAuthenticationToken("rw", "supersecret", Arrays.asList(new GrantedAuthority[] { new GeoServerRole("READER"), new GeoServerRole("WRITER") })); roUser = new TestingAuthenticationToken("ro", "supersecret", Arrays.asList( new GrantedAuthority[] { new GeoServerRole("READER") })); anonymous = new TestingAuthenticationToken("anonymous", null); milUser = new TestingAuthenticationToken("military", "supersecret", Arrays.asList(new GrantedAuthority[] { new GeoServerRole("MILITARY") })); root = new TestingAuthenticationToken("admin", "geoserver", Arrays.asList(new GrantedAuthority[] { new GeoServerRole(SecureTreeNode.ROOT_ROLE) })); catalog = createNiceMock(Catalog.class); expect(catalog.getWorkspace((String) anyObject())).andReturn( createNiceMock(WorkspaceInfo.class)).anyTimes(); replay(catalog); toppWs = createNiceMock(WorkspaceInfo.class); expect(toppWs.getName()).andReturn("topp").anyTimes(); replay(toppWs); nurcWs = createNiceMock(WorkspaceInfo.class); expect(nurcWs.getName()).andReturn("nurc").anyTimes(); replay(nurcWs); statesLayer = buildLayer("states", toppWs, FeatureTypeInfo.class, false); roadsLayer = buildLayer("roads", toppWs, FeatureTypeInfo.class, false); citiesLayer = buildLayer("cities", nurcWs, FeatureTypeInfo.class); landmarksLayer = buildLayer("landmarks", toppWs, FeatureTypeInfo.class); basesLayer = buildLayer("bases", toppWs, FeatureTypeInfo.class); forestsLayer = buildLayer("forests", toppWs, FeatureTypeInfo.class); // let's add one with a dot inside the name arcGridLayer = buildLayer("arc.grid", nurcWs, CoverageInfo.class); // resources states = (FeatureTypeInfo) statesLayer.getResource(); statesStore = states.getStore(); arcGrid = (CoverageInfo) arcGridLayer.getResource(); arcGridStore = arcGrid.getStore(); roads = (FeatureTypeInfo) roadsLayer.getResource(); cities = (FeatureTypeInfo) citiesLayer.getResource(); roadsStore = roads.getStore(); landmarks = (FeatureTypeInfo) landmarksLayer.getResource(); bases = (FeatureTypeInfo) basesLayer.getResource(); // styles pointStyle = buildStyle("point", null); lineStyle = buildStyle("line", toppWs); // layer groups layerGroupGlobal = buildLayerGroup("layerGroup", pointStyle, null, arcGridLayer); layerGroupTopp = buildLayerGroup("layerGroupTopp", lineStyle, toppWs, statesLayer); layerGroupWithSomeLockedLayer = buildLayerGroup("layerGroupWithSomeLockedLayer", lineStyle, toppWs, statesLayer, roadsLayer); // container groups for testing group security namedTreeA = buildLayerGroup("namedTreeA", Mode.NAMED, null, statesLayer, roadsLayer, citiesLayer); nestedContainerE = buildLayerGroup("nestedContainerE", Mode.CONTAINER, null, forestsLayer); containerTreeB = buildLayerGroup("containerTreeB", Mode.CONTAINER, null, roadsLayer, landmarksLayer, nestedContainerE); singleGroupC = buildLayerGroup("singleGroupC", Mode.SINGLE, null, statesLayer, basesLayer); wsContainerD = buildLayerGroup("wsContainerD", Mode.CONTAINER, nurcWs, arcGridLayer); layerGroups = Arrays.asList(layerGroupGlobal, layerGroupTopp, layerGroupWithSomeLockedLayer, namedTreeA, containerTreeB, singleGroupC, wsContainerD, nestedContainerE); // cascaded WMS layer cascadedLayer = buildLayer("cascaded", toppWs, WMSLayerInfo.class); cascaded = (WMSLayerInfo) cascadedLayer.getResource(); } @After public void cleanupRequestThreadLocal() throws Exception { Dispatcher.REQUEST.remove(); } protected void setupRequestThreadLocal(String service) { Request request = new Request(); request.setService(service); Dispatcher.REQUEST.set(request); } protected LayerInfo buildLayer(String name, WorkspaceInfo ws, Class<? extends ResourceInfo> resourceClass) throws Exception { return buildLayer(name, ws, resourceClass, true); } protected LayerInfo buildLayer(String name, WorkspaceInfo ws, Class<? extends ResourceInfo> resourceClass, boolean advertised) throws Exception { FeatureStore fs = createNiceMock(FeatureStore.class); replay(fs); DataStore dstore = createNiceMock(DataStore.class); replay(dstore); StoreInfo store; if (resourceClass.equals(CoverageInfo.class)) { store = createNiceMock(CoverageStoreInfo.class); } else if (resourceClass.equals(WMSLayerInfo.class)) { store = createNiceMock(WMSStoreInfo.class); } else { store = createNiceMock(DataStoreInfo.class); expect((DataStore)((DataStoreInfo) store).getDataStore(null)).andReturn(dstore); } expect(store.getWorkspace()).andReturn(ws).anyTimes(); replay(store); NamespaceInfo ns = createNiceMock(NamespaceInfo.class); expect(ns.getName()).andReturn(ws.getName()).anyTimes(); expect(ns.getPrefix()).andReturn(ws.getName()).anyTimes(); expect(ns.getURI()).andReturn("http://www.geoserver.org/test/" + ws.getName()).anyTimes(); replay(ns); ResourceInfo resource = createNiceMock(resourceClass); expect(resource.getStore()).andReturn(store).anyTimes(); expect(resource.getName()).andReturn(name).anyTimes(); expect(resource.getPrefixedName()).andReturn(ws.getName() + ":" + name).anyTimes(); expect(resource.prefixedName()).andReturn(ws.getName() + ":" + name).anyTimes(); expect(resource.getNamespace()).andReturn(ns).anyTimes(); if (resource instanceof FeatureTypeInfo) { expect( ((FeatureTypeInfo) resource).getFeatureSource((ProgressListener) anyObject(), (Hints) anyObject())).andReturn(fs).anyTimes(); } if (!advertised) expect(resource.isAdvertised()).andReturn(advertised).anyTimes(); expect(resource.getId()).andReturn(name + "-id").anyTimes(); replay(resource); LayerInfo layer = createNiceMock(LayerInfo.class); expect(layer.getName()).andReturn(name).anyTimes(); expect(layer.getPrefixedName()).andReturn(ws.getName() + ":" + name).anyTimes(); expect(layer.prefixedName()).andReturn(ws.getName() + ":" + name).anyTimes(); expect(layer.getResource()).andReturn(resource).anyTimes(); expect(layer.getId()).andReturn(name + "-lid").anyTimes(); if (!advertised) expect(layer.isAdvertised()).andReturn(advertised).anyTimes(); replay(layer); return layer; } protected StyleInfo buildStyle(String name, WorkspaceInfo ws) { StyleInfo style = createNiceMock(StyleInfo.class); expect(style.getName()).andReturn(name).anyTimes(); expect(style.getFilename()).andReturn(name+".sld").anyTimes(); expect(style.getWorkspace()).andReturn(ws).anyTimes(); replay(style); return style; } protected LayerGroupInfo buildLayerGroup(String name, StyleInfo style, WorkspaceInfo ws, LayerInfo... layer) { return buildLayerGroup(name, LayerGroupInfo.Mode.SINGLE, ws, layer); } protected LayerGroupInfo buildLayerGroup(String name, LayerGroupInfo.Mode type, WorkspaceInfo ws, PublishedInfo... contents) { LayerGroupInfo layerGroup = createNiceMock(LayerGroupInfo.class); expect(layerGroup.getName()).andReturn(name).anyTimes(); expect(layerGroup.prefixedName()).andReturn((ws != null ? ws.getName() + ":" : "") + name).anyTimes(); expect(layerGroup.getMode()).andReturn(type).anyTimes(); expect(layerGroup.getLayers()).andReturn(new ArrayList<PublishedInfo>(Arrays.asList(contents))).anyTimes(); expect(layerGroup.getStyles()).andReturn(buildUniqueStylesForLayers(contents)).anyTimes(); expect(layerGroup.getWorkspace()).andReturn(ws).anyTimes(); expect(layerGroup.layers()).andAnswer(() -> new LayerGroupHelper(layerGroup).allLayers()).anyTimes(); expect(layerGroup.getId()).andAnswer(() -> (ws == null ? name : ws.getName() + ":" + name) + "-id").anyTimes(); replay(layerGroup); return layerGroup; } private List<StyleInfo> buildUniqueStylesForLayers(PublishedInfo[] contents) { if(contents == null) { return null; } List<StyleInfo> result = new ArrayList<>(); for (PublishedInfo pi : contents) { if(pi instanceof LayerInfo) { StyleInfo style = buildStyle(pi.prefixedName().replace(':', '-') + "-style", null); result.add(style); } else { // group result.add(null); } } return result; } protected LayerGroupInfo buildEOLayerGroup(String name, LayerInfo rootLayer, StyleInfo style, WorkspaceInfo ws, PublishedInfo... contents) { LayerGroupInfo layerGroup = createNiceMock(LayerGroupInfo.class); expect(layerGroup.getName()).andReturn(name).anyTimes(); expect(layerGroup.prefixedName()).andReturn((ws != null ? ws.getName() + ":" : "") + name).anyTimes(); expect(layerGroup.getMode()).andReturn(Mode.EO).anyTimes(); expect(layerGroup.getRootLayer()).andReturn(rootLayer).anyTimes(); expect(layerGroup.getLayers()).andReturn(new ArrayList<PublishedInfo>(Arrays.asList(contents))).anyTimes(); expect(layerGroup.getStyles()).andReturn(Arrays.asList(style)).anyTimes(); expect(layerGroup.getWorkspace()).andReturn(ws).anyTimes(); expect(layerGroup.layers()).andAnswer(() -> new LayerGroupHelper(layerGroup).allLayers()).anyTimes(); expect(layerGroup.getId()).andAnswer(() -> (ws == null ? name : ws.getName() + ":" + name) + "-id").anyTimes(); replay(layerGroup); return layerGroup; } protected ResourceAccessManager buildManager(String propertyFile) throws Exception { return buildManager(propertyFile, null); } protected ResourceAccessManager buildManager(String propertyFile, ResourceAccessManagerWrapper wrapper) throws Exception { ResourceAccessManager manager = buildAccessManager(propertyFile); if (wrapper != null) { wrapper.setDelegate(manager); manager = wrapper; } sc = new SecureCatalogImpl(catalog, manager) { @Override protected boolean isAdmin(Authentication authentication) { return false; } }; GeoServerExtensionsHelper.singleton("secureCatalog", sc, SecureCatalogImpl.class); return manager; } protected DefaultResourceAccessManager buildAccessManager(String propertyFile) throws Exception { Properties props = new Properties(); props.load(getClass().getResourceAsStream(propertyFile)); return new DefaultResourceAccessManager(new MemoryDataAccessRuleDAO(catalog, props), catalog); } /** * Sets up a mock catalog. */ protected void populateCatalog() { // build resource collections layers = Arrays.asList(statesLayer, roadsLayer, landmarksLayer, basesLayer, arcGridLayer, cascadedLayer); featureTypes = new ArrayList<>(); coverages = new ArrayList<>(); wmsLayers = new ArrayList<>(); for (LayerInfo layer : layers) { if (layer.getResource() instanceof FeatureTypeInfo) { featureTypes.add((FeatureTypeInfo) layer.getResource()); } else if (layer.getResource() instanceof WMSLayerInfo) { wmsLayers.add((WMSLayerInfo) layer.getResource()); } else { coverages.add((CoverageInfo) layer.getResource()); } } workspaces = Arrays.asList(toppWs, nurcWs); // prime the catalog catalog = createNiceMock(Catalog.class); expect(catalog.getFeatureTypeByName("topp:states")).andReturn(states) .anyTimes(); expect(catalog.getLayerByName("topp:cascaded")).andReturn(cascadedLayer) .anyTimes(); expect(catalog.getResourceByName("topp:cascaded", WMSLayerInfo.class)).andReturn(cascaded) .anyTimes(); expect(catalog.getResourceByName("topp:states", FeatureTypeInfo.class)).andReturn( states).anyTimes(); expect(catalog.getLayerByName("topp:states")).andReturn(statesLayer).anyTimes(); expect(catalog.getCoverageByName("nurc:arcgrid")).andReturn(arcGrid) .anyTimes(); expect(catalog.getResourceByName("nurc:arcgrid", CoverageInfo.class)).andReturn( arcGrid).anyTimes(); expect(catalog.getFeatureTypeByName("topp:roads")).andReturn(roads) .anyTimes(); expect(catalog.getFeatureTypeByName("nurc:cities")).andReturn(cities) .anyTimes(); expect(catalog.getLayerByName("topp:roads")).andReturn(roadsLayer).anyTimes(); expect(catalog.getLayerByName("nurc:cities")).andReturn(citiesLayer).anyTimes(); expect(catalog.getFeatureTypeByName("topp:landmarks")).andReturn( landmarks).anyTimes(); expect(catalog.getFeatureTypeByName("topp:bases")).andReturn(bases) .anyTimes(); expect(catalog.getDataStoreByName("states")).andReturn(statesStore) .anyTimes(); expect(catalog.getDataStoreByName("roads")).andReturn(roadsStore) .anyTimes(); expect(catalog.getCoverageStoreByName("arcGrid")).andReturn( arcGridStore).anyTimes(); expect(catalog.getLayerByName("topp:landmarks")).andReturn(landmarksLayer).anyTimes(); expect(catalog.getLayerByName("topp:bases")).andReturn(basesLayer).anyTimes(); expect(catalog.getLayerByName("nurc:arc.grid")).andReturn(arcGridLayer).anyTimes(); expect(catalog.getLayerByName("topp:forests")).andReturn(forestsLayer).anyTimes(); expect(catalog.getLayers()).andReturn(layers).anyTimes(); stubList(catalog, LayerInfo.class, layers); expect(catalog.getFeatureTypes()).andReturn(featureTypes).anyTimes(); stubList(catalog, FeatureTypeInfo.class, featureTypes); expect(catalog.getCoverages()).andReturn(coverages).anyTimes(); stubList(catalog, CoverageInfo.class, coverages); expect(catalog.getWorkspaces()).andReturn(workspaces).anyTimes(); stubList(catalog, WorkspaceInfo.class, workspaces); stubList(catalog, StyleInfo.class, Arrays.asList(pointStyle, lineStyle)); expect(catalog.getWorkspaceByName("topp")).andReturn(toppWs).anyTimes(); expect(catalog.getWorkspaceByName("nurc")).andReturn(nurcWs).anyTimes(); expect(catalog.getStyles()).andReturn(Arrays.asList(pointStyle, lineStyle)).anyTimes(); expect(catalog.getStylesByWorkspace(toppWs)).andReturn(Arrays.asList(pointStyle, lineStyle)).anyTimes(); expect(catalog.getStylesByWorkspace(nurcWs)).andReturn(Arrays.asList(pointStyle)).anyTimes(); expect(catalog.getLayerGroups()).andReturn(layerGroups).anyTimes(); for (LayerGroupInfo lg : layerGroups) { expect(catalog.getLayerGroup(lg.getId())).andReturn(lg).anyTimes(); if(lg.getWorkspace() == null) { expect(catalog.getLayerGroupByName(lg.getName())).andReturn(lg).anyTimes(); expect(catalog.getLayerGroupByName(NULL_STRING, lg.getName())).andReturn(lg).anyTimes(); } else { expect(catalog.getLayerGroupByName(lg.getWorkspace(), lg.getName())).andReturn(lg).anyTimes(); expect(catalog.getLayerGroupByName(lg.getWorkspace().getName(), lg.getName())).andReturn(lg).anyTimes(); } } expect(catalog.getLayerGroupsByWorkspace("topp")).andReturn(Arrays.asList(new LayerGroupInfo[] { layerGroupTopp, layerGroupWithSomeLockedLayer })).anyTimes(); expect(catalog.getLayerGroupsByWorkspace("nurc")).andReturn(Arrays.asList(layerGroupGlobal)).anyTimes(); expect(catalog.list(eq(LayerGroupInfo.class), anyObject(Filter.class))).andAnswer(() -> { List<LayerGroupInfo> groups = catalog.getLayerGroups(); Filter f = (Filter) EasyMock.getCurrentArguments()[1]; Iterator<LayerGroupInfo> it = groups.stream().filter(lg -> f.evaluate(lg) ).iterator(); return new CloseableIteratorAdapter<LayerGroupInfo>(it); }).anyTimes(); replay(catalog); GeoServerExtensionsHelper.singleton("catalog", catalog); } <T extends CatalogInfo> void stubList(Catalog mock, Class<T> clazz, final List<T> source) { final Capture<Filter> cap = new Capture<Filter>(); expect(catalog.list(eq(clazz), capture(cap))).andStubAnswer(new IAnswer<CloseableIterator<T>>(){ @Override public CloseableIterator<T> answer() throws Throwable { return makeCIterator(source, cap.getValue()); } }); expect(catalog.list(eq(clazz), capture(cap), EasyMock.anyInt(), EasyMock.anyInt(), (SortBy)anyObject())).andStubAnswer(new IAnswer<CloseableIterator<T>>(){ @Override public CloseableIterator<T> answer() throws Throwable { return makeCIterator(source, cap.getValue()); } }); } static <T> CloseableIterator<T> makeCIterator(List<T> source, Filter f) { return CloseableIteratorAdapter.filter(source.iterator(), f); } }