View Javadoc
1   /**
2    * This file Copyright (c) 2013-2016 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.AccessControlListField.NewEntryHandler;
42  import info.magnolia.security.app.dialog.field.validator.WorkspaceAccessControlValidator;
43  import info.magnolia.ui.api.app.ChooseDialogCallback;
44  import info.magnolia.ui.api.context.UiContext;
45  import info.magnolia.ui.api.i18n.I18NAuthoringSupport;
46  import info.magnolia.ui.contentapp.choosedialog.ChooseDialogComponentProviderUtil;
47  import info.magnolia.ui.contentapp.field.WorkbenchFieldDefinition;
48  import info.magnolia.ui.dialog.choosedialog.ChooseDialogPresenter;
49  import info.magnolia.ui.dialog.choosedialog.ChooseDialogView;
50  import info.magnolia.ui.dialog.definition.ConfiguredChooseDialogDefinition;
51  import info.magnolia.ui.vaadin.integration.contentconnector.ConfiguredJcrContentConnectorDefinition;
52  import info.magnolia.ui.vaadin.integration.contentconnector.ConfiguredNodeTypeDefinition;
53  import info.magnolia.ui.vaadin.integration.contentconnector.NodeTypeDefinition;
54  import info.magnolia.ui.vaadin.integration.jcr.AbstractJcrNodeAdapter;
55  import info.magnolia.ui.vaadin.integration.jcr.JcrItemId;
56  import info.magnolia.ui.vaadin.integration.jcr.JcrItemUtil;
57  import info.magnolia.ui.vaadin.integration.jcr.JcrNodeAdapter;
58  import info.magnolia.ui.vaadin.integration.jcr.ModelConstants;
59  import info.magnolia.ui.workbench.column.definition.ColumnDefinition;
60  import info.magnolia.ui.workbench.column.definition.PropertyColumnDefinition;
61  import info.magnolia.ui.workbench.definition.ConfiguredWorkbenchDefinition;
62  import info.magnolia.ui.workbench.definition.ContentPresenterDefinition;
63  import info.magnolia.ui.workbench.definition.WorkbenchDefinition;
64  import info.magnolia.ui.workbench.tree.TreePresenterDefinition;
65  
66  import java.util.ArrayList;
67  import java.util.List;
68  import java.util.Map;
69  
70  import javax.inject.Inject;
71  import javax.jcr.Node;
72  import javax.jcr.RepositoryException;
73  
74  import org.apache.commons.lang3.StringUtils;
75  import org.apache.jackrabbit.JcrConstants;
76  import org.slf4j.Logger;
77  import org.slf4j.LoggerFactory;
78  
79  import com.google.common.collect.ImmutableMap;
80  import com.vaadin.data.Item;
81  import com.vaadin.data.Property;
82  import com.vaadin.data.util.ObjectProperty;
83  import com.vaadin.ui.AbstractOrderedLayout;
84  import com.vaadin.ui.Button;
85  import com.vaadin.ui.Component;
86  import com.vaadin.ui.Field;
87  import com.vaadin.ui.HorizontalLayout;
88  import com.vaadin.ui.Label;
89  import com.vaadin.ui.NativeSelect;
90  import com.vaadin.ui.TextField;
91  
92  /**
93   * Field factory for workspace ACL fields; unlike other field factories, it does not read ACLs straight from the JCR adapter.
94   *
95   * <p>First, reading and saving entries from/to the role node is delegated to a {@link WorkspaceAccessControlList}.
96   * This typed ACL is then carried over as a property of the dialog item to the save action,
97   * where it gets removed from the item, not to interfere with the JCR adapter.
98   *
99   * @see WorkspaceAccessFieldDefinition
100  * @see info.magnolia.security.app.dialog.action.SaveRoleDialogAction
101  */
102 public class WorkspaceAccessFieldFactory extends AbstractAccessFieldFactory<WorkspaceAccessFieldDefinition, AccessControlList> {
103 
104     private static final Logger log = LoggerFactory.getLogger(WorkspaceAccessFieldFactory.class);
105 
106     /**
107      * @deprecated since 5.4.8, not used anymore now that fields operate over ACEs directly.
108      */
109     @Deprecated
110     public static final String INTERMEDIARY_FORMAT_PROPERTY_NAME = "__intermediary_format";
111 
112     /**
113      * @deprecated since 5.4.8, constant has been relocated to {@link WorkspaceAccessControlList#ACCESS_TYPE_PROPERTY_NAME}
114      */
115     @Deprecated
116     public static final String ACCESS_TYPE_PROPERTY_NAME = WorkspaceAccessControlList.ACCESS_TYPE_PROPERTY_NAME;
117 
118     private final UiContext uiContext;
119     private final SimpleTranslator i18n;
120     private final ComponentProvider componentProvider;
121 
122     private final String workspace;
123     private final String aclName;
124 
125     @Inject
126     public WorkspaceAccessFieldFactory(WorkspaceAccessFieldDefinition definition, Item relatedFieldItem, UiContext uiContext, I18NAuthoringSupport i18nAuthoringSupport, ChooseDialogPresenter workbenchChooseDialogPresenter, SimpleTranslator i18n, ComponentProvider componentProvider) {
127         super(definition, relatedFieldItem, uiContext, i18nAuthoringSupport);
128         this.uiContext = uiContext;
129         this.i18n = i18n;
130         this.componentProvider = componentProvider;
131 
132         workspace = definition.getWorkspace();
133         aclName = "acl_" + workspace;
134     }
135 
136     /**
137      * @deprecated since 5.4.7 - use {@link #WorkspaceAccessFieldFactory(WorkspaceAccessFieldDefinition, Item, UiContext, I18NAuthoringSupport, ChooseDialogPresenter, SimpleTranslator, ComponentProvider)} instead.
138      */
139     @Deprecated
140     public WorkspaceAccessFieldFactory(WorkspaceAccessFieldDefinition definition, Item relatedFieldItem, UiContext uiContext, ChooseDialogPresenter workbenchChooseDialogPresenter, SimpleTranslator i18n, ComponentProvider componentProvider) {
141         this(definition, relatedFieldItem, uiContext, componentProvider.getComponent(I18NAuthoringSupport.class), workbenchChooseDialogPresenter, i18n, componentProvider);
142     }
143 
144     /**
145      * @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}.
146      */
147     @Deprecated
148     public WorkspaceAccessFieldFactory(WorkspaceAccessFieldDefinition definition, Item relatedFieldItem, UiContext uiContext, ChooseDialogPresenter workbenchChooseDialogPresenter, SimpleTranslator i18n) {
149         this(definition, relatedFieldItem, uiContext, Components.getComponent(I18NAuthoringSupport.class), workbenchChooseDialogPresenter, i18n, Components.getComponentProvider());
150     }
151 
152     @Override
153     protected Field<AccessControlList> createFieldComponent() {
154         final Map<Long, String> permissionItems = ImmutableMap.of(
155                 Permission.ALL, i18n.translate("security.workspace.field.readWrite"),
156                 Permission.READ, i18n.translate("security.workspace.field.readOnly"),
157                 Permission.NONE, i18n.translate("security.workspace.field.denyAccess"));
158 
159         final Map<Long, String> accessTypeItems = ImmutableMap.of(
160                 WorkspaceAccessControlList.ACCESS_TYPE_NODE, i18n.translate("security.workspace.field.selected"),
161                 WorkspaceAccessControlList.ACCESS_TYPE_CHILDREN, i18n.translate("security.workspace.field.subnodes"),
162                 WorkspaceAccessControlList.ACCESS_TYPE_NODE_AND_CHILDREN, i18n.translate("security.workspace.field.selectedSubnodes"));
163 
164         final String chooseCaption = i18n.translate("security.workspace.field.choose");
165 
166         AccessControlListField aclField = new AccessControlListField(permissionItems, new NewEntryHandler() {
167             @Override
168             public AccessControlList.Entry createEntry() {
169                 return new WorkspaceAccessControlList.Entry(Permission.ALL, WorkspaceAccessControlList.ACCESS_TYPE_NODE_AND_CHILDREN, "");
170             }
171         });
172 
173         final AccessControlField.PathChooserHandler pathChooserHandler = new AccessControlField.PathChooserHandler() {
174             @Override
175             public void openChooser(final Property<String> pathProperty) {
176                 openChooseDialog(pathProperty.getValue(), new ChooseDialogCallback() {
177                     @Override
178                     public void onItemChosen(String actionName, Object value) {
179                         try {
180                             String newPath = value instanceof JcrItemId ? JcrItemUtil.getJcrItem((JcrItemId) value).getPath() : "/";
181                             pathProperty.setValue(newPath);
182                         } catch (RepositoryException e) {
183                             log.error("Failed to read chosen node", e);
184                         }
185                     }
186 
187                     @Override
188                     public void onCancel() {
189                     }
190                 });
191             }
192         };
193 
194         aclField.setEntryFieldFactory(new AccessControlListField.EntryFieldFactory() {
195             @Override
196             public Field<AccessControlList.Entry> createField(AccessControlList.Entry entry) {
197                 AccessControlField entryField = new AccessControlField(permissionItems, accessTypeItems);
198                 entryField.setPropertyDataSource(new ObjectProperty<>(entry));
199                 entryField.setPathChooserHandler(pathChooserHandler);
200                 entryField.setChooseButtonCaption(chooseCaption);
201                 entryField.addValidator(new WorkspaceAccessControlValidator(definition.getWorkspace(), i18n.translate("security-app.role.acls.errorMessage")));
202                 return entryField;
203             }
204         });
205         aclField.setAddButtonCaption(i18n.translate("security.workspace.field.addButton"));
206         aclField.setRemoveButtonCaption(i18n.translate("security.workspace.field.delete"));
207         aclField.setEmptyPlaceholderCaption(i18n.translate("security.workspace.field.noAccess"));
208 
209         return aclField;
210     }
211 
212     @Override
213     protected Property<AccessControlList> initializeProperty() {
214         // prepare backing WorkspaceAccessControlList bean
215         JcrNodeAdapter roleItem = (JcrNodeAdapter) item;
216         AccessControlList<WorkspaceAccessControlList.Entry> acl = new WorkspaceAccessControlList();
217         roleItem.addItemProperty(aclName, new ObjectProperty<>(acl));
218 
219         if (!(roleItem.isNew())) {
220             try {
221                 Node roleNode = roleItem.getJcrItem();
222                 if (roleNode.hasNode(aclName)) {
223                     final Node aclNode = roleNode.getNode(aclName);
224                     acl.readEntries(aclNode);
225 
226                 }
227             } catch (RepositoryException e) {
228                 throw new RuntimeRepositoryException(e);
229             }
230         }
231 
232         return new ObjectProperty<AccessControlList>(acl);
233     }
234 
235     /**
236      * @deprecated since 5.4.8 - won't use anymore.
237      */
238     @Deprecated
239     protected Component createRuleRow(final AbstractOrderedLayout parentContainer, final AbstractJcrNodeAdapter ruleItem, final Label emptyLabel) {
240 
241         final HorizontalLayout ruleLayout = new HorizontalLayout();
242         ruleLayout.setSpacing(true);
243         ruleLayout.setWidth("100%");
244 
245         NativeSelect accessRights = new NativeSelect();
246         accessRights.setNullSelectionAllowed(false);
247         accessRights.setImmediate(true);
248         accessRights.setInvalidAllowed(false);
249         accessRights.setNewItemsAllowed(false);
250         accessRights.addItem(Permission.ALL);
251         accessRights.setItemCaption(Permission.ALL, i18n.translate("security.workspace.field.readWrite"));
252         accessRights.addItem(Permission.READ);
253         accessRights.setItemCaption(Permission.READ, i18n.translate("security.workspace.field.readOnly"));
254         accessRights.addItem(Permission.NONE);
255         accessRights.setItemCaption(Permission.NONE, i18n.translate("security.workspace.field.denyAccess"));
256         accessRights.setPropertyDataSource(ruleItem.getItemProperty(AccessControlList.PERMISSIONS_PROPERTY_NAME));
257         ruleLayout.addComponent(accessRights);
258 
259         NativeSelect accessType = new NativeSelect();
260         accessType.setNullSelectionAllowed(false);
261         accessType.setImmediate(true);
262         accessType.setInvalidAllowed(false);
263         accessType.setNewItemsAllowed(false);
264         accessType.setWidth("150px");
265         accessType.addItem(WorkspaceAccessControlList.ACCESS_TYPE_NODE);
266         accessType.setItemCaption(WorkspaceAccessControlList.ACCESS_TYPE_NODE, i18n.translate("security.workspace.field.selected"));
267         accessType.addItem(WorkspaceAccessControlList.ACCESS_TYPE_CHILDREN);
268         accessType.setItemCaption(WorkspaceAccessControlList.ACCESS_TYPE_CHILDREN, i18n.translate("security.workspace.field.subnodes"));
269         accessType.addItem(WorkspaceAccessControlList.ACCESS_TYPE_NODE_AND_CHILDREN);
270         accessType.setItemCaption(WorkspaceAccessControlList.ACCESS_TYPE_NODE_AND_CHILDREN, i18n.translate("security.workspace.field.selectedSubnodes"));
271         Property accessTypeProperty = ruleItem.getItemProperty(ACCESS_TYPE_PROPERTY_NAME);
272         accessType.setPropertyDataSource(accessTypeProperty);
273         ruleLayout.addComponent(accessType);
274 
275         final TextField path = new TextField();
276         path.setWidth("100%");
277         path.setPropertyDataSource(ruleItem.getItemProperty(AccessControlList.PATH_PROPERTY_NAME));
278         ruleLayout.addComponent(path);
279         ruleLayout.setExpandRatio(path, 1.0f);
280 
281         Button chooseButton = new Button(i18n.translate("security.workspace.field.choose"));
282         chooseButton.addClickListener(new Button.ClickListener() {
283 
284             @Override
285             public void buttonClick(Button.ClickEvent event) {
286                 openChooseDialog(path);
287             }
288         });
289         ruleLayout.addComponent(chooseButton);
290 
291         Button deleteButton = new Button();
292         deleteButton.setHtmlContentAllowed(true);
293         deleteButton.setCaption("<span class=\"" + "icon-trash" + "\"></span>");
294         deleteButton.addStyleName("inline");
295         deleteButton.setDescription(i18n.translate("security.workspace.field.delete"));
296         deleteButton.addClickListener(new Button.ClickListener() {
297 
298             @Override
299             public void buttonClick(Button.ClickEvent event) {
300                 parentContainer.removeComponent(ruleLayout);
301                 ruleItem.getParent().removeChild(ruleItem);
302                 if (parentContainer.getComponentCount() == 1) {
303                     parentContainer.addComponent(emptyLabel, 0);
304                 }
305             }
306         });
307         ruleLayout.addComponent(deleteButton);
308 
309         return ruleLayout;
310     }
311 
312     /**
313      * @deprecated since 5.4.8 - won't use anymore.
314      */
315     @Deprecated
316     protected void openChooseDialog(final TextField textField) {
317         openChooseDialog(textField.getValue(), new ChooseDialogCallback() {
318             @Override
319             public void onItemChosen(String actionName, Object value) {
320                 try {
321                     if (value instanceof JcrItemId) {
322                         JcrItemId jcrItemId = (JcrItemId) value;
323                         textField.setValue(JcrItemUtil.getJcrItem(jcrItemId).getPath());
324                     } else {
325                         textField.setValue("/");
326                     }
327                 } catch (RepositoryException e) {
328                     log.error("Failed to read chosen node", e);
329                 }
330             }
331 
332             @Override
333             public void onCancel() {
334             }
335         });
336     }
337 
338     protected void openChooseDialog(String initialItemId, ChooseDialogCallback callback) {
339         final ConfiguredChooseDialogDefinition def = new ConfiguredChooseDialogDefinition();
340         final ConfiguredJcrContentConnectorDefinition contentConnectorDefinition = new ConfiguredJcrContentConnectorDefinition();
341         contentConnectorDefinition.setWorkspace(getFieldDefinition().getWorkspace());
342         contentConnectorDefinition.setRootPath("/");
343         contentConnectorDefinition.setDefaultOrder(ModelConstants.JCR_NAME);
344         // node types
345         contentConnectorDefinition.setNodeTypes(resolveNodeTypes());
346         def.setContentConnector(contentConnectorDefinition);
347 
348         final WorkbenchDefinition wbDef = resolveWorkbenchDefinition();
349         final WorkbenchFieldDefinition fieldDef = new WorkbenchFieldDefinition();
350         fieldDef.setWorkbench(wbDef);
351         def.setField(fieldDef);
352 
353         // create chooseDialogComponentProvider and get new instance of presenter from there
354         ComponentProvider chooseDialogComponentProvider = ChooseDialogComponentProviderUtil.createChooseDialogComponentProvider(def, componentProvider);
355         ChooseDialogPresenter workbenchChooseDialogPresenter = chooseDialogComponentProvider.newInstance(def.getPresenterClass(), chooseDialogComponentProvider);
356 
357         ChooseDialogView chooseDialogView = workbenchChooseDialogPresenter.start(callback, def, uiContext, initialItemId);
358         chooseDialogView.setCaption(StringUtils.capitalize(getFieldDefinition().getWorkspace()));
359     }
360 
361     protected WorkbenchDefinition resolveWorkbenchDefinition() {
362 
363         if (getFieldDefinition().getWorkbench() != null) {
364             return getFieldDefinition().getWorkbench();
365         }
366 
367         ConfiguredWorkbenchDefinition workbenchDefinition = new ConfiguredWorkbenchDefinition();
368         workbenchDefinition.setDialogWorkbench(true);
369         workbenchDefinition.setEditable(false);
370 
371         // content views
372         ArrayList<ContentPresenterDefinition> contentViews = new ArrayList<>();
373         TreePresenterDefinition treeView = new TreePresenterDefinition();
374         ArrayList<ColumnDefinition> columns = new ArrayList<>();
375         PropertyColumnDefinition column = new PropertyColumnDefinition();
376         column.setEditable(false);
377         column.setDisplayInChooseDialog(true);
378         column.setLabel(i18n.translate("security.workspace.field.nodeName"));
379         column.setPropertyName(ModelConstants.JCR_NAME);
380         column.setName(ModelConstants.JCR_NAME);
381         columns.add(column);
382         treeView.setColumns(columns);
383         contentViews.add(treeView);
384         workbenchDefinition.setContentViews(contentViews);
385 
386         return workbenchDefinition;
387     }
388 
389     private List<NodeTypeDefinition> resolveNodeTypes() {
390 
391         if (getFieldDefinition().getNodeTypes() != null) {
392             return getFieldDefinition().getNodeTypes();
393         }
394 
395         ArrayList<NodeTypeDefinition> nodeTypes = new ArrayList<>();
396         ConfiguredNodeTypeDefinition nodeType = new ConfiguredNodeTypeDefinition();
397         nodeType.setName(JcrConstants.NT_BASE);
398         nodeType.setIcon("icon-folder");
399         nodeTypes.add(nodeType);
400 
401         return nodeTypes;
402     }
403 }