/* (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.web.data; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; import org.apache.wicket.ajax.form.OnChangeAjaxBehavior; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.CheckBox; import org.apache.wicket.markup.html.form.ChoiceRenderer; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.FormComponent; import org.apache.wicket.markup.html.form.SubmitLink; import org.apache.wicket.markup.html.form.validation.AbstractFormValidator; import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.PropertyModel; import org.geoserver.catalog.CatalogFacade; import org.geoserver.catalog.Predicates; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.util.CloseableIterator; import org.geoserver.security.AccessMode; import org.geoserver.security.impl.DataAccessRule; import org.geoserver.security.impl.GeoServerRole; import org.geoserver.security.web.AbstractSecurityPage; import org.geoserver.security.web.role.RuleRolesFormComponent; import org.geoserver.web.wicket.ParamResourceModel; import org.opengis.filter.Filter; /** * Abstract page binding a {@link DataAccessRule} */ @SuppressWarnings("serial") public abstract class AbstractDataAccessRulePage extends AbstractSecurityPage { public class RootLabelModel extends LoadableDetachableModel<String> { @Override protected String load() { if(globalGroupRule.getModelObject()) { return new ParamResourceModel("globalGroup", AbstractDataAccessRulePage.this).getString(); } else { return new ParamResourceModel("workspace", AbstractDataAccessRulePage.this).getString(); } } } public class RootsModel extends LoadableDetachableModel<List<String>> { @Override protected List<String> load() { if(globalGroupRule.getModelObject()) { return getGlobalLayerGroupNames(); } else { return getWorkspaceNames(); } } /** * Returns a sorted list of global layer group names */ List<String> getGlobalLayerGroupNames() { Stream<String> names = getCatalog().getLayerGroupsByWorkspace(CatalogFacade.NO_WORKSPACE).stream().map(lg -> lg.getName()).sorted(); return Stream.concat(Stream.of("*"), names).collect(Collectors.toList()); } /** * Returns a sorted list of workspace names */ List<String> getWorkspaceNames() { Stream<String> names = getCatalog().getWorkspaces().stream().map(ws -> ws.getName()).sorted(); return Stream.concat(Stream.of("*"), names).collect(Collectors.toList()); } } static List<AccessMode> MODES = Arrays.asList(AccessMode.READ, AccessMode.WRITE, AccessMode.ADMIN); DropDownChoice<String> rootChoice, layerChoice; DropDownChoice<AccessMode> accessModeChoice; RuleRolesFormComponent rolesFormComponent; CheckBox globalGroupRule; WebMarkupContainer layerContainer; Label rootLabel; WebMarkupContainer layerAndLabel; public AbstractDataAccessRulePage(final DataAccessRule rule) { // build the form Form form = new Form<DataAccessRule>("form", new CompoundPropertyModel(rule)); add(form); form.add(new EmptyRolesValidator()); form.add(globalGroupRule = new CheckBox("globalGroupRule")); globalGroupRule.setOutputMarkupId(true); globalGroupRule.add(new OnChangeAjaxBehavior() { @Override protected void onUpdate(AjaxRequestTarget target) { rootChoice.getModel().detach(); target.add(rootChoice); layerAndLabel.setVisible(!globalGroupRule.getModelObject()); target.add(layerContainer); rootLabel.getDefaultModel().detach(); target.add(rootLabel); } }); form.add(rootLabel = new Label("rootLabel", new RootLabelModel())); rootLabel.setOutputMarkupId(true); form.add(rootChoice = new DropDownChoice<String>("root", new RootsModel())); rootChoice.setRequired(true); rootChoice.setOutputMarkupId(true); rootChoice.add(new AjaxFormComponentUpdatingBehavior("change") { @Override protected void onUpdate(AjaxRequestTarget target) { layerChoice.setChoices(new Model<ArrayList<String>>( getLayerNames(rootChoice.getConvertedInput()))); layerChoice.modelChanged(); target.add(layerChoice); } }); form.add(layerContainer = new WebMarkupContainer("layerContainer")); layerContainer.setOutputMarkupId(true); layerContainer.add(layerAndLabel = new WebMarkupContainer("layerAndLabel")); layerAndLabel.add(layerChoice = new DropDownChoice<String>("layer", getLayerNames(rule.getRoot()))); layerAndLabel.setVisible(!rule.isGlobalGroupRule()); layerChoice.setRequired(true); layerChoice.setOutputMarkupId(true); form.add(accessModeChoice = new DropDownChoice<AccessMode>("accessMode", MODES, new AccessModeRenderer())); accessModeChoice.setRequired(true); form.add(rolesFormComponent = new RuleRolesFormComponent("roles", new PropertyModel(rule, "roles")).setHasAnyRole( rule.getRoles().contains(GeoServerRole.ANY_ROLE.getAuthority()))); // build the submit/cancel form.add(new SubmitLink("save") { @Override public void onSubmit() { DataAccessRule rule = (DataAccessRule) getForm().getModelObject(); if (rolesFormComponent.isHasAnyRole()) { rule.getRoles().clear(); rule.getRoles().add(GeoServerRole.ANY_ROLE.getAuthority()); } if(globalGroupRule.getModelObject()) { // just to be on the safe side rule.setLayer(null); } onFormSubmit(rule); } }); form.add(new BookmarkablePageLink<DataAccessRule>("cancel", DataSecurityPage.class)); } /** * Implements the actual save action */ protected abstract void onFormSubmit(DataAccessRule rule); /** * Returns a sorted list of layer names in the specified workspace (or * if the workspace is *) */ ArrayList<String> getLayerNames(String rootName) { ArrayList<String> result = new ArrayList<String>(); if (!rootName.equals("*")) { Filter wsResources = Predicates.equal("store.workspace.name", rootName); try(CloseableIterator<ResourceInfo> it = getCatalog().list(ResourceInfo.class, wsResources)) { while(it.hasNext()) { result.add(it.next().getName()); } } // collect also layer groups getCatalog().getLayerGroupsByWorkspace(rootName).stream().map(lg -> lg.getName()).forEach(name -> { if(!result.contains(name)) { result.add(name); } }); Collections.sort(result); } result.add(0, "*"); return result; } /** * Makes sure we see translated text, by the raw name is used for the model */ class AccessModeRenderer extends ChoiceRenderer<AccessMode> { public Object getDisplayValue(AccessMode object) { return (String) new ParamResourceModel( object.name(), getPage()) .getObject(); } public String getIdValue(AccessMode object, int index) { return object.name(); } } class EmptyRolesValidator extends AbstractFormValidator { @Override public FormComponent<?>[] getDependentFormComponents() { return new FormComponent[] { rootChoice, layerChoice, accessModeChoice, rolesFormComponent }; } @Override public void validate(Form<?> form) { // only validate on final submit if (form.findSubmittingButton() != form.get("save")) { return; } updateModels(); String roleInputString = rolesFormComponent.getPalette().getRecorderComponent().getInput(); if ((roleInputString == null || roleInputString.trim().isEmpty()) && !rolesFormComponent.isHasAnyRole()) { form.error(new ParamResourceModel("emptyRoles", getPage()).getString()); } } } protected void updateModels() { rootChoice.updateModel(); layerChoice.updateModel(); accessModeChoice.updateModel(); rolesFormComponent.updateModel(); } }