Skip to content

Commit d3dcdac

Browse files
authored
XWIKI-22656: Add a UI for setting required rights on a document (#4158)
* Add an analyzer for analyzing just the content and title of the document. * Add an analyzer for analyzing a whole document including all translations. * Add a REST API to get the analysis results and the available rights. * Add a JavaScript component for a dialog to set required rights. * Add a UI Extension to warn users about missing rights. * Add a UI Extension for the page information to display and set required rights. * Add a dependency to the UI to the flavor. * Reload the information and the warning above the document when the document is saved. * Support updating required rights via the REST API. * Extend the Document script API with methods that take EntityReference instead of String for the class name. * Add a new JavaScript event that signals when required rights have been updated. * Reload the content both in view and edit mode when required rights are updated. * Add page objects. * Add integration tests.
1 parent 277b272 commit d3dcdac

File tree

48 files changed

+4969
-38
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4969
-38
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* See the NOTICE file distributed with this work for additional
3+
* information regarding copyright ownership.
4+
*
5+
* This is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU Lesser General Public License as
7+
* published by the Free Software Foundation; either version 2.1 of
8+
* the License, or (at your option) any later version.
9+
*
10+
* This software is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this software; if not, write to the Free
17+
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18+
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19+
*/
20+
(function () {
21+
'use strict';
22+
const $ = jQuery;
23+
24+
// Reload the content of the CKEditor instances when the document rights are changed to reflect the new rights
25+
// in the executed macros.
26+
$(document).on('xwiki:document:requiredRightsUpdated.ckeditor', function (event, data) {
27+
$.each(CKEDITOR.instances, function(key, editor) {
28+
if (matchesRequiredRightsChangeEvent(editor, event, data)) {
29+
maybeReload(editor);
30+
}
31+
});
32+
});
33+
34+
const matchesRequiredRightsChangeEvent = function (editor, event, data) {
35+
// Check if the syntax change event targets the edited document (the source document).
36+
return editor.config.sourceDocument.documentReference.equals(data.documentReference) &&
37+
// Check if the syntax plugin is enabled for this editor instance.
38+
editor.plugins['xwiki-rights'];
39+
};
40+
41+
const maybeReload = function (editor) {
42+
// Only reload in WYSIWYG mode. In source mode, rights aren't influencing anything.
43+
// TODO: it would be nice if we could mark something in source mode to ensure that on the next switch to
44+
// wysiwyg mode, the content would be reloaded regardless if it has been modified or not.
45+
if (editor.mode === 'wysiwyg') {
46+
editor.execCommand('xwiki-refresh');
47+
}
48+
};
49+
50+
// An empty plugin that can be used to enable / disable the reloading on required rights changes on a particular
51+
// CKEditor instance.
52+
CKEDITOR.plugins.add('xwiki-rights', {
53+
requires: 'xwiki-macro,xwiki-source'
54+
});
55+
})();

xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-webjar/src/main/webjar/config.js

+1
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ CKEDITOR.editorConfig = function(config) {
174174
'xwiki-maximize',
175175
'xwiki-office',
176176
'xwiki-realtime',
177+
'xwiki-rights',
177178
'xwiki-save',
178179
'xwiki-selection',
179180
'xwiki-slash',

xwiki-platform-core/xwiki-platform-edit/xwiki-platform-edit-test/xwiki-platform-edit-test-docker/pom.xml

+7
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@
7575
<version>${project.version}</version>
7676
<type>xar</type>
7777
</dependency>
78+
<!-- Used to test the integration between required rights and the editor. -->
79+
<dependency>
80+
<groupId>org.xwiki.platform</groupId>
81+
<artifactId>xwiki-platform-security-requiredrights-ui</artifactId>
82+
<version>${project.version}</version>
83+
<scope>runtime</scope>
84+
</dependency>
7885
<!-- ================================
7986
Test only dependencies
8087
================================ -->

xwiki-platform-core/xwiki-platform-edit/xwiki-platform-edit-test/xwiki-platform-edit-test-docker/src/test/it/org/xwiki/edit/test/ui/InplaceEditIT.java

+59
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@
3737
import org.xwiki.test.docker.junit5.TestReference;
3838
import org.xwiki.test.docker.junit5.UITest;
3939
import org.xwiki.test.ui.TestUtils;
40+
import org.xwiki.test.ui.po.InformationPane;
41+
import org.xwiki.test.ui.po.RequiredRightsModal;
4042

43+
import static org.hamcrest.MatcherAssert.assertThat;
44+
import static org.hamcrest.Matchers.startsWith;
4145
import static org.junit.jupiter.api.Assertions.assertEquals;
4246
import static org.junit.jupiter.api.Assertions.assertFalse;
4347
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -439,4 +443,59 @@ void selectionRestoreOnSwitchToSource(TestUtils setup, TestReference testReferen
439443

440444
viewPage.cancel();
441445
}
446+
447+
@Test
448+
@Order(8)
449+
void refreshOnRequiredRightsChange(TestUtils setup, TestReference testReference)
450+
{
451+
// Test that updating required rights refreshes the content.
452+
453+
// Grant alice script right on this page to allow using the Velocity macro.
454+
setup.loginAsSuperAdmin();
455+
setup.setRights(testReference, null, "XWiki.alice", "script", true);
456+
setup.loginAndGotoPage("alice", "pa$$word", setup.getURL(testReference));
457+
458+
// Enter in-place edit mode.
459+
InplaceEditablePage viewPage = new InplaceEditablePage().editInplace();
460+
CKEditor ckeditor = new CKEditor("content");
461+
RichTextAreaElement richTextArea = ckeditor.getRichTextArea();
462+
richTextArea.clear();
463+
464+
// Insert the Velocity macro. The macro placeholder should be displayed.
465+
richTextArea.sendKeys(Keys.ENTER, "/velocity");
466+
AutocompleteDropdown qa = new AutocompleteDropdown();
467+
qa.waitForItemSelected("/velocity", "Velocity");
468+
richTextArea.sendKeys(Keys.ENTER);
469+
qa.waitForItemSubmitted();
470+
471+
richTextArea.waitForContentRefresh();
472+
473+
assertEquals("macro:velocity", richTextArea.getText());
474+
475+
InformationPane informationPane = viewPage.openInformationDocExtraPane();
476+
477+
RequiredRightsModal requiredRightsModal = informationPane.openRequiredRightsModal();
478+
requiredRightsModal.setEnforceRequiredRights(true);
479+
requiredRightsModal.clickSave(true);
480+
481+
richTextArea.waitForContentRefresh();
482+
483+
assertThat(richTextArea.getText(), startsWith("Failed to execute the [velocity] macro."));
484+
485+
viewPage.save();
486+
487+
assertTrue(viewPage.hasRequiredRightsWarning(true));
488+
489+
requiredRightsModal = viewPage.openRequiredRightsModal();
490+
requiredRightsModal.setEnforcedRequiredRight("script");
491+
requiredRightsModal.clickSave(true);
492+
493+
richTextArea.waitForContentRefresh();
494+
495+
assertEquals("macro:velocity", richTextArea.getText());
496+
497+
setup.getDriver().waitUntilCondition(driver -> !viewPage.hasRequiredRightsWarning(false));
498+
499+
viewPage.cancel();
500+
}
442501
}

xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/Document.java

+58-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import org.xwiki.model.internal.document.SafeDocumentAuthors;
5555
import org.xwiki.model.reference.DocumentReference;
5656
import org.xwiki.model.reference.DocumentReferenceResolver;
57+
import org.xwiki.model.reference.EntityReference;
5758
import org.xwiki.model.reference.EntityReferenceSerializer;
5859
import org.xwiki.model.reference.ObjectReference;
5960
import org.xwiki.model.reference.PageReference;
@@ -1169,6 +1170,24 @@ public Object newObject(String classname) throws XWikiException
11691170
return getObject(classname, nb);
11701171
}
11711172

1173+
/**
1174+
* Creates a new XObject from the given class reference.
1175+
*
1176+
* @param classReference the reference to the class of the XObject to be created
1177+
* @return the object created
1178+
* @throws XWikiException if an error occurs while creating the XObject
1179+
* @since 17.4.0RC1
1180+
*/
1181+
@Unstable
1182+
public Object newObject(EntityReference classReference) throws XWikiException
1183+
{
1184+
int index = getDoc().createXObject(classReference, getXWikiContext());
1185+
1186+
updateAuthor();
1187+
1188+
return getObject(classReference, index);
1189+
}
1190+
11721191
/**
11731192
* @return true of the document has been loaded from cache
11741193
*/
@@ -1229,6 +1248,21 @@ public Vector<Object> getObjects(String className)
12291248
return getXObjects(objects);
12301249
}
12311250

1251+
/**
1252+
* Retrieves and returns all objects corresponding to the class reference corresponding to the resolution of the
1253+
* given entity reference, or an empty list if there are none.
1254+
*
1255+
* @param classReference the reference that is resolved to an XClass for retrieving the corresponding xobjects
1256+
* @return a list of xobjects corresponding to the given XClass or an empty list.
1257+
* @since 17.4.0RC1
1258+
*/
1259+
@Unstable
1260+
public List<Object> getObjects(EntityReference classReference)
1261+
{
1262+
List<BaseObject> objects = this.getDoc().getXObjects(classReference);
1263+
return getXObjects(objects);
1264+
}
1265+
12321266
/**
12331267
* Get the first object that contains the given fieldname
12341268
*
@@ -1387,6 +1421,29 @@ public Object getObject(String classname, int nb)
13871421
}
13881422
}
13891423

1424+
/**
1425+
* Gets the object matching the given class reference and given object number.
1426+
*
1427+
* @param classReference the reference of the class of the object
1428+
* @param nb the number of the object
1429+
* @return the XWiki Object
1430+
* @since 17.4.0RC1
1431+
*/
1432+
@Unstable
1433+
public Object getObject(EntityReference classReference, int nb)
1434+
{
1435+
try {
1436+
BaseObject obj = this.getDoc().getXObject(classReference, nb);
1437+
if (obj == null) {
1438+
return null;
1439+
} else {
1440+
return newObjectApi(obj, getXWikiContext());
1441+
}
1442+
} catch (Exception e) {
1443+
return null;
1444+
}
1445+
}
1446+
13901447
/**
13911448
* @param objectReference the object reference
13921449
* @return the XWiki object from this document that matches the specified object reference
@@ -1882,7 +1939,7 @@ public Attachment getAttachment(String filename)
18821939

18831940
/**
18841941
* @param filename the name of the attachment
1885-
* @return the attachment with the given filename or null if the attachment does not exist
1942+
* @return the attachment with the given filename or null if the attachment doesn’t exist
18861943
* @since 17.2.0RC1
18871944
*/
18881945
public Attachment removeAttachment(String filename)

xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/internal/document/DocumentRequiredRightsReader.java

+34-7
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.slf4j.Logger;
3232
import org.xwiki.component.annotation.Component;
3333
import org.xwiki.model.EntityType;
34+
import org.xwiki.model.reference.DocumentReference;
3435
import org.xwiki.model.reference.EntityReference;
3536
import org.xwiki.model.reference.LocalDocumentReference;
3637
import org.xwiki.security.authorization.Right;
@@ -115,17 +116,24 @@ public DocumentRequiredRights readRequiredRights(XWikiDocument document)
115116
return new DocumentRequiredRights(enforce, rights);
116117
}
117118

118-
private DocumentRequiredRight readRequiredRight(BaseObject object)
119+
/**
120+
* Read the required right from an XObject.
121+
*
122+
* @param object the XObject to read the required right from. Must not be {@code null}
123+
* @return the required right. Can be an illegal right if the value of the property is not a valid right
124+
* @since 17.4.0RC1
125+
*/
126+
public DocumentRequiredRight readRequiredRight(BaseObject object)
119127
{
120128
String value = object.getStringValue(PROPERTY_NAME);
121-
EntityType entityType = EntityType.DOCUMENT;
129+
EntityType initialEntityType = EntityType.DOCUMENT;
122130
Right right = Right.toRight(value);
123131
if (right.equals(Right.ILLEGAL)) {
124132
String[] levelRight = StringUtils.split(value, "_", 2);
125133
if (levelRight.length == 2) {
126134
right = Right.toRight(levelRight[1]);
127135
try {
128-
entityType = EntityType.valueOf(levelRight[0].toUpperCase());
136+
initialEntityType = EntityType.valueOf(levelRight[0].toUpperCase());
129137
} catch (IllegalArgumentException e) {
130138
// Ensure that we return an illegal right even if the right part of the value could be parsed.
131139
right = Right.ILLEGAL;
@@ -134,15 +142,35 @@ private DocumentRequiredRight readRequiredRight(BaseObject object)
134142
}
135143
}
136144

145+
EntityType entityType = getEffectiveEntityType(right, initialEntityType, object.getDocumentReference());
146+
147+
return new DocumentRequiredRight(right, entityType);
148+
}
149+
150+
/**
151+
* Determines the most specific effective {@link EntityType} based on the specified parameters. It adjusts
152+
* the entity type according to the rights' targeted entity types and the provided base document reference.
153+
*
154+
* @param right the {@link Right} being analyzed; it defines the targeted entity types to consider
155+
* @param initialEntityType the initial {@link EntityType} to be evaluated
156+
* @param baseDocumentReference the base {@link DocumentReference} used to adjust and validate the entity type
157+
* @return the most specific {@link EntityType} that is targeted by the {@link Right} and the same level or above
158+
* the given initial entity type in the hierarchy of the document reference. If no suitable entity type is found,
159+
* it defaults to {@link EntityType#DOCUMENT}, or null if the right targets only the farm level
160+
*/
161+
public EntityType getEffectiveEntityType(Right right, EntityType initialEntityType,
162+
DocumentReference baseDocumentReference)
163+
{
164+
EntityType entityType = initialEntityType;
137165
Set<EntityType> targetedEntityTypes = right.getTargetedEntityType();
138166
if (targetedEntityTypes == null) {
139167
// This means the right targets only the farm level, which is null.
140168
entityType = null;
141169
} else {
142-
EntityReference entityReference = object.getDocumentReference().extractReference(entityType);
170+
EntityReference entityReference = baseDocumentReference.extractReference(entityType);
143171
// The specified entity type seems to be below the document level. Fall back to document level instead.
144172
if (entityReference == null) {
145-
entityReference = object.getDocumentReference();
173+
entityReference = baseDocumentReference;
146174
}
147175
// Try to get the lowest level where this right can be assigned. This is done to ensure that, e.g.,
148176
// programming right can imply admin right on the wiki level even if programming right is only specified on
@@ -157,7 +185,6 @@ private DocumentRequiredRight readRequiredRight(BaseObject object)
157185
entityType = EntityType.DOCUMENT;
158186
}
159187
}
160-
161-
return new DocumentRequiredRight(right, entityType);
188+
return entityType;
162189
}
163190
}

xwiki-platform-core/xwiki-platform-security/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,6 @@
3535
<module>xwiki-platform-security-authentication</module>
3636
<module>xwiki-platform-security-authorization</module>
3737
<module>xwiki-platform-security-requiredrights</module>
38+
<module>xwiki-platform-security-test</module>
3839
</modules>
3940
</project>

xwiki-platform-core/xwiki-platform-security/xwiki-platform-security-requiredrights/pom.xml

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
<module>xwiki-platform-security-requiredrights-api</module>
3434
<module>xwiki-platform-security-requiredrights-default</module>
3535
<module>xwiki-platform-security-requiredrights-macro</module>
36+
<module>xwiki-platform-security-requiredrights-rest</module>
37+
<module>xwiki-platform-security-requiredrights-ui</module>
3638
</modules>
3739
<profiles>
3840
<profile>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* See the NOTICE file distributed with this work for additional
3+
* information regarding copyright ownership.
4+
*
5+
* This is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU Lesser General Public License as
7+
* published by the Free Software Foundation; either version 2.1 of
8+
* the License, or (at your option) any later version.
9+
*
10+
* This software is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this software; if not, write to the Free
17+
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18+
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19+
*/
20+
package org.xwiki.platform.security.requiredrights.internal;
21+
22+
import org.xwiki.security.authorization.requiredrights.DocumentRequiredRight;
23+
24+
/**
25+
* A suggestion for an operation that removes or adds rights to a document.
26+
*
27+
* @param increasesRights if more rights are granted due to this change
28+
* @param rightToRemove the right to replace
29+
* @param rightToAdd the right to add
30+
* @param requiresManualReview if the analysis is certain that the right is needed/not needed or the user needs to
31+
* @param hasPermission if the current user has the permission to perform the proposed change manually review the
32+
* analysis result to determine if the right is actually needed/not needed
33+
*
34+
* @version $Id$
35+
* @since 17.4.0RC1
36+
*/
37+
public record RequiredRightChangeSuggestion(boolean increasesRights, DocumentRequiredRight rightToRemove,
38+
DocumentRequiredRight rightToAdd, boolean requiresManualReview,
39+
boolean hasPermission)
40+
{
41+
}

0 commit comments

Comments
 (0)