This is an automated email from the ASF dual-hosted git repository.
solomax pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openmeetings.git
The following commit(s) were added to refs/heads/master by this push:
new 14a082a6b [OPENMEETINGS-2843] group admin is limited to manage only
regular users in own group
14a082a6b is described below
commit 14a082a6b8c8dac011c65521db2555ce98f81de1
Author: Maxim Solodovnik <[email protected]>
AuthorDate: Fri Oct 9 14:49:05 2026 +0700
[OPENMEETINGS-2843] group admin is limited to manage only regular users in
own group
---
LICENSE | 49 ++++---
.../apache/openmeetings/db/entity/user/User.java | 4 +-
.../openmeetings/web/admin/extra/ExtraForm.java | 4 +-
.../openmeetings/web/admin/groups/GroupForm.java | 23 ++--
.../openmeetings/web/admin/rooms/RoomForm.java | 12 +-
.../openmeetings/web/admin/users/UserForm.java | 76 ++++++++---
.../apache/openmeetings/web/app/WebSession.java | 4 +
.../openmeetings/web/common/GeneralUserForm.java | 127 +++++++++++++-----
.../openmeetings/web/common/general-user-form.js | 32 +++++
.../web/room/menu/RoomInvitationForm.java | 4 +-
.../web/user/calendar/AppointmentDialog.java | 4 +-
.../openmeetings/web/util/OmSelect2Choice.java | 33 +++++
...rMultiChoice.java => OmSelect2MultiChoice.java} | 27 ++--
.../openmeetings/web/util/UserMultiChoice.java | 3 +-
.../apache/openmeetings/web/GroupAdminTest.java | 147 +++++++++++++++++++++
pom.xml | 4 +-
16 files changed, 431 insertions(+), 122 deletions(-)
diff --git a/LICENSE b/LICENSE
index 27ff7e5ad..6d8623be5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -319,7 +319,7 @@ conditions of the following licenses.
* Microsoft JDBC Driver for SQL Server
(com.microsoft.sqlserver:mssql-jdbc:13.6.0.jre11 -
https://github.com/Microsoft/mssql-jdbc)
* minimasonry (org.webjars.npm:minimasonry:1.3.2 -
https://www.webjars.org)
* Modernizr (org.webjars:modernizr:2.8.3-1 - http://webjars.org)
- * moment (org.webjars.npm:moment:2.30.1 - https://www.webjars.org)
+ * moment (org.webjars.npm:moment:2.31.0 - https://www.webjars.org)
* org.brotli:dec (org.brotli:dec:0.1.2 - http://brotli.org/dec)
* org.danekja.jdk-serializable-functional
(org.danekja:jdk-serializable-functional:1.9.0 -
https://github.com/danekja/jdk-serializable-functional)
* progressbar.js (org.webjars.npm:progressbar.js:1.1.1 -
https://www.webjars.org)
@@ -331,7 +331,7 @@ conditions of the following licenses.
* Tempus Dominus Bootstrap 4
(org.webjars:tempusdominus-bootstrap-4:5.39.0 - http://webjars.org)
* typeahead.js (org.webjars:typeaheadjs:0.10.5-1 - http://webjars.org)
* uuid (org.webjars.npm:uuid:9.0.0-beta.0 - https://www.webjars.org)
- * wicketstuff-urlfragment
(org.wicketstuff:wicketstuff-urlfragment:10.11.0 -
https://github.com/Horsed/WicketUrlFragment/wicketstuff-urlfragment)
+ * wicketstuff-urlfragment
(org.wicketstuff:wicketstuff-urlfragment:10.12.0-SNAPSHOT -
https://github.com/Horsed/WicketUrlFragment/wicketstuff-urlfragment)
NOASSERTION
@@ -433,8 +433,8 @@ conditions of the following licenses.
* Apache Tika XML parser module
(org.apache.tika:tika-parser-xml-module:4.1.0 -
https://tika.apache.org/tika-parser-xml-module/)
* Apache Tika XMP commons
(org.apache.tika:tika-parser-xmp-commons:4.1.0 -
https://tika.apache.org/tika-parser-xmp-commons/)
* Apache Tika ZIP commons
(org.apache.tika:tika-parser-zip-commons:4.1.0 -
https://tika.apache.org/tika-parser-zip-commons/)
- * Apache Wicket IPageStore Common
(org.wicketstuff:wicketstuff-datastore-common:10.11.0 -
http://wicketstuff.org/datastores-parent/wicketstuff-datastore-common)
- * Apache Wicket IPageStore Hazelcast
(org.wicketstuff:wicketstuff-datastore-hazelcast:10.11.0 -
http://wicketstuff.org/datastores-parent/wicketstuff-datastore-hazelcast)
+ * Apache Wicket IPageStore Common
(org.wicketstuff:wicketstuff-datastore-common:10.12.0-SNAPSHOT -
http://wicketstuff.org/datastores-parent/wicketstuff-datastore-common)
+ * Apache Wicket IPageStore Hazelcast
(org.wicketstuff:wicketstuff-datastore-hazelcast:10.12.0-SNAPSHOT -
http://wicketstuff.org/datastores-parent/wicketstuff-datastore-hazelcast)
* Apache XBean :: ASM shaded (repackaged)
(org.apache.xbean:xbean-asm9-shaded:4.26 -
http://geronimo.apache.org/maven/xbean/4.26/xbean-asm9-shaded)
* Asterisk-Java (org.asteriskjava:asterisk-java:3.42.2 -
https://github.com/asterisk-java/asterisk-java)
* Bootstrap (org.webjars:bootstrap:5.3.8 - http://webjars.org)
@@ -480,8 +480,8 @@ conditions of the following licenses.
* Jettison (org.codehaus.jettison:jettison:1.5.7 -
https://github.com/jettison-json/jettison)
* JODConverter Core (org.jodconverter:jodconverter-core:4.4.11 -
https://github.com/jodconverter/jodconverter/)
* JODConverter Local (org.jodconverter:jodconverter-local:4.4.11 -
https://github.com/jodconverter/jodconverter/)
- * jqplot4java (org.wicketstuff:jqplot4java:10.11.0 -
http://maven.apache.org)
- * JqPlot Plugin Integration (org.wicketstuff:jqplot:10.11.0 -
http://wicketstuff.org/jqplot-parent/jqplot)
+ * jqplot4java (org.wicketstuff:jqplot4java:10.12.0-SNAPSHOT -
http://maven.apache.org)
+ * JqPlot Plugin Integration (org.wicketstuff:jqplot:10.12.0-SNAPSHOT -
http://wicketstuff.org/jqplot-parent/jqplot)
* JSpecify annotations (org.jspecify:jspecify:1.0.0 -
http://jspecify.org/)
* jwarc (org.netpreserve:jwarc:0.37.0 - https://github.com/iipc/jwarc)
* Kurento Client (org.kurento:kurento-client:7.3.0 -
https://kurento.openvidu.io/)
@@ -551,23 +551,28 @@ conditions of the following licenses.
* swagger-annotations (io.swagger.core.v3:swagger-annotations:2.2.55 -
https://github.com/swagger-api/swagger-core/modules/swagger-annotations)
* tomcat (org.apache.tomcat:tomcat:11.0.26 -
https://tomcat.apache.org/)
* UNO interface library (org.openoffice:unoil:4.1.2 -
https://wiki.openoffice.org/wiki/Uno)
- * Wicket Auth Roles (org.apache.wicket:wicket-auth-roles:10.11.0 -
http://wicket.apache.org/wicket-auth-roles)
- * Wicket Core (org.apache.wicket:wicket-core:10.11.0 -
http://wicket.apache.org/wicket-core)
- * Wicket Development Utilities
(org.apache.wicket:wicket-devutils:10.11.0 -
http://wicket.apache.org/wicket-devutils)
- * Wicket Extensions (org.apache.wicket:wicket-extensions:10.11.0 -
http://wicket.apache.org/wicket-extensions)
- * Wicket IoC common code (org.apache.wicket:wicket-ioc:10.11.0 -
http://wicket.apache.org/wicket-ioc)
+ * Wicket Auth Roles
(org.apache.wicket:wicket-auth-roles:10.12.0-SNAPSHOT -
http://wicket.apache.org/wicket-auth-roles)
+ * Wicket Core (org.apache.wicket:wicket-core:10.12.0-20260929.171021-8
- http://wicket.apache.org/wicket-core)
+ * Wicket Core (org.apache.wicket:wicket-core:10.12.0-SNAPSHOT -
http://wicket.apache.org/wicket-core)
+ * Wicket Development Utilities
(org.apache.wicket:wicket-devutils:10.12.0-SNAPSHOT -
http://wicket.apache.org/wicket-devutils)
+ * Wicket Extensions
(org.apache.wicket:wicket-extensions:10.12.0-20260929.171021-6 -
http://wicket.apache.org/wicket-extensions)
+ * Wicket Extensions
(org.apache.wicket:wicket-extensions:10.12.0-SNAPSHOT -
http://wicket.apache.org/wicket-extensions)
+ * Wicket IoC common code
(org.apache.wicket:wicket-ioc:10.12.0-SNAPSHOT -
http://wicket.apache.org/wicket-ioc)
* Wicket JQuery Selectors
(de.agilecoders.wicket:jquery-selectors:4.0.15 -
http://nexus.sonatype.org/oss-repository-hosting.html/jquery-selectors)
- * wicket-jquery-ui-calendar
(org.wicketstuff:wicketstuff-jquery-ui-calendar:10.11.0 -
https://github.com/sebfz1/wicket-jquery-ui/wicketstuff-jquery-ui-calendar)
- * wicket-jquery-ui-core
(org.wicketstuff:wicketstuff-jquery-ui-core:10.11.0 -
https://github.com/sebfz1/wicket-jquery-ui/wicketstuff-jquery-ui-core)
- * wicket-jquery-ui-plugins
(org.wicketstuff:wicketstuff-jquery-ui-plugins:10.11.0 -
https://github.com/sebfz1/wicket-jquery-ui/wicketstuff-jquery-ui-plugins)
- * Wicket Native WebSocket Core
(org.apache.wicket:wicket-native-websocket-core:10.11.0 -
http://wicket.apache.org/wicket-native-websocket/wicket-native-websocket-core)
- * Wicket Native WebSocket Javax
(org.apache.wicket:wicket-native-websocket-javax:10.11.0 -
http://wicket.apache.org/wicket-native-websocket/wicket-native-websocket-javax)
- * Wicket Request (org.apache.wicket:wicket-request:10.11.0 -
http://maven.apache.org)
- * Wicket Spring Integration (org.apache.wicket:wicket-spring:10.11.0 -
http://wicket.apache.org/wicket-spring)
- * Wicketstuff Dashboard Core
(org.wicketstuff:wicketstuff-dashboard-core:10.11.0 -
http://wicketstuff.org/wicketstuff-dashboard-parent/wicketstuff-dashboard-core)
- * wicketstuff-jquery-ui (org.wicketstuff:wicketstuff-jquery-ui:10.11.0
- https://github.com/sebfz1/wicket-jquery-ui/wicketstuff-jquery-ui)
- * Wicketstuff-Select2 Components
(org.wicketstuff:wicketstuff-select2:10.11.0 -
http://wicketstuff.org/wicketstuff-select2-parent/wicketstuff-select2)
- * Wicket Util (org.apache.wicket:wicket-util:10.11.0 -
http://wicket.apache.org/wicket-util)
+ * wicket-jquery-ui-calendar
(org.wicketstuff:wicketstuff-jquery-ui-calendar:10.12.0-SNAPSHOT -
https://github.com/sebfz1/wicket-jquery-ui/wicketstuff-jquery-ui-calendar)
+ * wicket-jquery-ui-core
(org.wicketstuff:wicketstuff-jquery-ui-core:10.12.0-SNAPSHOT -
https://github.com/sebfz1/wicket-jquery-ui/wicketstuff-jquery-ui-core)
+ * wicket-jquery-ui-plugins
(org.wicketstuff:wicketstuff-jquery-ui-plugins:10.12.0-SNAPSHOT -
https://github.com/sebfz1/wicket-jquery-ui/wicketstuff-jquery-ui-plugins)
+ * Wicket Native WebSocket Core
(org.apache.wicket:wicket-native-websocket-core:10.12.0-SNAPSHOT -
http://wicket.apache.org/wicket-native-websocket/wicket-native-websocket-core)
+ * Wicket Native WebSocket Javax
(org.apache.wicket:wicket-native-websocket-javax:10.12.0-20260929.171021-6 -
http://wicket.apache.org/wicket-native-websocket/wicket-native-websocket-javax)
+ * Wicket Native WebSocket Javax
(org.apache.wicket:wicket-native-websocket-javax:10.12.0-SNAPSHOT -
http://wicket.apache.org/wicket-native-websocket/wicket-native-websocket-javax)
+ * Wicket Request
(org.apache.wicket:wicket-request:10.12.0-20260929.171021-8 -
http://maven.apache.org)
+ * Wicket Request (org.apache.wicket:wicket-request:10.12.0-SNAPSHOT -
http://maven.apache.org)
+ * Wicket Spring Integration
(org.apache.wicket:wicket-spring:10.12.0-SNAPSHOT -
http://wicket.apache.org/wicket-spring)
+ * Wicketstuff Dashboard Core
(org.wicketstuff:wicketstuff-dashboard-core:10.12.0-SNAPSHOT -
http://wicketstuff.org/wicketstuff-dashboard-parent/wicketstuff-dashboard-core)
+ * wicketstuff-jquery-ui
(org.wicketstuff:wicketstuff-jquery-ui:10.12.0-SNAPSHOT -
https://github.com/sebfz1/wicket-jquery-ui/wicketstuff-jquery-ui)
+ * Wicketstuff-Select2 Components
(org.wicketstuff:wicketstuff-select2:10.12.0-SNAPSHOT -
http://wicketstuff.org/wicketstuff-select2-parent/wicketstuff-select2)
+ * Wicket Util (org.apache.wicket:wicket-util:10.12.0-20260929.171021-8
- http://wicket.apache.org/wicket-util)
+ * Wicket Util (org.apache.wicket:wicket-util:10.12.0-SNAPSHOT -
http://wicket.apache.org/wicket-util)
* Woodstox (com.fasterxml.woodstox:woodstox-core:7.2.1 -
https://github.com/FasterXML/woodstox)
* XmlBeans (org.apache.xmlbeans:xmlbeans:5.3.0 -
https://xmlbeans.apache.org/)
* XML Commons Resolver Component (xml-resolver:xml-resolver:1.2 -
http://xml.apache.org/commons/components/resolver/)
diff --git
a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/User.java
b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/User.java
index 9121b31ca..5839bbbc4 100644
---
a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/User.java
+++
b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/User.java
@@ -205,13 +205,13 @@ public class User extends HistoricalEntity {
return groupAdminAllowed;
}
- public static List<Right> getAllowed(boolean groupAdmin) {
+ public static Stream<Right> getAllowed(boolean groupAdmin) {
Stream<Right> stream = Stream.of(Right.values())
.filter(r -> Right.GROUP_ADMIN != r);
if (groupAdmin) {
stream =
stream.filter(Right::isGroupAdminAllowed);
}
- return stream.toList();
+ return stream;
}
}
diff --git
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/extra/ExtraForm.java
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/extra/ExtraForm.java
index 01b82539c..9239e7179 100644
---
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/extra/ExtraForm.java
+++
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/extra/ExtraForm.java
@@ -30,6 +30,7 @@ import org.apache.openmeetings.db.entity.user.Group;
import org.apache.openmeetings.web.admin.AdminBaseForm;
import org.apache.openmeetings.web.app.WebSession;
import org.apache.openmeetings.web.common.GroupChoiceProvider;
+import org.apache.openmeetings.web.util.OmSelect2MultiChoice;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.Form;
@@ -38,14 +39,13 @@ import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.model.util.CollectionModel;
-import org.wicketstuff.select2.Select2MultiChoice;
import jakarta.inject.Inject;
public class ExtraForm extends AdminBaseForm<ExtraMenu> {
private static final long serialVersionUID = 1L;
private final WebMarkupContainer list;
- final Select2MultiChoice<Group> groups = new
Select2MultiChoice<>("groups"
+ final OmSelect2MultiChoice<Group> groups = new
OmSelect2MultiChoice<>("groups"
, new CollectionModel<>(new ArrayList<>())
, new GroupChoiceProvider());
diff --git
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/groups/GroupForm.java
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/groups/GroupForm.java
index 59eab4b9f..490f5ea3d 100644
---
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/groups/GroupForm.java
+++
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/groups/GroupForm.java
@@ -39,6 +39,7 @@ import org.apache.openmeetings.db.entity.user.User;
import org.apache.openmeetings.web.admin.AdminBaseForm;
import org.apache.openmeetings.web.admin.AdminUserChoiceProvider;
import org.apache.openmeetings.web.common.UploadableImagePanel;
+import org.apache.openmeetings.web.util.OmSelect2Choice;
import org.apache.openmeetings.util.StoredFile;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
@@ -52,7 +53,6 @@ import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.ResourceModel;
-import org.wicketstuff.select2.Select2Choice;
import jakarta.inject.Inject;
@@ -60,7 +60,7 @@ public class GroupForm extends AdminBaseForm<Group> {
private static final long serialVersionUID = 1L;
private GroupUsersPanel usersPanel;
private final WebMarkupContainer groupList;
- private final Select2Choice<User> userToadd;
+ private OmSelect2Choice<User> userToadd;
private final NumberTextField<Integer> maxFilesSize = new
NumberTextField<>("maxFilesSize");
private final NumberTextField<Integer> maxRecordingsSize = new
NumberTextField<>("maxRecordingsSize");
private final NumberTextField<Integer> maxRooms = new
NumberTextField<>("maxRooms");
@@ -107,11 +107,19 @@ public class GroupForm extends AdminBaseForm<Group> {
setMultiPart(true);
this.groupList = groupList;
setOutputMarkupId(true);
+ }
+
+ static String formatUser(User choice) {
+ return String.format("%s [%s]", choice.getLogin(),
choice.getDisplayName());
+ }
+ @Override
+ protected void onInitialize() {
+ super.onInitialize();
usersPanel = new GroupUsersPanel("users", getGroupId());
add(usersPanel);
- add(userToadd = new Select2Choice<>("user2add",
Model.of((User)null), new AdminUserChoiceProvider() {
+ add(userToadd = new OmSelect2Choice<>("user2add",
Model.of((User)null), new AdminUserChoiceProvider() {
private static final long serialVersionUID = 1L;
@Override
@@ -142,15 +150,6 @@ public class GroupForm extends AdminBaseForm<Group> {
}
}
}));
- }
-
- static String formatUser(User choice) {
- return String.format("%s [%s]", choice.getLogin(),
choice.getDisplayName());
- }
-
- @Override
- protected void onInitialize() {
- super.onInitialize();
final boolean isGroupAdmin = hasGroupAdminLevel(getRights());
setNewVisible(!isGroupAdmin);
setNewRecordVisible(!isGroupAdmin);
diff --git
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomForm.java
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomForm.java
index 60ce63657..592e0b5aa 100644
---
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomForm.java
+++
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomForm.java
@@ -50,6 +50,8 @@ import org.apache.openmeetings.db.entity.user.User;
import org.apache.openmeetings.web.admin.AdminBaseForm;
import org.apache.openmeetings.web.admin.AdminUserChoiceProvider;
import org.apache.openmeetings.web.app.ClientManager;
+import org.apache.openmeetings.web.util.OmSelect2Choice;
+import org.apache.openmeetings.web.util.OmSelect2MultiChoice;
import org.apache.openmeetings.web.util.RestrictiveChoiceProvider;
import org.apache.openmeetings.web.util.RoomTypeDropDown;
import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -75,8 +77,6 @@ import org.apache.wicket.model.util.CollectionModel;
import org.apache.wicket.util.string.Strings;
import org.wicketstuff.select2.ChoiceProvider;
import org.wicketstuff.select2.Response;
-import org.wicketstuff.select2.Select2Choice;
-import org.wicketstuff.select2.Select2MultiChoice;
import de.agilecoders.wicket.core.markup.html.bootstrap.badge.BootstrapBadge;
import
de.agilecoders.wicket.core.markup.html.bootstrap.button.BootstrapAjaxButton;
@@ -168,7 +168,7 @@ public class RoomForm extends AdminBaseForm<Room> {
for (Group org : orgList) {
orgRooms.add(new RoomGroup(org, getModelObject()));
}
- add(new Select2MultiChoice<>("groups", null, new
RestrictiveChoiceProvider<RoomGroup>() {
+ add(new OmSelect2MultiChoice<>("groups", null, new
RestrictiveChoiceProvider<RoomGroup>() {
private static final long serialVersionUID = 1L;
@Override
@@ -215,7 +215,7 @@ public class RoomForm extends AdminBaseForm<Room> {
add(new
CheckBox("allowRecording").setEnabled(isRecordingsEnabled()));
add(new CheckBox("chatModerated"));
- add(new Select2MultiChoice<>("hiddenElements", null, new
ChoiceProvider<RoomElement>() {
+ add(new OmSelect2MultiChoice<>("hiddenElements", null, new
ChoiceProvider<RoomElement>() {
private static final long serialVersionUID = 1L;
@Override
@@ -250,7 +250,7 @@ public class RoomForm extends AdminBaseForm<Room> {
add(new CheckBox("filesOpened"));
// Moderators
- final Select2Choice<User> moderatorChoice = new
Select2Choice<>("moderator2add", moderator2add, new AdminUserChoiceProvider() {
+ final OmSelect2Choice<User> moderatorChoice = new
OmSelect2Choice<>("moderator2add", moderator2add, new AdminUserChoiceProvider()
{
private static final long serialVersionUID = 1L;
@Override
@@ -322,7 +322,7 @@ public class RoomForm extends AdminBaseForm<Room> {
// Files
Form<Void> filesForm = new Form<>("filesForm");
- add(filesForm.add(new Select2MultiChoice<>("files2add",
files2add, new ChoiceProvider<BaseFileItem>() {
+ add(filesForm.add(new OmSelect2MultiChoice<>("files2add",
files2add, new ChoiceProvider<BaseFileItem>() {
private static final long serialVersionUID = 1L;
@Override
diff --git
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/users/UserForm.java
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/users/UserForm.java
index be1278515..368363df4 100644
---
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/users/UserForm.java
+++
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/users/UserForm.java
@@ -23,11 +23,11 @@ import static
org.apache.openmeetings.db.dao.user.UserDao.getNewUserInstance;
import static org.apache.openmeetings.db.entity.user.User.DISPLAY_NAME_NA;
import static org.apache.openmeetings.db.util.AuthLevelUtil.hasAdminLevel;
import static org.apache.openmeetings.db.util.AuthLevelUtil.hasGroupAdminLevel;
-import static org.apache.openmeetings.web.app.WebSession.getRights;
-import static org.apache.openmeetings.web.app.WebSession.getUserId;
import static
org.apache.openmeetings.util.OpenmeetingsVariables.getMinLoginLength;
import static org.apache.openmeetings.util.OpenmeetingsVariables.isOtpEnabled;
import static
org.apache.openmeetings.util.OpenmeetingsVariables.isSendRegisterEmail;
+import static org.apache.openmeetings.web.app.WebSession.getRights;
+import static org.apache.openmeetings.web.app.WebSession.getUserId;
import static
org.apache.wicket.validation.validator.StringValidator.minimumLength;
import java.util.ArrayList;
@@ -35,6 +35,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
import org.apache.openmeetings.core.util.StrongPasswordValidator;
import org.apache.openmeetings.db.dao.server.LdapConfigDao;
@@ -48,10 +50,12 @@ import org.apache.openmeetings.db.entity.user.User.Type;
import org.apache.openmeetings.db.util.AuthLevelUtil;
import org.apache.openmeetings.service.mail.EmailManager;
import org.apache.openmeetings.web.admin.AdminBaseForm;
+import org.apache.openmeetings.web.app.WebSession;
import org.apache.openmeetings.web.common.CommunityUserForm;
import org.apache.openmeetings.web.common.GeneralUserForm;
import org.apache.openmeetings.web.common.UploadableProfileImagePanel;
import org.apache.openmeetings.web.util.DateLabel;
+import org.apache.openmeetings.web.util.OmSelect2MultiChoice;
import org.apache.openmeetings.web.util.RestrictiveChoiceProvider;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
@@ -73,7 +77,6 @@ import
org.danekja.java.util.function.serializable.SerializableConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wicketstuff.select2.Response;
-import org.wicketstuff.select2.Select2MultiChoice;
import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
import jakarta.inject.Inject;
@@ -91,6 +94,7 @@ public class UserForm extends AdminBaseForm<User> {
private final WebMarkupContainer listContainer;
private final WebMarkupContainer domain = new
WebMarkupContainer("domain");
private GeneralUserForm generalForm;
+ private OmSelect2MultiChoice<Right> rightsSelect;
private final RequiredTextField<String> login = new
RequiredTextField<>("login");
private StrongPasswordValidator passValidator;
private final PasswordTextField password = new
PasswordTextField("password", new Model<>());
@@ -143,7 +147,7 @@ public class UserForm extends AdminBaseForm<User> {
mainContainer.add(new DateLabel("inserted"));
mainContainer.add(new DateLabel("updated"));
- mainContainer.add(new Select2MultiChoice<>("rights", null, new
RestrictiveChoiceProvider<Right>() {
+ rightsSelect = new OmSelect2MultiChoice<>("rights", null, new
RestrictiveChoiceProvider<Right>() {
private static final long serialVersionUID = 1L;
@Override
@@ -156,21 +160,37 @@ public class UserForm extends AdminBaseForm<User> {
return choice.name();
}
+ public String getDisabledAttribute() {
+ return "data-disabled";
+ }
+
+ @Override
+ public boolean isDisabled(Right choice) {
+ if (hasAdminLevel(getRights())) {
+ return false;
+ }
+ return choice == null ||
!choice.isGroupAdminAllowed();
+ }
+
@Override
public void query(String term, int page,
Response<Right> response) {
boolean isGroupAdmin =
hasGroupAdminLevel(getRights());
- for (Right r : Right.getAllowed(isGroupAdmin)) {
- if (Strings.isEmpty(term) ||
r.name().contains(term)) {
- response.add(r);
- }
- }
+ Right.getAllowed(isGroupAdmin)
+ .filter(r -> Strings.isEmpty(term) ||
r.name().contains(term))
+ .forEach(r -> response.add(r));
}
@Override
public Right fromId(String id) {
return Right.valueOf(id);
}
- }));
+ });
+ mainContainer.add(rightsSelect.setLabel(new
ResourceModel("881")));
+ rightsSelect.getSettings()
+
.setTemplateSelection("templateSelectionWithDisabled")
+ .setTemplateResult("templateResultWithDisabled")
+ .setDataAdapter("OmWithDisabledAdapter");
+
mainContainer.add(new CommunityUserForm("comunity",
getModel()));
remove(validationBehavior);
setNewRecordVisible(true);
@@ -186,15 +206,24 @@ public class UserForm extends AdminBaseForm<User> {
protected void onModelChanged() {
super.onModelChanged();
User u = getModelObject();
- boolean nd = !u.isDeleted();
boolean isNew = u.getId() == null;
- mainContainer.setEnabled(nd);
+ // This form is enabled if:
+ boolean nd = !u.isDeleted(); // user is NOT deleted
+ boolean enabled = nd
+ && (isAdmin(getRights()) // edited by 'full' admin
+ || isNew // the user is new
+ // group admin can edit themselves
+ || WebSession.getUserId().equals(u.getId())
+ // the user is NOT new and has only rights
allowed for group admin
+ || (!isNew &&
u.getRights().stream().allMatch(Right::isGroupAdminAllowed))
+ );
+ mainContainer.setEnabled(enabled);
otpEnabled.setModelObject(u.getOtpSecret() != null)
.setEnabled(isOtpEnabled() && u.getOtpSecret()
!= null); // admin can only disable OTP
- setSaveVisible(nd);
- setDelVisible(nd && !isNew);
- setRestoreVisible(!nd);
- setPurgeVisible(!isNew);
+ setSaveVisible(enabled);
+ setDelVisible(enabled && !isNew);
+ setRestoreVisible(enabled && !nd);
+ setPurgeVisible(enabled && !isNew);
password.setModelObject(null);
generalForm.updateModelObject(u, true);
passValidator.setUser(u);
@@ -238,14 +267,14 @@ public class UserForm extends AdminBaseForm<User> {
}
}
- private static boolean checkLevel(Set<User.Right> rights) {
+ private static boolean isAdmin(Set<User.Right> rights) {
return hasAdminLevel(rights) ||
AuthLevelUtil.hasWebServiceLevel(rights);
}
boolean isAdminPassRequired() {
User u = getModelObject();
User ou = userDao.get(u.getId());
- return checkLevel(u.getRights()) || (ou != null &&
checkLevel(ou.getRights()));
+ return isAdmin(u.getRights()) || (ou != null &&
isAdmin(ou.getRights()));
}
private void purgeUser(AjaxRequestTarget target) {
@@ -361,6 +390,17 @@ public class UserForm extends AdminBaseForm<User> {
if (!userDao.checkLogin(login.getConvertedInput(), u.getType(),
u.getDomainId(), u.getId())) {
error(getString("error.login.inuse"));
}
+ if (u.getId() != null && !isAdmin(getRights())) {
+ // group admin, let's check rights modifications
+ Set<Right> originalRights =
userDao.get(u.getId()).getRights();
+ Set<Right> currentRights =
Set.copyOf(rightsSelect.getConvertedInput());
+ if
(Stream.concat(originalRights.stream().filter(Predicate.not(currentRights::contains))
+ ,
currentRights.stream().filter(Predicate.not(originalRights::contains)))
+
.anyMatch(Predicate.not(Right::isGroupAdminAllowed)))
+ {
+ rightsSelect.error(getString("204") + " " +
getString("881"));
+ }
+ }
super.onValidate();
}
diff --git
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/WebSession.java
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/WebSession.java
index e218ceb45..583c2ac5e 100644
---
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/WebSession.java
+++
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/WebSession.java
@@ -420,6 +420,10 @@ public class WebSession extends
AbstractAuthenticatedWebSession implements IWebS
return session.languageId;
}
+ public static boolean isRtl() {
+ return LabelDao.getLanguage(getLanguage()).isRtl();
+ }
+
public static Long getUserId() {
checkIsInvalid();
return get().userId;
diff --git
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/GeneralUserForm.java
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/GeneralUserForm.java
index e8a9a4df0..c31122168 100644
---
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/GeneralUserForm.java
+++
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/GeneralUserForm.java
@@ -18,14 +18,19 @@
*/
package org.apache.openmeetings.web.common;
+import static org.apache.openmeetings.db.util.AuthLevelUtil.hasAdminLevel;
import static org.apache.openmeetings.db.util.AuthLevelUtil.hasGroupAdminLevel;
+import static
org.apache.openmeetings.util.OpenmeetingsVariables.isDisplayNameEditable;
import static org.apache.openmeetings.web.app.WebSession.AVAILABLE_TIMEZONES;
import static org.apache.openmeetings.web.app.WebSession.getRights;
import static org.apache.openmeetings.web.app.WebSession.getUserId;
-import static
org.apache.openmeetings.util.OpenmeetingsVariables.isDisplayNameEditable;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.openmeetings.db.dao.user.GroupDao;
import org.apache.openmeetings.db.dao.user.UserDao;
@@ -33,10 +38,15 @@ import org.apache.openmeetings.db.entity.user.Group;
import org.apache.openmeetings.db.entity.user.GroupUser;
import org.apache.openmeetings.db.entity.user.User;
import org.apache.openmeetings.db.entity.user.User.Salutation;
+import org.apache.openmeetings.web.app.WebSession;
import org.apache.openmeetings.web.common.datetime.AjaxOmDatePicker;
import org.apache.openmeetings.web.util.CountryDropDown;
+import org.apache.openmeetings.web.util.OmSelect2MultiChoice;
import org.apache.openmeetings.web.util.RestrictiveChoiceProvider;
import
org.apache.wicket.extensions.validation.validator.RfcCompliantEmailAddressValidator;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.markup.head.PriorityHeaderItem;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.LambdaChoiceRenderer;
@@ -47,9 +57,9 @@ import
org.apache.wicket.markup.html.panel.IMarkupSourcingStrategy;
import org.apache.wicket.markup.html.panel.PanelMarkupSourcingStrategy;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.apache.wicket.util.string.Strings;
import org.wicketstuff.select2.Response;
-import org.wicketstuff.select2.Select2MultiChoice;
import jakarta.inject.Inject;
@@ -58,7 +68,54 @@ public class GeneralUserForm extends Form<User> {
private final RequiredTextField<String> email = new
RequiredTextField<>("address.email");
private final List<GroupUser> grpUsers = new ArrayList<>();
private final AjaxOmDatePicker bday = new AjaxOmDatePicker("age");
+ private final OmSelect2MultiChoice<GroupUser> groupSelect = new
OmSelect2MultiChoice<>("groupUsers", null, new
RestrictiveChoiceProvider<GroupUser>() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public String getDisplayValue(GroupUser choice) {
+ return choice.getGroup().getName();
+ }
+
+ @Override
+ public String toId(GroupUser choice) {
+ Long id = choice.getGroup().getId();
+ return id == null ? null : "" + id;
+ }
+
+ public String getDisabledAttribute() {
+ return "data-disabled";
+ }
+
+ @Override
+ public boolean isDisabled(GroupUser choice) {
+ if (hasAdminLevel(getRights())) {
+ return false;
+ }
+ Long id = choice.getGroup().getId();
+ return id != null && !adminGrps.contains(id);
+ }
+
+ @Override
+ public void query(String term, int page, Response<GroupUser>
response) {
+ for (GroupUser ou : grpUsers) {
+ if (Strings.isEmpty(term) ||
ou.getGroup().getName().contains(term)) {
+ response.add(ou);
+ }
+ }
+ }
+
+ @Override
+ public GroupUser fromId(String inId) {
+ Long id = Long.parseLong(inId);
+ User u = GeneralUserForm.this.getModelObject();
+ Group g = groupDao.get(id);
+ GroupUser gu = new GroupUser(g, u);
+ int idx = grpUsers.indexOf(gu);
+ return idx < 0 ? gu : grpUsers.get(idx);
+ }
+ });
private final boolean isAdminForm;
+ private final Set<Long> adminGrps;
@Inject
private GroupDao groupDao;
@@ -70,6 +127,11 @@ public class GeneralUserForm extends Form<User> {
this.isAdminForm = isAdminForm;
updateModelObject(getModelObject(), isAdminForm);
setOutputMarkupId(true);
+ adminGrps =
userDao.get(WebSession.getUserId()).getGroupUsers().stream()
+ .filter(GroupUser::isModerator)
+ .map(GroupUser::getGroup)
+ .map(Group::getId)
+ .collect(Collectors.toSet());
}
@Override
@@ -94,39 +156,21 @@ public class GeneralUserForm extends Form<User> {
add(new TextField<String>("address.town"));
add(new CountryDropDown("address.country"));
add(new TextArea<String>("address.comment"));
- add(new Select2MultiChoice<>("groupUsers", null, new
RestrictiveChoiceProvider<GroupUser>() {
- private static final long serialVersionUID = 1L;
-
- @Override
- public String getDisplayValue(GroupUser choice) {
- return choice.getGroup().getName();
- }
+ groupSelect.getSettings()
+
.setTemplateSelection("templateSelectionWithDisabled")
+ .setTemplateResult("templateResultWithDisabled")
+ .setDataAdapter("OmWithDisabledAdapter");
+ add(groupSelect
+ .setLabel(new ResourceModel("161"))
+ .setRequired(isAdminForm &&
hasGroupAdminLevel(getRights()))
+ .setEnabled(isAdminForm));
+ }
- @Override
- public String toId(GroupUser choice) {
- Long id = choice.getGroup().getId();
- return id == null ? null : "" + id;
- }
- @Override
- public void query(String term, int page,
Response<GroupUser> response) {
- for (GroupUser ou : grpUsers) {
- if (Strings.isEmpty(term) ||
ou.getGroup().getName().contains(term)) {
- response.add(ou);
- }
- }
- }
-
- @Override
- public GroupUser fromId(String inId) {
- Long id = Long.parseLong(inId);
- User u = GeneralUserForm.this.getModelObject();
- Group g = groupDao.get(id);
- GroupUser gu = new GroupUser(g, u);
- int idx = grpUsers.indexOf(gu);
- return idx < 0 ? gu : grpUsers.get(idx);
- }
- }).setLabel(new ResourceModel("161")).setRequired(isAdminForm
&& hasGroupAdminLevel(getRights())).setEnabled(isAdminForm));
+ @Override
+ public void renderHead(IHeaderResponse response) {
+ super.renderHead(response);
+ response.render(new
PriorityHeaderItem(JavaScriptHeaderItem.forReference(new
JavaScriptResourceReference(getClass(), "general-user-form.js"),
"general-user-form")));
}
public void updateModelObject(User u, boolean isAdminForm) {
@@ -152,6 +196,23 @@ public class GeneralUserForm extends Form<User> {
if (!userDao.checkEmail(email.getConvertedInput(), u.getType(),
u.getDomainId(), u.getId())) {
error(getString("error.email.inuse"));
}
+ if (u.getId() != null && isAdminForm &&
!hasAdminLevel(getRights())) {
+ // group admin, let's check group modifications
+ Set<Long> originalGroups =
userDao.get(u.getId()).getGroupUsers().stream()
+ .map(GroupUser::getGroup)
+ .map(Group::getId)
+ .collect(Collectors.toSet());
+ Set<Long> currentGroups =
groupSelect.getConvertedInput().stream()
+ .map(GroupUser::getGroup)
+ .map(Group::getId)
+ .collect(Collectors.toSet());
+ if
(Stream.concat(originalGroups.stream().filter(Predicate.not(currentGroups::contains))
+ ,
currentGroups.stream().filter(Predicate.not(originalGroups::contains)))
+ .anyMatch(Predicate.not(adminGrps::contains)))
+ {
+ groupSelect.error(getString("204") + " " +
getString("161"));
+ }
+ }
super.onValidate();
}
diff --git
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/general-user-form.js
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/general-user-form.js
new file mode 100644
index 000000000..5fd804c56
--- /dev/null
+++
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/general-user-form.js
@@ -0,0 +1,32 @@
+/* Licensed under the Apache License, Version 2.0 (the "License")
http://www.apache.org/licenses/LICENSE-2.0 */
+const AjaxAdapter = $.fn.select2.amd.require('select2/data/ajax');
+class OmWithDisabledAdapter extends AjaxAdapter {
+ constructor($element, options) {
+ super($element, options);
+ }
+
+ item($element) {
+ const res = super.item($element);
+ if ($element.data('disabled') === 'data-disabled') {
+ res.disabled = true;
+ }
+ return res;
+ }
+}
+
+function templateSelectionWithDisabled(data, container) {
+ if (data.disabled === true) {
+ container.addClass('select2-results__option--disabled');
+ container.find('button').css('pointer-events', 'none');
+ }
+ return data.text;
+}
+
+function templateResultWithDisabled(data, container) {
+ if (data.disabled === true && data.loading !== true) {
+
container.classList.remove('select2-results__option--selectable');
+ container.classList.add('select2-results__option--disabled');
+ container.style.pointerEvents = 'none';
+ }
+ return data.text;
+}
diff --git
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomInvitationForm.java
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomInvitationForm.java
index 63dc55f34..2fbd55417 100644
---
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomInvitationForm.java
+++
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomInvitationForm.java
@@ -35,6 +35,7 @@ import org.apache.openmeetings.service.room.InvitationManager;
import org.apache.openmeetings.web.app.WebSession;
import org.apache.openmeetings.web.common.GroupChoiceProvider;
import org.apache.openmeetings.web.common.InvitationForm;
+import org.apache.openmeetings.web.util.OmSelect2MultiChoice;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
@@ -49,7 +50,6 @@ import org.apache.wicket.model.util.CollectionModel;
import org.apache.wicket.util.string.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.wicketstuff.select2.Select2MultiChoice;
import jakarta.inject.Inject;
@@ -59,7 +59,7 @@ public class RoomInvitationForm extends InvitationForm {
private final RadioGroup<InviteeType> rdi = new
RadioGroup<>("inviteeType", Model.of(InviteeType.USER));
private final Long roomId;
private final WebMarkupContainer groupContainer = new
WebMarkupContainer("groupContainer");
- private final Select2MultiChoice<Group> groups = new
Select2MultiChoice<>("groups"
+ private final OmSelect2MultiChoice<Group> groups = new
OmSelect2MultiChoice<>("groups"
, new CollectionModel<>(new ArrayList<>())
, new GroupChoiceProvider());
final WebMarkupContainer sipContainer = new
WebMarkupContainer("sip-container");
diff --git
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/AppointmentDialog.java
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/AppointmentDialog.java
index c9087e4ac..183e4d966 100644
---
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/AppointmentDialog.java
+++
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/AppointmentDialog.java
@@ -56,6 +56,7 @@ import
org.apache.openmeetings.web.common.datetime.OmDateTimePicker;
import org.apache.openmeetings.web.pages.MainPage;
import org.apache.openmeetings.web.user.OmWysiwygToolbar;
import org.apache.openmeetings.web.user.rooms.RoomEnterBehavior;
+import org.apache.openmeetings.web.util.OmSelect2MultiChoice;
import org.apache.openmeetings.web.util.RoomTypeDropDown;
import org.apache.openmeetings.web.util.UserMultiChoice;
import org.apache.wicket.AttributeModifier;
@@ -86,7 +87,6 @@ import org.apache.wicket.model.util.CollectionModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.wicketstuff.select2.Select2MultiChoice;
import org.wicketstuff.jquery.ui.plugins.wysiwyg.WysiwygEditor;
@@ -109,7 +109,7 @@ public class AppointmentDialog extends Modal<Appointment> {
private final NotificationPanel feedback = new
NotificationPanel("feedback");
private final WebMarkupContainer sipContainer = new
WebMarkupContainer("sip-container");
private final RadioGroup<InviteeType> rdi = new
RadioGroup<>("inviteeType", Model.of(InviteeType.USER));
- private final Select2MultiChoice<Group> groups = new
Select2MultiChoice<>("groups"
+ private final OmSelect2MultiChoice<Group> groups = new
OmSelect2MultiChoice<>("groups"
, new CollectionModel<>(new ArrayList<>())
, new GroupChoiceProvider());
private final UserMultiChoice attendees = new
UserMultiChoice("attendees", new CollectionModel<>(new ArrayList<>()));
diff --git
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/OmSelect2Choice.java
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/OmSelect2Choice.java
new file mode 100644
index 000000000..07decc9ab
--- /dev/null
+++
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/OmSelect2Choice.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") + you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.web.util;
+
+import org.apache.openmeetings.web.app.WebSession;
+import org.apache.wicket.model.IModel;
+import org.wicketstuff.select2.ChoiceProvider;
+import org.wicketstuff.select2.Select2Choice;
+import org.wicketstuff.select2.Settings;
+
+public class OmSelect2Choice<T> extends Select2Choice<T> {
+ public OmSelect2Choice(String id, IModel<T> model, ChoiceProvider<T>
provider) {
+ super(id, model, provider);
+ getSettings()
+ .setDir(WebSession.isRtl() ? Settings.DIRECTION.rtl :
Settings.DIRECTION.ltr);
+ }
+}
diff --git
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/UserMultiChoice.java
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/OmSelect2MultiChoice.java
similarity index 50%
copy from
openmeetings-web/src/main/java/org/apache/openmeetings/web/util/UserMultiChoice.java
copy to
openmeetings-web/src/main/java/org/apache/openmeetings/web/util/OmSelect2MultiChoice.java
index f56f6b46d..50013f44e 100644
---
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/UserMultiChoice.java
+++
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/OmSelect2MultiChoice.java
@@ -20,27 +20,16 @@ package org.apache.openmeetings.web.util;
import java.util.Collection;
-import org.apache.openmeetings.db.entity.user.User;
-import org.apache.wicket.markup.head.IHeaderResponse;
-import org.apache.wicket.markup.head.JavaScriptHeaderItem;
-import org.apache.wicket.markup.head.PriorityHeaderItem;
+import org.apache.openmeetings.web.app.WebSession;
import org.apache.wicket.model.IModel;
-import org.apache.wicket.request.resource.JavaScriptResourceReference;
+import org.wicketstuff.select2.ChoiceProvider;
import org.wicketstuff.select2.Select2MultiChoice;
+import org.wicketstuff.select2.Settings;
-public class UserMultiChoice extends Select2MultiChoice<User> {
- private static final long serialVersionUID = 1L;
-
- public UserMultiChoice(String id, IModel<Collection<User>> model) {
- super(id, model, new UserChoiceProvider());
- getSettings().setTemplateResult("formatOmUser");
- getSettings().setTemplateSelection("formatOmUser");
- getSettings().setEscapeMarkup("escapeOmUserMarkup");
- }
-
- @Override
- public void renderHead(IHeaderResponse response) {
- super.renderHead(response);
- response.render(new
PriorityHeaderItem(JavaScriptHeaderItem.forReference(new
JavaScriptResourceReference(getClass(), "user-multi-choice.js"),
"user-multi-choice")));
+public class OmSelect2MultiChoice<T> extends Select2MultiChoice<T> {
+ public OmSelect2MultiChoice(String id, IModel<Collection<T>> model,
ChoiceProvider<T> provider) {
+ super(id, model, provider);
+ getSettings()
+ .setDir(WebSession.isRtl() ? Settings.DIRECTION.rtl :
Settings.DIRECTION.ltr);
}
}
diff --git
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/UserMultiChoice.java
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/UserMultiChoice.java
index f56f6b46d..f4448ee65 100644
---
a/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/UserMultiChoice.java
+++
b/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/UserMultiChoice.java
@@ -26,9 +26,8 @@ import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.PriorityHeaderItem;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
-import org.wicketstuff.select2.Select2MultiChoice;
-public class UserMultiChoice extends Select2MultiChoice<User> {
+public class UserMultiChoice extends OmSelect2MultiChoice<User> {
private static final long serialVersionUID = 1L;
public UserMultiChoice(String id, IModel<Collection<User>> model) {
diff --git
a/openmeetings-web/src/test/java/org/apache/openmeetings/web/GroupAdminTest.java
b/openmeetings-web/src/test/java/org/apache/openmeetings/web/GroupAdminTest.java
new file mode 100644
index 000000000..78e8eaa42
--- /dev/null
+++
b/openmeetings-web/src/test/java/org/apache/openmeetings/web/GroupAdminTest.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") + you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.web;
+
+import static java.util.UUID.randomUUID;
+
+import static org.apache.openmeetings.db.util.AuthLevelUtil.hasAdminLevel;
+import static org.apache.openmeetings.db.util.AuthLevelUtil.hasGroupAdminLevel;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.openmeetings.db.dao.room.RoomDao;
+import org.apache.openmeetings.db.entity.room.Room;
+import org.apache.openmeetings.db.entity.user.Group;
+import org.apache.openmeetings.db.entity.user.GroupUser;
+import org.apache.openmeetings.db.entity.user.User;
+import org.apache.openmeetings.db.manager.RoomManager;
+import org.apache.openmeetings.web.admin.users.UserForm;
+import org.apache.openmeetings.web.admin.users.UsersPanel;
+import org.apache.openmeetings.web.pages.MainPage;
+import org.apache.openmeetings.web.util.OmSelect2MultiChoice;
+import org.apache.openmeetings.web.util.OmUrlFragment.AreaKeys;
+import org.apache.wicket.behavior.AbstractAjaxBehavior;
+import org.apache.wicket.Component;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.protocol.ws.tester.WebSocketTester;
+import org.junit.jupiter.api.Test;
+
+import jakarta.inject.Inject;
+
+public class GroupAdminTest extends AbstractWicketTesterTest {
+ @Inject
+ private RoomDao roomDao;
+ @Inject
+ private RoomManager roomManager;
+
+ @Test
+ void groupAdminCanRemoveVictimsUnrelatedMembershipThroughUserForm()
throws Exception {
+ String token = randomUUID().toString();
+ Group groupA = groupDao.update(new Group().setName("i007-A-" +
token), null);
+ Group groupB = groupDao.update(new Group().setName("i007-B-" +
token), null);
+
+ User attacker = createUser("i007-attacker-" + token);
+ attacker.setGroupUsers(new ArrayList<>());
+ GroupUser attackerMembership = new GroupUser(groupA, attacker);
+ attackerMembership.setModerator(true);
+ attacker.getGroupUsers().add(attackerMembership);
+ attacker = userDao.update(attacker, null);
+
+ User victim = createUser("i007-victim-" + token);
+ victim.setGroupUsers(new ArrayList<>());
+ victim.addGroup(groupA);
+ victim.addGroup(groupB);
+ victim = userDao.update(victim, null);
+
+ Room room = new Room();
+ room.setName("i007-B-only-" + token);
+ room.setType(Room.Type.CONFERENCE);
+ room.setIspublic(false);
+ room.setOwnerId(userDao.get(1L).getId());
+ room.addGroup(groupB);
+ room = roomDao.update(room, userDao.get(1L).getId());
+
+ User attackerReloaded = userDao.get(attacker.getId());
+ User victimBefore = userDao.get(victim.getId());
+ Room roomBefore = roomDao.get(room.getId());
+ assertNotNull(attackerReloaded);
+ assertNotNull(victimBefore);
+ assertNotNull(roomBefore);
+ assertTrue(hasGroupAdminLevel(attackerReloaded.getRights()) ||
attackerReloaded.getGroupUsers().stream().anyMatch(GroupUser::isModerator));
+ assertFalse(hasAdminLevel(attackerReloaded.getRights()),
"attacker must not be global ADMIN");
+
assertTrue(groupIds(victimBefore).containsAll(Set.of(groupA.getId(),
groupB.getId())));
+ assertTrue(roomManager.isRoomAllowedToUser(roomBefore,
victimBefore), "victim initially reaches B-only room");
+
+ login(attacker.getLogin(), createPass());
+ MainPage page = tester.startPage(MainPage.class);
+ tester.assertRenderedPage(MainPage.class);
+
tester.executeBehavior((AbstractAjaxBehavior)page.getBehaviorById(1));
+
tester.executeBehavior((AbstractAjaxBehavior)page.get("main-container").getBehaviorById(0));
+ WebSocketTester webSocketTester = new WebSocketTester(tester,
page);
+
webSocketTester.sendMessage(org.apache.openmeetings.web.common.OmWebSocketPanel.CONNECTED_MSG);
+ tester.getRequest().setParameter(AreaKeys.ADMIN.zone(), "user");
+
tester.executeBehavior((AbstractAjaxBehavior)page.getBehaviorById(0));
+ tester.assertComponent(PATH_CHILD, UsersPanel.class);
+ UsersPanel usersPanel = (UsersPanel)page.get(PATH_CHILD);
+
+ Item<?> victimItem = null;
+ for (int i = 0; i < 20; ++i) {
+ Component candidate =
usersPanel.get("listContainer:userList:" + i);
+ if (candidate != null &&
victim.getId().equals(candidate.getDefaultModelObject() instanceof User u ?
u.getId() : null)) {
+ victimItem = (Item<?>)candidate;
+ break;
+ }
+ }
+ assertNotNull(victimItem, "victim must be present in
GROUP_ADMIN-scoped list");
+
tester.executeBehavior((AbstractAjaxBehavior)victimItem.getBehaviorById(0));
+
+ UserForm form = (UserForm)usersPanel.get("form");
+ @SuppressWarnings("unchecked")
+ OmSelect2MultiChoice<GroupUser> memberships =
(OmSelect2MultiChoice<GroupUser>)form.get("adminForm:general:groupUsers");
+ Collection<GroupUser> loadedMemberships =
memberships.getModelObject();
+ assertNotNull(loadedMemberships);
+
assertTrue(groupIds(loadedMemberships).containsAll(Set.of(groupA.getId(),
groupB.getId())), "loaded form contains A and unrelated B");
+
+ memberships.setModelObject(loadedMemberships.stream().filter(gu
-> groupA.getId().equals(gu.getGroup().getId())).toList());
+
tester.executeBehavior((AbstractAjaxBehavior)form.get("buttons:btn-save").getBehaviorById(0));
+
+ User victimAfter = userDao.get(victim.getId());
+ Room roomAfter = roomDao.get(room.getId());
+ Set<Long> afterGroups = groupIds(victimAfter);
+ boolean accessAfter =
roomManager.isRoomAllowedToUser(roomAfter, victimAfter);
+ assertTrue(afterGroups.contains(groupA.getId()));
+ assertFalse(afterGroups.contains(groupB.getId()), "B membership
must be deleted by persisted form collection");
+ assertFalse(accessAfter, "victim must lose B-only room access
after B membership deletion");
+ }
+
+ private static Set<Long> groupIds(User user) {
+ return groupIds(user.getGroupUsers());
+ }
+
+ private static Set<Long> groupIds(Collection<GroupUser> memberships) {
+ return memberships.stream().map(gu ->
gu.getGroup().getId()).collect(Collectors.toSet());
+ }
+}
diff --git a/pom.xml b/pom.xml
index 1bb673fd9..9ba77ec12 100644
--- a/pom.xml
+++ b/pom.xml
@@ -87,8 +87,8 @@
<!-- dependency versions -->
<junit.version>6.1.3</junit.version>
- <wicket.version>10.11.0</wicket.version>
- <wicketstuff.version>10.11.0</wicketstuff.version>
+ <wicket.version>10.12.0-SNAPSHOT</wicket.version>
+ <wicketstuff.version>10.12.0-SNAPSHOT</wicketstuff.version>
<wicket-bootstrap.version>7.0.15</wicket-bootstrap.version>
<fullcalendar.version>7.1.0</fullcalendar.version>
<spring.version>7.0.9</spring.version>