View Javadoc
1   /**
2    * This file Copyright (c) 2013-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.security.app.dialog.field;
35  
36  import info.magnolia.cms.security.Permission;
37  import info.magnolia.i18nsystem.SimpleTranslator;
38  import info.magnolia.jcr.RuntimeRepositoryException;
39  import info.magnolia.objectfactory.ComponentProvider;
40  import info.magnolia.objectfactory.Components;
41  import info.magnolia.security.app.dialog.field.validator.WorkspaceAccessControlValidator;
42  import info.magnolia.ui.api.app.ChooseDialogCallback;
43  import info.magnolia.ui.api.context.UiContext;
44  import info.magnolia.ui.api.i18n.I18NAuthoringSupport;
45  import info.magnolia.ui.contentapp.choosedialog.ChooseDialogComponentProviderUtil;
46  import info.magnolia.ui.contentapp.field.WorkbenchFieldDefinition;
47  import info.magnolia.ui.dialog.choosedialog.ChooseDialogPresenter;
48  import info.magnolia.ui.dialog.choosedialog.ChooseDialogView;
49  import info.magnolia.ui.dialog.definition.ConfiguredChooseDialogDefinition;
50  import info.magnolia.ui.vaadin.integration.contentconnector.ConfiguredJcrContentConnectorDefinition;
51  import info.magnolia.ui.vaadin.integration.contentconnector.ConfiguredNodeTypeDefinition;
52  import info.magnolia.ui.vaadin.integration.contentconnector.NodeTypeDefinition;
53  import info.magnolia.ui.vaadin.integration.jcr.AbstractJcrNodeAdapter;
54  import info.magnolia.ui.vaadin.integration.jcr.JcrItemId;
55  import info.magnolia.ui.vaadin.integration.jcr.JcrItemUtil;
56  import info.magnolia.ui.vaadin.integration.jcr.JcrNodeAdapter;
57  import info.magnolia.ui.vaadin.integration.jcr.ModelConstants;
58  import info.magnolia.ui.workbench.column.definition.ColumnDefinition;
59  import info.magnolia.ui.workbench.column.definition.PropertyColumnDefinition;
60  import info.magnolia.ui.workbench.definition.ConfiguredWorkbenchDefinition;
61  import info.magnolia.ui.workbench.definition.ContentPresenterDefinition;
62  import info.magnolia.ui.workbench.definition.WorkbenchDefinition;
63  import info.magnolia.ui.workbench.tree.TreePresenterDefinition;
64  
65  import java.util.ArrayList;
66  import java.util.List;
67  import java.util.Map;
68  
69  import javax.inject.Inject;
70  import javax.jcr.Node;
71  import javax.jcr.RepositoryException;
72  
73  import org.apache.commons.lang3.StringUtils;
74  import org.apache.jackrabbit.JcrConstants;
75  import org.slf4j.Logger;
76  import org.slf4j.LoggerFactory;
77  
78  import com.google.common.collect.ImmutableMap;
79  import com.vaadin.ui.AbstractOrderedLayout;
80  import com.vaadin.ui.Button;
81  import com.vaadin.ui.Component;
82  import com.vaadin.v7.data.Item;
83  import com.vaadin.v7.data.Property;
84  import com.vaadin.v7.data.util.ObjectProperty;
85  import com.vaadin.v7.ui.Field;
86  import com.vaadin.v7.ui.HorizontalLayout;
87  import com.vaadin.v7.ui.Label;
88  import com.vaadin.v7.ui.NativeSelect;
89  import com.vaadin.v7.ui.TextField;
90  
91  /**
92   * Field factory for workspace ACL fields; unlike other field factories, it does not read ACLs straight from the JCR adapter.
93   *
94   * <p>First, reading and saving entries from/to the role node is delegated to a {@link WorkspaceAccessControlList}.
95   * This typed ACL is then carried over as a property of the dialog item to the save action,
96   * where it gets removed from the item, not to interfere with the JCR adapter.
97   *
98   * @see WorkspaceAccessFieldDefinition
99   * @see info.magnolia.security.app.dialog.action.SaveRoleDialogAction
100  */
101 public class WorkspaceAccessFieldFactory extends AbstractAccessFieldFactory<WorkspaceAccessFieldDefinition, AccessControlList> {
102 
103     private static final Logger log = LoggerFactory.getLogger(WorkspaceAccessFieldFactory.class);
104 
105     /**
106      * @deprecated since 5.4.8, not used anymore now that fields operate over ACEs directly.
107      */
108     @Deprecated
109     public static final String INTERMEDIARY_FORMAT_PROPERTY_NAME = "__intermediary_format";
110 
111     /**
112      * @deprecated since 5.4.8, constant has been relocated to {@link WorkspaceAccessControlList#ACCESS_TYPE_PROPERTY_NAME}
113      */
114     @Deprecated
115     public static final String ACCESS_TYPE_PROPERTY_NAME = WorkspaceAccessControlList.ACCESS_TYPE_PROPERTY_NAME;
116 
117     private final UiContext uiContext;
118     private final SimpleTranslator i18n;
119     private final ComponentProvider componentProvider;
120 
121     private final String workspace;
122     private final String aclName;
123 
124     @Inject
125     public WorkspaceAccessFieldFactory(WorkspaceAccessFieldDefinition definition, Item relatedFieldItem, UiContext uiContext, I18NAuthoringSupport i18nAuthoringSupport, ChooseDialogPresenter workbenchChooseDialogPresenter, SimpleTranslator i18n, ComponentProvider componentProvider) {
126         super(definition, relatedFieldItem, uiContext, i18nAuthoringSupport);
127         this.uiContext = uiContext;
128         this.i18n = i18n;
129         this.componentProvider = componentProvider;
130 
131         workspace = definition.getWorkspace();
132         aclName = "acl_" + workspace;
133     }
134 
135     /**
136      * @deprecated since 5.4.7 - use {@link #WorkspaceAccessFieldFactory(WorkspaceAccessFieldDefinition, Item, UiContext, I18NAuthoringSupport, ChooseDialogPresenter, SimpleTranslator, ComponentProvider)} instead.
137      */
138     @Deprecated
139     public WorkspaceAccessFieldFactory(WorkspaceAccessFieldDefinition definition, Item relatedFieldItem, UiContext uiContext, ChooseDialogPresenter workbenchChooseDialogPresenter, SimpleTranslator i18n, ComponentProvider componentProvider) {
140         this(definition, relatedFieldItem, uiContext, componentProvider.getComponent(I18NAuthoringSupport.class), workbenchChooseDialogPresenter, i18n, componentProvider);
141     }
142 
143     /**
144      * @deprecated since 5.3.1. {@link ComponentProvider} has to be injected in order to create the choose-dialog specific component provider, with proper bindings for e.g. {@link info.magnolia.ui.vaadin.integration.contentconnector.ContentConnector} or {@link info.magnolia.ui.imageprovider.ImageProvider}.
145      */
146     @Deprecated
147     public WorkspaceAccessFieldFactory(WorkspaceAccessFieldDefinition definition, Item relatedFieldItem, UiContext uiContext, ChooseDialogPresenter workbenchChooseDialogPresenter, SimpleTranslator i18n) {
148         this(definition, relatedFieldItem, uiContext, Components.getComponent(I18NAuthoringSupport.class), workbenchChooseDialogPresenter, i18n, Components.getComponentProvider());
149     }
150 
151     @Override
152     protected Field<AccessControlList> createFieldComponent() {
153         final Map<Long, String> permissionItems = ImmutableMap.of(
154                 Permission.ALL, i18n.translate("security.workspace.field.readWrite"),
155                 Permission.READ, i18n.translate("security.workspace.field.readOnly"),
156                 Permission.NONE, i18n.translate("security.workspace.field.denyAccess"));
157 
158         final Map<Long, String> accessTypeItems = ImmutableMap.of(
159                 WorkspaceAccessControlList.ACCESS_TYPE_NODE, i18n.translate("security.workspace.field.selected"),
160                 WorkspaceAccessControlList.ACCESS_TYPE_CHILDREN, i18n.translate("security.workspace.field.subnodes"),
161                 WorkspaceAccessControlList.ACCESS_TYPE_NODE_AND_CHILDREN, i18n.translate("security.workspace.field.selectedSubnodes"));
162 
163         final String chooseCaption = i18n.translate("security.workspace.field.choose");
164 
165         AccessControlListFieldAccessControlListField.html#AccessControlListField">AccessControlListField aclField = new AccessControlListField(permissionItems, () -> new WorkspaceAccessControlList.Entry(Permission.ALL, WorkspaceAccessControlList.ACCESS_TYPE_NODE_AND_CHILDREN, ""));
166 
167         final AccessControlField.PathChooserHandler pathChooserHandler = pathProperty -> openChooseDialog(pathProperty.getValue(), new ChooseDialogCallback() {
168             @Override
169             public void onItemChosen(String actionName, Object value) {
170                 try {
171                     String newPath = value instanceof JcrItemId ? JcrItemUtil.getJcrItem((JcrItemId) value).getPath() : "/";
172                     pathProperty.setValue(newPath);
173                 } catch (RepositoryException e) {
174                     log.error("Failed to read chosen node", e);
175                 }
176             }
177 
178             @Override
179             public void onCancel() {
180             }
181         });
182 
183         aclField.setEntryFieldFactory(entry -> {
184             AccessControlFieldd/AccessControlField.html#AccessControlField">AccessControlField entryField = new AccessControlField(permissionItems, accessTypeItems);
185             entryField.setPropertyDataSource(new ObjectProperty<>(entry));
186             entryField.setPathChooserHandler(pathChooserHandler);
187             entryField.setChooseButtonCaption(chooseCaption);
188             entryField.addValidator(new WorkspaceAccessControlValidator(definition.getWorkspace(), i18n.translate("security-app.role.acls.errorMessage")));
189             return entryField;
190         });
191         aclField.setAddButtonCaption(i18n.translate("security.workspace.field.addButton"));
192         aclField.setRemoveButtonCaption(i18n.translate("security.workspace.field.delete"));
193         aclField.setEmptyPlaceholderCaption(i18n.translate("security.workspace.field.noAccess"));
194 
195         return aclField;
196     }
197 
198     @Override
199     protected Property<AccessControlList> initializeProperty() {
200         // prepare backing WorkspaceAccessControlList bean
201         JcrNodeAdapter roleItem = (JcrNodeAdapter) item;
202         AccessControlList<WorkspaceAccessControlList.Entry> acl = new WorkspaceAccessControlList();
203         roleItem.addItemProperty(aclName, new ObjectProperty<>(acl));
204 
205         if (!(roleItem.isNew())) {
206             try {
207                 Node roleNode = roleItem.getJcrItem();
208                 if (roleNode.hasNode(aclName)) {
209                     final Node aclNode = roleNode.getNode(aclName);
210                     acl.readEntries(aclNode);
211 
212                 }
213             } catch (RepositoryException e) {
214                 throw new RuntimeRepositoryException(e);
215             }
216         }
217 
218         return new ObjectProperty<>(acl);
219     }
220 
221     /**
222      * @deprecated since 5.4.8 - won't use anymore.
223      */
224     @Deprecated
225     protected Component createRuleRow(final AbstractOrderedLayout parentContainer, final AbstractJcrNodeAdapter ruleItem, final Label emptyLabel) {
226 
227         final HorizontalLayout ruleLayout = new HorizontalLayout();
228         ruleLayout.setSpacing(true);
229         ruleLayout.setWidth("100%");
230 
231         NativeSelect accessRights = new NativeSelect();
232         accessRights.setNullSelectionAllowed(false);
233         accessRights.setInvalidAllowed(false);
234         accessRights.setNewItemsAllowed(false);
235         accessRights.addItem(Permission.ALL);
236         accessRights.setItemCaption(Permission.ALL, i18n.translate("security.workspace.field.readWrite"));
237         accessRights.addItem(Permission.READ);
238         accessRights.setItemCaption(Permission.READ, i18n.translate("security.workspace.field.readOnly"));
239         accessRights.addItem(Permission.NONE);
240         accessRights.setItemCaption(Permission.NONE, i18n.translate("security.workspace.field.denyAccess"));
241         accessRights.setPropertyDataSource(ruleItem.getItemProperty(AccessControlList.PERMISSIONS_PROPERTY_NAME));
242         ruleLayout.addComponent(accessRights);
243 
244         NativeSelect accessType = new NativeSelect();
245         accessType.setNullSelectionAllowed(false);
246         accessType.setInvalidAllowed(false);
247         accessType.setNewItemsAllowed(false);
248         accessType.setWidth("150px");
249         accessType.addItem(WorkspaceAccessControlList.ACCESS_TYPE_NODE);
250         accessType.setItemCaption(WorkspaceAccessControlList.ACCESS_TYPE_NODE, i18n.translate("security.workspace.field.selected"));
251         accessType.addItem(WorkspaceAccessControlList.ACCESS_TYPE_CHILDREN);
252         accessType.setItemCaption(WorkspaceAccessControlList.ACCESS_TYPE_CHILDREN, i18n.translate("security.workspace.field.subnodes"));
253         accessType.addItem(WorkspaceAccessControlList.ACCESS_TYPE_NODE_AND_CHILDREN);
254         accessType.setItemCaption(WorkspaceAccessControlList.ACCESS_TYPE_NODE_AND_CHILDREN, i18n.translate("security.workspace.field.selectedSubnodes"));
255         Property accessTypeProperty = ruleItem.getItemProperty(ACCESS_TYPE_PROPERTY_NAME);
256         accessType.setPropertyDataSource(accessTypeProperty);
257         ruleLayout.addComponent(accessType);
258 
259         final TextField path = new TextField();
260         path.setWidth("100%");
261         path.setPropertyDataSource(ruleItem.getItemProperty(AccessControlList.PATH_PROPERTY_NAME));
262         ruleLayout.addComponent(path);
263         ruleLayout.setExpandRatio(path, 1.0f);
264 
265         Button chooseButton = new Button(i18n.translate("security.workspace.field.choose"));
266         chooseButton.addClickListener(new Button.ClickListener() {
267 
268             @Override
269             public void buttonClick(Button.ClickEvent event) {
270                 openChooseDialog(path);
271             }
272         });
273         ruleLayout.addComponent(chooseButton);
274 
275         Button deleteButton = new Button();
276         deleteButton.setHtmlContentAllowed(true);
277         deleteButton.setCaption("<span class=\"" + "icon-trash" + "\"></span>");
278         deleteButton.addStyleName("inline");
279         deleteButton.setDescription(i18n.translate("security.workspace.field.delete"));
280         deleteButton.addClickListener(new Button.ClickListener() {
281 
282             @Override
283             public void buttonClick(Button.ClickEvent event) {
284                 parentContainer.removeComponent(ruleLayout);
285                 ruleItem.getParent().removeChild(ruleItem);
286                 if (parentContainer.getComponentCount() == 1) {
287                     parentContainer.addComponent(emptyLabel, 0);
288                 }
289             }
290         });
291         ruleLayout.addComponent(deleteButton);
292 
293         return ruleLayout;
294     }
295 
296     /**
297      * @deprecated since 5.4.8 - won't use anymore.
298      */
299     @Deprecated
300     protected void openChooseDialog(final TextField textField) {
301         openChooseDialog(textField.getValue(), new ChooseDialogCallback() {
302             @Override
303             public void onItemChosen(String actionName, Object value) {
304                 try {
305                     if (value instanceof JcrItemId) {
306                         JcrItemId jcrItemId = (JcrItemId) value;
307                         textField.setValue(JcrItemUtil.getJcrItem(jcrItemId).getPath());
308                     } else {
309                         textField.setValue("/");
310                     }
311                 } catch (RepositoryException e) {
312                     log.error("Failed to read chosen node", e);
313                 }
314             }
315 
316             @Override
317             public void onCancel() {
318             }
319         });
320     }
321 
322     protected void openChooseDialog(String initialItemId, ChooseDialogCallback callback) {
323         final ConfiguredChooseDialogDefinition def = new ConfiguredChooseDialogDefinition();
324         final ConfiguredJcrContentConnectorDefinition contentConnectorDefinition = new ConfiguredJcrContentConnectorDefinition();
325         contentConnectorDefinition.setWorkspace(getFieldDefinition().getWorkspace());
326         contentConnectorDefinition.setRootPath("/");
327         contentConnectorDefinition.setDefaultOrder(ModelConstants.JCR_NAME);
328         // node types
329         contentConnectorDefinition.setNodeTypes(resolveNodeTypes());
330         def.setContentConnector(contentConnectorDefinition);
331 
332         final WorkbenchDefinition wbDef = resolveWorkbenchDefinition();
333         final WorkbenchFieldDefinition fieldDef = new WorkbenchFieldDefinition();
334         fieldDef.setWorkbench(wbDef);
335         def.setField(fieldDef);
336 
337         // create chooseDialogComponentProvider and get new instance of presenter from there
338         ComponentProvider chooseDialogComponentProvider = ChooseDialogComponentProviderUtil.createChooseDialogComponentProvider(def, componentProvider);
339         ChooseDialogPresenter workbenchChooseDialogPresenter = chooseDialogComponentProvider.newInstance(def.getPresenterClass(), chooseDialogComponentProvider);
340 
341         ChooseDialogView chooseDialogView = workbenchChooseDialogPresenter.start(callback, def, uiContext, initialItemId);
342         chooseDialogView.setCaption(StringUtils.capitalize(getFieldDefinition().getWorkspace()));
343     }
344 
345     protected WorkbenchDefinition resolveWorkbenchDefinition() {
346 
347         if (getFieldDefinition().getWorkbench() != null) {
348             return getFieldDefinition().getWorkbench();
349         }
350 
351         ConfiguredWorkbenchDefinition workbenchDefinition = new ConfiguredWorkbenchDefinition();
352         workbenchDefinition.setDialogWorkbench(true);
353         workbenchDefinition.setEditable(false);
354 
355         // content views
356         ArrayList<ContentPresenterDefinition> contentViews = new ArrayList<>();
357         TreePresenterDefinition treeView = new TreePresenterDefinition();
358         ArrayList<ColumnDefinition> columns = new ArrayList<>();
359         PropertyColumnDefinition column = new PropertyColumnDefinition();
360         column.setEditable(false);
361         column.setDisplayInChooseDialog(true);
362         column.setLabel(i18n.translate("security.workspace.field.nodeName"));
363         column.setPropertyName(ModelConstants.JCR_NAME);
364         column.setName(ModelConstants.JCR_NAME);
365         columns.add(column);
366         treeView.setColumns(columns);
367         contentViews.add(treeView);
368         workbenchDefinition.setContentViews(contentViews);
369 
370         return workbenchDefinition;
371     }
372 
373     private List<NodeTypeDefinition> resolveNodeTypes() {
374 
375         if (getFieldDefinition().getNodeTypes() != null) {
376             return getFieldDefinition().getNodeTypes();
377         }
378 
379         ArrayList<NodeTypeDefinition> nodeTypes = new ArrayList<>();
380         ConfiguredNodeTypeDefinition nodeType = new ConfiguredNodeTypeDefinition();
381         nodeType.setName(JcrConstants.NT_BASE);
382         nodeType.setIcon("icon-folder");
383         nodeTypes.add(nodeType);
384 
385         return nodeTypes;
386     }
387 }