/** * * Copyright 2021 Florian Schmaus, Dan Caseley * * Licensed 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.jivesoftware.smackx.muc; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smackx.muc.packet.MUCUser; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; import org.igniterealtime.smack.inttest.TestNotPossibleException; import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest; import org.igniterealtime.smack.inttest.util.ResultSyncPoint; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.Jid; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.parts.Resourcepart; public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends AbstractMultiUserChatIntegrationTest{ public MultiUserChatRolesAffiliationsPrivilegesIntegrationTest(SmackIntegrationTestEnvironment environment) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, TestNotPossibleException { super(environment); } /** * Asserts that a user who undergoes a role change receives that change as a presence update * *
From XEP-0045 § 5.1.3:
** ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change * to all occupants... ** *
From XEP-0045 § 9.6:
** The service MUST then send updated presence from this individual to all occupants, indicating the addition of * moderator status... ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucRoleTestForReceivingModerator() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); final ResultSyncPoint
From XEP-0045 § 5.1.3:
** ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change * to all occupants... ** *
From XEP-0045 § 9.6:
** The service MUST then send updated presence from this individual to all occupants, indicating the addition of * moderator status... ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucRoleTestForWitnessingModerator() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); final ResultSyncPoint
From XEP-0045 § 5.1.3:
** ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change * to all occupants... ** *
From XEP-0045 § 9.7:
** The service MUST then send updated presence from this individual to all occupants, indicating the removal of * moderator status... ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucRoleTestForRemovingModerator() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); final ResultSyncPoint
From XEP-0045 § 5.1.3:
** ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change * to all occupants... ** *
From XEP-0045 § 9.7:
** The service MUST then send updated presence from this individual to all occupants, indicating the removal of * moderator status... ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucRoleTestForWitnessingModeratorRemoval() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); final ResultSyncPoint
From XEP-0045 § 5.1.3:
** ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change * to all occupants... ** *
From XEP-0045 § 8.4:
** The service MUST then send updated presence from this individual to all occupants, indicating the removal of * voice privileges... ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucRoleTestForRevokingVoice() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); final ResultSyncPoint
From XEP-0045 § 5.1.3:
** ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change * to all occupants... ** *
From XEP-0045 § 8.4:
** The service MUST then send updated presence from this individual to all occupants, indicating the removal of * voice privileges... ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucRoleTestForWitnessingRevokingVoice() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); final ResultSyncPoint
From XEP-0045 § 5.2.2:
** ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that * to all occupants... ** *
From XEP-0045 § 10.6:
** If the user is in the room, the service MUST then send updated presence from this individual to all occupants, * indicating the granting of admin status... ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucAffiliationTestForReceivingAdmin() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); final ResultSyncPoint
From XEP-0045 § 5.2.2:
** ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that * to all occupants... ** *
From XEP-0045 § 10.6:
** If the user is in the room, the service MUST then send updated presence from this individual to all occupants, * indicating the granting of admin status... ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucAffiliationTestForWitnessingAdmin() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); final ResultSyncPoint
From XEP-0045 § 5.2.2:
** ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that to * all occupants... ** *
From XEP-0045 § 10.7:
** If the user is in the room, the service MUST then send updated presence from this individual to all occupants, * indicating the loss of admin status by sending a presence element... ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucAffiliationTestForRemovingAdmin() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); final ResultSyncPoint
From XEP-0045 § 5.2.2:
** ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that to * all occupants... ** *
From XEP-0045 § 10.7:
** If the user is in the room, the service MUST then send updated presence from this individual to all occupants, * indicating the loss of admin status by sending a presence element... ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucAffiliationTestForWitnessingAdminRemoval() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); final ResultSyncPoint
From XEP-0045 § 8.2:
** The kick is performed based on the occupant's room nickname and is completed by setting the role of a * participant or visitor to a value of "none". * * The service MUST remove the kicked occupant by sending a presence stanza of type "unavailable" to each kicked * occupant, including status code 307 in the extended presence information, optionally along with the reason (if * provided) and the roomnick or bare JID of the user who initiated the kick. ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucPresenceTestForGettingKicked() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); createMuc(mucAsSeenByOne, "one-" + randomString); try { final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); mucAsSeenByTwo.join(nicknameTwo); final ResultSyncPoint
From XEP-0045 § 8.2:
** ...the service MUST then inform all of the remaining occupants that the kicked occupant is no longer in the room * by sending presence stanzas of type "unavailable" from the individual's roomnick (<room@service/nick>) to all * the remaining occupants (just as it does when occupants exit the room of their own volition), including the * status code and optionally the reason and actor. ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucPresenceTestForWitnessingKick() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); createMuc(mucAsSeenByOne, "one-" + randomString); try { final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); mucAsSeenByTwo.join(nicknameTwo); mucAsSeenByThree.join(nicknameThree); final ResultSyncPoint
From XEP-0045 § 5.2:
** These affiliations are long-lived in that they persist across a user's visits to the room and are not affected * by happenings in the room...Affiliations are granted, revoked, and maintained based on the user's bare JID, not * the nick as with roles. ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucTestPersistentAffiliation() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); createMuc(mucAsSeenByOne, nicknameOne); try { mucAsSeenByTwo.join(nicknameTwo); mucAsSeenByThree.join(nicknameThree); mucAsSeenByOne.grantOwnership(conTwo.getUser().asBareJid()); mucAsSeenByOne.grantAdmin(conThree.getUser().asBareJid()); mucAsSeenByTwo.leave(); mucAsSeenByThree.leave(); Presence p2 = mucAsSeenByTwo.join(nicknameTwo); Presence p3 = mucAsSeenByThree.join(nicknameThree); assertEquals(MUCAffiliation.owner, MUCUser.from(p2).getItem().getAffiliation()); assertEquals(MUCAffiliation.admin, MUCUser.from(p3).getItem().getAffiliation()); } finally { tryDestroy(mucAsSeenByOne); } } /** * Asserts that a moderator cannot revoke voice from an owner * *
From XEP-0045 § 5.1.1:
** A moderator MUST NOT be able to revoke voice privileges from an admin or owner ** *
From XEP-0045 § 8.4:
** A moderator MUST NOT be able to revoke voice from a user whose affiliation is at or above the moderator's level. * In addition, a service MUST NOT allow the voice privileges of an admin or owner to be removed by anyone. If a * moderator attempts to revoke voice privileges from such a user, the service MUST deny the request and return a * <not-allowed/> error to the sender along with the offending item(s) ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucTestModeratorCannotRevokeVoiceFromOwner() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); createModeratedMuc(mucAsSeenByOne, nicknameOne); try { mucAsSeenByTwo.join(nicknameTwo); mucAsSeenByOne.grantModerator(nicknameTwo); XMPPException.XMPPErrorException xe = assertThrows(XMPPException.XMPPErrorException.class, () -> mucAsSeenByTwo.revokeVoice(nicknameOne)); assertEquals(xe.getStanzaError().getCondition().toString(), "not-allowed"); } finally { tryDestroy(mucAsSeenByOne); } } /** * Asserts that a moderator cannot revoke moderator privileges from a moderator with a higher affiliation * than themselves. * *
From XEP-0045 § 5.1.3 and §5.2.1:
** A moderator SHOULD NOT be allowed to revoke moderation privileges from someone with a higher affiliation than * themselves (i.e., an unaffiliated moderator SHOULD NOT be allowed to revoke moderation privileges from an admin * or an owner, and an admin SHOULD NOT be allowed to revoke moderation privileges from an owner) ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucTestModeratorCannotBeRevokedFromHigherAffiliation() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); createModeratedMuc(mucAsSeenByOne, nicknameOne); try { mucAsSeenByTwo.join(nicknameTwo); mucAsSeenByThree.join(nicknameThree); mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); mucAsSeenByOne.grantModerator(nicknameThree); // Admin cannot revoke from Owner XMPPException.XMPPErrorException xe1 = assertThrows(XMPPException.XMPPErrorException.class, () -> mucAsSeenByTwo.revokeModerator(nicknameOne)); // Moderator cannot revoke from Admin XMPPException.XMPPErrorException xe2 = assertThrows(XMPPException.XMPPErrorException.class, () -> mucAsSeenByThree.revokeModerator(nicknameOne)); // Moderator cannot revoke from Owner XMPPException.XMPPErrorException xe3 = assertThrows(XMPPException.XMPPErrorException.class, () -> mucAsSeenByThree.revokeModerator(nicknameTwo)); assertEquals(xe1.getStanzaError().getCondition().toString(), "not-allowed"); assertEquals(xe2.getStanzaError().getCondition().toString(), "not-allowed"); assertEquals(xe3.getStanzaError().getCondition().toString(), "not-allowed"); } finally { tryDestroy(mucAsSeenByOne); } } /** * Asserts that an unmoderated room assigns the correct default roles for a given affiliation * *
From XEP-0045 § 5.1.2:
** ...the initial default roles that a service SHOULD set based on the user's affiliation... ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucTestDefaultRoleForAffiliationInUnmoderatedRoom() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest-unmoderatedroles"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); createMuc(mucAsSeenByOne, nicknameOne); try { mucAsSeenByTwo.join(nicknameTwo); mucAsSeenByThree.join(nicknameThree); final ResultSyncPoint
From XEP-0045 § 5.1.2:
** ...the initial default roles that a service SHOULD set based on the user's affiliation... ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucTestDefaultRoleForAffiliationInModeratedRoom() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest-moderatedroles"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); final ResultSyncPoint
From XEP-0045 § 5.1.2:
** ...the initial default roles that a service SHOULD set based on the user's affiliation... ** * @throws Exception when errors occur */ @SmackIntegrationTest public void mucTestDefaultRoleForAffiliationInMembersOnlyRoom() throws Exception { EntityBareJid mucAddress = getRandomRoom("smack-inttest-membersonlyroles"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); final EntityFullJid jidOne = JidCreate.entityFullFrom(mucAddress, nicknameOne); final EntityFullJid jidTwo = JidCreate.entityFullFrom(mucAddress, nicknameTwo); final EntityFullJid jidThree = JidCreate.entityFullFrom(mucAddress, nicknameThree); createMembersOnlyMuc(mucAsSeenByOne, nicknameOne); final ResultSyncPoint