Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A/V moderation #734

Merged
merged 26 commits into from
May 12, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b1bf86a
hack: Read Message-s with JsonMessageExtension.
bgrozev Apr 29, 2021
8a775ee
feat: Implements av moderation.
damencho Apr 29, 2021
41ed4d4
squash: Validates from address for av moderation messages.
damencho Apr 29, 2021
c603415
squash: Drop unnecessary semicolons.
damencho Apr 29, 2021
568a78e
squash: Fixes comments.
damencho Apr 29, 2021
9db7d65
squash: Fixes comments.
damencho Apr 30, 2021
1785727
squash: Move kept component address as DomainBareJid.
damencho Apr 30, 2021
e0c8ddc
squash: Move value to boolean.
damencho Apr 30, 2021
3ce1480
squash: Fixes logic around unmuting.
damencho May 1, 2021
0b2da64
squash: Allow unmutinng for moderators when av moderation is on.
damencho May 2, 2021
9db7caf
squash: Parse mediaType when enabled/disabled.
damencho May 7, 2021
3d0055f
squash: Force mute participants when av moderation is enabled.
damencho May 7, 2021
8a9fdc5
squash: Fixes post-build checks.
damencho May 11, 2021
23235ef
squash: Fixes comments.
damencho May 11, 2021
da45a70
squash: Moves AvModerationHandler to a file of its own.
damencho May 11, 2021
abee48f
fix: Fixes tests.
damencho May 11, 2021
e99631f
squash: Removes handling of actor for AV moderation.
damencho May 11, 2021
3edf5cd
squash: Fixes a log line.
damencho May 11, 2021
521bb07
squash: Fixes comments.
damencho May 11, 2021
89b3205
squash: Move discoverInfo to XmppProvider, provide a mock.
bgrozev May 11, 2021
eabca1b
feat: Handles mute state for channels.
damencho May 12, 2021
7c0faed
fix: Updates participant's channel info with mute state.
damencho May 12, 2021
da3e72e
fix: Fixes direction when updating channels.
damencho May 12, 2021
fc2d1ee
fix: Handle MuteIq and MuteVideoIq async.
bgrozev May 12, 2021
29d5b8c
squash: Remove unused import.
bgrozev May 12, 2021
4707b2c
squash: Fixes comments.
damencho May 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/main/java/org/jitsi/impl/protocol/xmpp/ChatRoom.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import org.jitsi.jicofo.*;
import org.jitsi.jicofo.xmpp.muc.*;
import org.jitsi.utils.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.packet.*;
import org.jxmpp.jid.*;
Expand Down Expand Up @@ -198,4 +199,40 @@ void join()
* Get the unique meeting ID associated by this room (set by the MUC service).
*/
String getMeetingId();

/**
* Whether A/V moderation is enabled.
* @param mediaType the media type.
* @return whether A/V moderation is enabled.
*/
boolean isAvModerationEnabled(MediaType mediaType);

/**
* Sets new value for A/V moderation.
* @param actorJid the jid of the participant performing the operation.
* @param mediaType the media type.
* @param value the new value.
*/
void setAvModerationEnabled(Jid actorJid, MediaType mediaType, boolean value);

/**
* Returns the actor that performed the last enable/disable action.
* @param mediaType the media type.
* @return the jid of the actor.
*/
Jid getAvModerationActor(MediaType mediaType);
bgrozev marked this conversation as resolved.
Show resolved Hide resolved

/**
* Updates the list of members that are allowed to unmute audio or video.
* @param whitelists a map with string keys (MediaType.AUDIO or MediaType.VIDEO).
*/
void updateAvModerationWhitelists(Map<String, List<String>> whitelists);

/**
* Checks the whitelists whether the supplied jid is allowed from a moderator to unmute.
* @param jid the jid to check.
* @param mediaType type of media for which we are checking.
* @return <tt>true</tt> if the member is allowed to unmute, false otherwise.
*/
boolean isMemberAllowedToUnmute(Jid jid, MediaType mediaType);
}
64 changes: 64 additions & 0 deletions src/main/java/org/jitsi/impl/protocol/xmpp/ChatRoomImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.jitsi.jicofo.*;
import org.jitsi.jicofo.xmpp.*;
import org.jitsi.jicofo.xmpp.muc.*;
import org.jitsi.utils.*;
import org.jitsi.utils.logging2.*;

import org.jivesoftware.smack.*;
Expand Down Expand Up @@ -139,6 +140,18 @@ public class ChatRoomImpl
*/
private String meetingId = null;

/**
* Indicates whether A/V Moderation is enabled for this room.
*/
private Map<MediaType, Boolean> avModerationEnabled = new HashMap<>();

/**
* The actor that last enabled/disabled the moderation.
*/
private Map<MediaType, Jid> avModerationActor = new HashMap<>();

private Map<String, List<String>> whitelists = new HashMap<>();

/**
* Creates new instance of <tt>ChatRoomImpl</tt>.
*
Expand Down Expand Up @@ -844,6 +857,57 @@ public void processPresence(Presence presence)
}
}

/**
* {@inheritDoc}
*/
public boolean isAvModerationEnabled(MediaType mediaType)
{
Boolean value = this.avModerationEnabled.get(mediaType);

// must be non null and true
return value != null && value;
}

/**
* {@inheritDoc}
*/
public void setAvModerationEnabled(Jid actorJid, MediaType mediaType, boolean value)
{
this.avModerationEnabled.put(mediaType, value);
this.avModerationActor.put(mediaType, actorJid);
}

/**
* {@inheritDoc}
*/
public Jid getAvModerationActor(MediaType mediaType)
{
return this.avModerationActor.get(mediaType);
}

/**
* {@inheritDoc}
*/
public void updateAvModerationWhitelists(@NotNull Map<String, List<String>> whitelists)
{
this.whitelists = whitelists;
}

/**
* {@inheritDoc}
*/
public boolean isMemberAllowedToUnmute(Jid jid, MediaType mediaType)
{
if (!this.isAvModerationEnabled(mediaType))
{
return true;
}

List<String> whitelist = this.whitelists.get(mediaType.toString());
bgrozev marked this conversation as resolved.
Show resolved Hide resolved

return whitelist == null ? false : whitelist.contains(jid.toString());
}

class MemberListener
implements ParticipantStatusListener
{
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/jitsi/jicofo/JitsiMeetConference.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.jitsi.jicofo.jibri.*;
import org.jitsi.jicofo.xmpp.*;
import org.jitsi.jicofo.xmpp.muc.*;
import org.jitsi.utils.*;
import org.jitsi.xmpp.extensions.jibri.*;
import org.jxmpp.jid.*;

Expand Down Expand Up @@ -107,6 +108,12 @@ default JibriSipGateway getJibriSipGateway()
*/
@NotNull IqProcessingResult handleJibriRequest(@NotNull IqRequest<JibriIq> request);

/**
* Used for av moderation, when we want to mute all participants that are not moderators.
* @param mediaType the media type we want to mute.
*/
void muteAllNonModeratorParticipants(MediaType mediaType);

/**
* Return {@code true} if the user with the given JID should be allowed to invite jigasi to this conference.
*/
Expand Down
89 changes: 84 additions & 5 deletions src/main/java/org/jitsi/jicofo/JitsiMeetConferenceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import java.util.logging.*;
import java.util.stream.*;

import static org.jitsi.jicofo.JitsiMeetConferenceImpl.MuteResult.*;
import static org.jitsi.jicofo.xmpp.IqProcessingResult.*;

/**
Expand Down Expand Up @@ -2102,7 +2103,8 @@ public MuteResult handleMuteRequest(Jid muterJid, Jid toBeMutedJid, boolean doMu
return MuteResult.ERROR;
}
// Only moderators can mute others
if (!muterJid.equals(toBeMutedJid) && !muter.getChatMember().getRole().hasModeratorRights())
boolean isMuterModerator = muter.getChatMember().getRole().hasModeratorRights();
if (!muterJid.equals(toBeMutedJid) && !isMuterModerator)
{
logger.warn("Mute not allowed for non-moderator " + muterJid);
return MuteResult.NOT_ALLOWED;
Expand All @@ -2115,11 +2117,25 @@ public MuteResult handleMuteRequest(Jid muterJid, Jid toBeMutedJid, boolean doMu
return MuteResult.ERROR;
}

// do not allow unmuting other participants even for the moderator
if (!doMute && !muterJid.equals(toBeMutedJid))
// process unmuting
if (!doMute)
{
logger.warn("Unmute now allowed, mutedJid=" + muterJid + ", toBeMutedJid=" + toBeMutedJid);
return MuteResult.NOT_ALLOWED;
// do not allow unmuting other participants even for the moderator
if (!muterJid.equals(toBeMutedJid))
{
logger.warn("Unmute not allowed, muterJid=" + muterJid + ", toBeMutedJid=" + toBeMutedJid);
return MuteResult.NOT_ALLOWED;
}
else if (this.chatRoom.isAvModerationEnabled(mediaType))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This first check is redundant, since isMemberAllowedToUnmute performs it.

{
// when A/V moderation is enabled we need to check the whitelists if participant is not moderator
if (!isMuterModerator && !this.chatRoom.isMemberAllowedToUnmute(toBeMutedJid, mediaType))
{
logger.warn("Unmute not allowed due to av moderation, muterJid=" + muterJid
bgrozev marked this conversation as resolved.
Show resolved Hide resolved
+ ", toBeMutedJid=" + toBeMutedJid);
return MuteResult.NOT_ALLOWED;
}
}
}

if (doMute
Expand Down Expand Up @@ -2154,6 +2170,69 @@ public MuteResult handleMuteRequest(Jid muterJid, Jid toBeMutedJid, boolean doMu
return succeeded ? MuteResult.SUCCESS : MuteResult.ERROR;
}

/**
* {@inheritDoc}
*/
public void muteAllNonModeratorParticipants(MediaType mediaType)
{
for (Participant participant : participants)
bgrozev marked this conversation as resolved.
Show resolved Hide resolved
{
muteParticipant(participant, mediaType);
}
}

/**
* Mutes a participant.
* @param participant the participant to mute and is not moderator and hasn't been already muted.
bgrozev marked this conversation as resolved.
Show resolved Hide resolved
* @param mediaType the media type for the operation.
*/
public void muteParticipant(Participant participant, MediaType mediaType)
{
boolean isMuterModerator = participant.getChatMember().getRole().hasModeratorRights();
bgrozev marked this conversation as resolved.
Show resolved Hide resolved
if (isMuterModerator || participant.isInitialAVModerationApplied(mediaType))
{
return;
}

Jid muterJid = this.chatRoom.getAvModerationActor(mediaType);

MuteResult result = handleMuteRequest(muterJid, participant.getMucJid(), true, mediaType);
if (result == SUCCESS)
{
// let's mark that this participant was muted
participant.setInitialAVModerationApplied(mediaType);

IQ muteIq = null;
if (mediaType == MediaType.AUDIO)
{
MuteIq muteStatusUpdate = new MuteIq();
muteStatusUpdate.setActor(muterJid);
muteStatusUpdate.setType(IQ.Type.set);
muteStatusUpdate.setTo(participant.getMucJid());

muteStatusUpdate.setMute(true);

muteIq = muteStatusUpdate;
}
else if (mediaType == MediaType.VIDEO)
{
MuteVideoIq muteStatusUpdate = new MuteVideoIq();
muteStatusUpdate.setActor(muterJid);
muteStatusUpdate.setType(IQ.Type.set);
muteStatusUpdate.setTo(participant.getMucJid());

muteStatusUpdate.setMute(true);

muteIq = muteStatusUpdate;
}

if (muteIq != null)
{
UtilKt.tryToSendStanza(clientXmppProvider.getXmppConnection(), muteIq);
}
}
}

/**
* Returns current participants count. A participant is chat member who has
* some videobridge and media state assigned(not just raw chat room member).
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/org/jitsi/jicofo/Participant.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import org.jetbrains.annotations.*;
import org.jitsi.impl.protocol.xmpp.*;
import org.jitsi.utils.*;
import org.jitsi.xmpp.extensions.colibri.*;
import org.jitsi.xmpp.extensions.jingle.*;

Expand Down Expand Up @@ -97,6 +98,11 @@ public static String getEndpointId(ChatRoomMember chatRoomMember)
*/
private List<String> supportedFeatures = new ArrayList<>();

/**
* State whether this participant was
*/
private Map<MediaType, Boolean> initialAVModerationApplied = new HashMap<>();

/**
* Creates new {@link Participant} for given chat room member.
*
Expand Down Expand Up @@ -498,6 +504,26 @@ JitsiMeetConferenceImpl.BridgeSession terminateBridgeSession()
return _session;
}

/**
* Changes the value for the supplied media type.
* @param mediaType the media type to change.
*/
public void setInitialAVModerationApplied(MediaType mediaType)
{
this.initialAVModerationApplied.put(mediaType, true);
}

/**
* Checks whether the initial moderation is applied.
* @param mediaType the media type to check.
* @return tru if it was applied.
*/
public boolean isInitialAVModerationApplied(MediaType mediaType)
{
Boolean value = this.initialAVModerationApplied.get(mediaType);
return value != null && value;
}

@Override
public String toString()
{
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/org/jitsi/jicofo/ParticipantChannelAllocator.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
import org.jitsi.jicofo.codec.*;
import org.jitsi.jicofo.xmpp.*;
import org.jitsi.protocol.xmpp.colibri.exception.*;
import org.jitsi.utils.*;
import org.jitsi.xmpp.extensions.colibri.*;
import org.jitsi.xmpp.extensions.jingle.*;
import org.jitsi.xmpp.extensions.jingle.JingleUtils;
import org.jitsi.xmpp.extensions.jitsimeet.*;
import org.jitsi.jicofo.discovery.*;
import org.jitsi.protocol.xmpp.*;
import org.jitsi.protocol.xmpp.util.*;
import org.jitsi.utils.logging2.*;
Expand Down Expand Up @@ -166,6 +166,17 @@ else if (reInvite)
participant.getSourcesCopy(),
participant.getSourceGroupsCopy());
}

// if participant is not av moderated but we need to let's do it
if (meetConference.getChatRoom().isAvModerationEnabled(MediaType.AUDIO))
bgrozev marked this conversation as resolved.
Show resolved Hide resolved
{
meetConference.muteParticipant(participant, MediaType.AUDIO);
}

if (meetConference.getChatRoom().isAvModerationEnabled(MediaType.VIDEO))
{
meetConference.muteParticipant(participant, MediaType.VIDEO);
}
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/main/kotlin/org/jitsi/jicofo/xmpp/Smack.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import org.jitsi.xmpp.extensions.jingle.JingleIQProvider
import org.jitsi.xmpp.extensions.jitsimeet.BridgeSessionPacketExtension
import org.jitsi.xmpp.extensions.jitsimeet.ConferenceIqProvider
import org.jitsi.xmpp.extensions.jitsimeet.IceStatePacketExtension
import org.jitsi.xmpp.extensions.jitsimeet.JsonMessageExtension
import org.jitsi.xmpp.extensions.jitsimeet.LoginUrlIqProvider
import org.jitsi.xmpp.extensions.jitsimeet.LogoutIqProvider
import org.jitsi.xmpp.extensions.jitsimeet.RegionPacketExtension
Expand Down Expand Up @@ -120,5 +121,10 @@ fun registerXmppExtensions() {
JingleIQ.NAMESPACE,
JingleIQProvider()
)
ProviderManager.addExtensionProvider(
JsonMessageExtension.ELEMENT_NAME,
JsonMessageExtension.NAMESPACE,
DefaultPacketExtensionProvider(JsonMessageExtension::class.java)
)
RayoIqProvider().registerRayoIQs()
}
Loading