diff --git a/VideoWeb/VideoWeb.Common/Models/Conference.cs b/VideoWeb/VideoWeb.Common/Models/Conference.cs
index 20fe593f7f..cd1e27ad96 100644
--- a/VideoWeb/VideoWeb.Common/Models/Conference.cs
+++ b/VideoWeb/VideoWeb.Common/Models/Conference.cs
@@ -119,11 +119,6 @@ public void AddParticipant(Participant participant)
Participants.Add(participant);
}
}
-
- public void RemoveParticipant(Guid referenceId)
- {
- Participants.RemoveAll(x => x.RefId == referenceId);
- }
public bool IsParticipantInConference(string username)
{
@@ -164,11 +159,79 @@ private CivilianRoom GetOrCreateCivilianRoom(long roomId)
return room;
}
- public CivilianRoom GetRoom(Guid participantId)
+ ///
+ /// Check if any of the participants or endpoints from the given list are screened from each other.
+ /// If any of the externalReferenceIds for an entity exist in the protectFrom list of another entity, they
+ /// cannot be in a consultation together.
+ ///
+ /// List of participants to be in a consultation
+ /// List of endpoints to be in a consultation
+ public bool AreEntitiesScreenedFromEachOther(List participantIds, List endpointIds)
+ {
+ var allExternalReferenceIds = Participants.Select(x => x.ExternalReferenceId)
+ .Union(Endpoints.Select(x => x.ExternalReferenceId)).ToList();
+
+ foreach (var participantId in participantIds)
+ {
+ var participant = Participants.Find(x => x.Id == participantId);
+ if (participant == null) continue;
+ if (participant.ProtectFrom.Exists(allExternalReferenceIds.Contains)) return true;
+ }
+
+ foreach (var endpointId in endpointIds)
+ {
+ var endpoint = Endpoints.Find(x => x.Id == endpointId);
+ if (endpoint == null) continue;
+ if (endpoint.ProtectFrom.Exists(allExternalReferenceIds.Contains)) return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Check if the participant is screened from any of the entities in a existing consultation room
+ ///
+ /// The label of the consultation room
+ /// The id of the participant attempting to join a consultation room
+ /// true if the participant is not screened from any of the entities in the room
+ public bool CanParticipantJoinConsultationRoom(string roomLabel, Guid participantId)
+ {
+ var participant = Participants.Find(x => x.Id == participantId);
+ if (participant == null)
+ throw new ArgumentException($"The participant {participantId} does not exist", nameof(participantId));
+
+ var ids = GetParticipantsAndEndpointsInRoom(roomLabel);
+ ids.participantIds.Add(participantId);
+
+ return !AreEntitiesScreenedFromEachOther(ids.participantIds, ids.endpointIds);
+ }
+
+ ///
+ /// Check if the endpoint is screened from any of the entities in a existing consultation room
+ ///
+ /// The label of the consultation room
+ /// The id of the endpoint attempting to join a consultation room
+ /// true if the endpoint is not screened from any of the entities in the room
+ public bool CanEndpointJoinConsultationRoom(string roomLabel, Guid endpointId)
+ {
+ var endpoint = Endpoints.Find(x => x.Id == endpointId);
+ if (endpoint == null)
+ throw new ArgumentException($"The endpoint {endpointId} does not exist", nameof(endpointId));
+
+ var ids = GetParticipantsAndEndpointsInRoom(roomLabel);
+ ids.endpointIds.Add(endpointId);
+
+ return !AreEntitiesScreenedFromEachOther(ids.participantIds, ids.endpointIds);
+ }
+
+ private (List participantIds, List endpointIds) GetParticipantsAndEndpointsInRoom(string roomLabel)
{
- return CivilianRooms.Find(room => room.Participants.Contains(participantId));
+ var participants = Participants.Where(x => x.CurrentRoom?.Label == roomLabel).Select(x => x.Id).ToList();
+ var endpoints = Endpoints.Where(x => x.CurrentRoom?.Label == roomLabel).Select(x => x.Id).ToList();
+ return (participants, endpoints);
}
+
public HearingLayout GetRecommendedLayout()
{
var numOfParticipantsIncJudge = Participants.Count + Endpoints.Count;
@@ -242,5 +305,7 @@ private List GetNonScreenedEndpoints()
return endpoints;
}
+
+
}
}
diff --git a/VideoWeb/VideoWeb.Contract/Request/StartPrivateConsultationRequest.cs b/VideoWeb/VideoWeb.Contract/Request/StartPrivateConsultationRequest.cs
index f38bb7caa7..016fd920b2 100644
--- a/VideoWeb/VideoWeb.Contract/Request/StartPrivateConsultationRequest.cs
+++ b/VideoWeb/VideoWeb.Contract/Request/StartPrivateConsultationRequest.cs
@@ -6,9 +6,9 @@ namespace VideoWeb.Contract.Request
{
public class StartPrivateConsultationRequest
{
- public Guid[] InviteParticipants { get; set; }
+ public Guid[] InviteParticipants { get; set; } = [];
- public Guid[] InviteEndpoints { get; set; }
+ public Guid[] InviteEndpoints { get; set; } = [];
public Guid ConferenceId { get; set; }
diff --git a/VideoWeb/VideoWeb.UnitTests/Builders/ConferenceCacheModelBuilder.cs b/VideoWeb/VideoWeb.UnitTests/Builders/ConferenceCacheModelBuilder.cs
index 111b6ccff6..cbb29235d9 100644
--- a/VideoWeb/VideoWeb.UnitTests/Builders/ConferenceCacheModelBuilder.cs
+++ b/VideoWeb/VideoWeb.UnitTests/Builders/ConferenceCacheModelBuilder.cs
@@ -24,22 +24,32 @@ public ConferenceCacheModelBuilder()
.Build(),
Builder.CreateNew().With(x => x.Role = Role.Individual)
.With(x => x.Username = Faker.Internet.Email())
- .With(x => x.Id = Guid.NewGuid()).Build(),
+ .With(x => x.Id = Guid.NewGuid())
+ .With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder.CreateNew().With(x => x.Role = Role.Representative)
.With(x => x.Username = Faker.Internet.Email())
- .With(x => x.Id = Guid.NewGuid()).Build(),
+ .With(x => x.Id = Guid.NewGuid())
+ .With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder.CreateNew().With(x => x.Role = Role.Individual)
.With(x => x.Username = Faker.Internet.Email())
- .With(x => x.Id = Guid.NewGuid()).Build(),
+ .With(x => x.Id = Guid.NewGuid())
+ .With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder.CreateNew().With(x => x.Role = Role.Representative)
.With(x => x.Username = Faker.Internet.Email())
- .With(x => x.Id = Guid.NewGuid()).Build()
+ .With(x => x.Id = Guid.NewGuid())
+ .With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build()
},
Endpoints = new List
{
- Builder.CreateNew().With(x => x.Id = Guid.NewGuid()).With(x => x.DisplayName = "EP1")
+ Builder.CreateNew()
+ .With(x => x.Id = Guid.NewGuid())
+ .With(x => x.DisplayName = "EP1")
+ .With(x => x.ExternalReferenceId = Guid.NewGuid().ToString())
.Build(),
- Builder.CreateNew().With(x => x.Id = Guid.NewGuid()).With(x => x.DisplayName = "EP2")
+ Builder.CreateNew()
+ .With(x => x.Id = Guid.NewGuid())
+ .With(x => x.DisplayName = "EP2")
+ .With(x => x.ExternalReferenceId = Guid.NewGuid().ToString())
.Build()
},
HearingVenueName = "Hearing Venue Test"
diff --git a/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/AddEndpointToConsultationTests.cs b/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/AddEndpointToConsultationTests.cs
index 37319a15e5..b8cbef31bc 100644
--- a/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/AddEndpointToConsultationTests.cs
+++ b/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/AddEndpointToConsultationTests.cs
@@ -53,7 +53,7 @@ public async Task should_return_accepted()
var request = new AddEndpointConsultationRequest
{
ConferenceId = _testConference.Id,
- EndpointId = Guid.NewGuid(),
+ EndpointId = _testConference.Endpoints[0].Id,
RoomLabel = "RoomLabel"
};
@@ -81,6 +81,36 @@ public async Task should_return_unauthorised_not_in_conference()
result.Should().BeOfType();
}
+ [Test]
+ public async Task should_return_forbidden_when_endpoint_is_screened()
+ {
+ // arrange
+ var roomLabel = "RoomLabel1";
+ var individual = _testConference.Participants.Find(x => x.Role == Role.Individual);
+ var endpoint = _testConference.Endpoints[0];
+ individual.ProtectFrom.Add(endpoint.ExternalReferenceId);
+ var rep = _testConference.Participants.Find(x => x.Role == Role.Representative);
+ individual.CurrentRoom = new ConsultationRoom {Label = roomLabel};
+ rep.CurrentRoom = new ConsultationRoom {Label = roomLabel};
+
+ SetupControllerWithClaims(rep.Username);
+ var request = new AddEndpointConsultationRequest
+ {
+ ConferenceId = _testConference.Id,
+ EndpointId = endpoint.Id,
+ RoomLabel = roomLabel
+ };
+
+ // act
+ var result = await _sut.AddEndpointToConsultationAsync(request, CancellationToken.None);
+
+ // assert
+ result.Should().BeOfType();
+
+ _mocker.Mock().Verify(
+ x => x.JoinEndpointToConsultationAsync(It.IsAny(), It.IsAny()), Times.Never);
+ }
+
[Test]
public async Task should_return_call_JoinEndpointToConsultationAsync_with_correct_params()
{
@@ -89,7 +119,7 @@ public async Task should_return_call_JoinEndpointToConsultationAsync_with_correc
var request = new AddEndpointConsultationRequest
{
ConferenceId = _testConference.Id,
- EndpointId = Guid.NewGuid(),
+ EndpointId = _testConference.Endpoints[0].Id,
RoomLabel = "RoomLabel"
};
@@ -115,7 +145,7 @@ public async Task should_return_call_videoapi_response_error_joining()
var request = new AddEndpointConsultationRequest
{
ConferenceId = _testConference.Id,
- EndpointId = Guid.NewGuid(),
+ EndpointId = _testConference.Endpoints[0].Id,
RoomLabel = "RoomLabel"
};
var apiException = new VideoApiException("Bad Request", (int) HttpStatusCode.BadRequest,
diff --git a/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/ConsultationHelper.cs b/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/ConsultationHelper.cs
index 4d24b044c4..fd7a4ec2a2 100644
--- a/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/ConsultationHelper.cs
+++ b/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/ConsultationHelper.cs
@@ -23,14 +23,18 @@ public static Conference BuildConferenceForTest()
.With(x => x.Username = "judge@hmcts.net")
.Build(),
Builder.CreateNew().With(x => x.Role = Role.Individual)
- .With(x => x.Id = Guid.NewGuid()).With(x => x.Username = "john@hmcts.net").Build(),
+ .With(x => x.Id = Guid.NewGuid()).With(x => x.Username = "john@hmcts.net")
+ .With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder.CreateNew().With(x => x.Role = Role.Representative)
.With(x=> x.Username = "rep1@hmcts.net")
- .With(x => x.Id = Guid.NewGuid()).Build(),
+ .With(x => x.Id = Guid.NewGuid())
+ .With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder.CreateNew().With(x => x.Role = Role.Individual)
- .With(x => x.Id = Guid.NewGuid()).Build(),
+ .With(x => x.Id = Guid.NewGuid())
+ .With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder.CreateNew().With(x => x.Role = Role.Representative)
- .With(x => x.Id = Guid.NewGuid()).Build(),
+ .With(x => x.Id = Guid.NewGuid())
+ .With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder.CreateNew()
.With(x => x.Role = Role.StaffMember).With(x => x.Id = Guid.NewGuid())
.With(x => x.Username = "staffMember@hmcts.net")
@@ -39,11 +43,14 @@ public static Conference BuildConferenceForTest()
Endpoints = new List
{
Builder.CreateNew().With(x => x.Id = Guid.NewGuid()).With(x => x.DisplayName = "EP1")
- .With(x=> x.DefenceAdvocateUsername = "rep1@hmcts.net").Build(),
+ .With(x=> x.DefenceAdvocateUsername = "rep1@hmcts.net")
+ .With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder.CreateNew().With(x => x.Id = Guid.NewGuid()).With(x => x.DisplayName = "EP2")
- .With(x=> x.DefenceAdvocateUsername = "john@hmcts.net").Build(),
+ .With(x=> x.DefenceAdvocateUsername = "john@hmcts.net")
+ .With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder.CreateNew().With(x => x.Id = Guid.NewGuid()).With(x => x.DisplayName = "EP3")
- .With(x=> x.DefenceAdvocateUsername = "john@hmcts.net").Build(),
+ .With(x=> x.DefenceAdvocateUsername = "john@hmcts.net")
+ .With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
}
};
}
diff --git a/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/InviteToConsultationTests.cs b/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/InviteToConsultationTests.cs
index cf1f1a69cd..749e666ed5 100644
--- a/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/InviteToConsultationTests.cs
+++ b/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/InviteToConsultationTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
@@ -96,11 +97,12 @@ public async Task should_return_accepted_if_user_is_in_consultation()
var cp = new ClaimsPrincipalBuilder().WithRole(AppRoles.RepresentativeRole)
.WithUsername("rep1@hmcts.net").Build();
_sut = SetupControllerWithClaims(cp);
+ var participant = _testConference.Participants.First(x => x.Role == Role.Individual);
var request = new InviteToConsultationRequest
{
ConferenceId = _testConference.Id,
- ParticipantId = _testConference.Participants[0].Id,
+ ParticipantId = participant.Id,
RoomLabel = "Room1"
};
@@ -112,7 +114,42 @@ public async Task should_return_accepted_if_user_is_in_consultation()
_mocker.Mock()
.Verify(
x => x.NotifyConsultationRequestAsync(_testConference, "Room1", _testConference.Participants[2].Id,
- _testConference.Participants[0].Id), Times.Once);
+ participant.Id), Times.Once);
+ }
+
+ [Test]
+ public async Task should_return_forbidden_if_invitee_is_screened_from_existing_participant_in_room()
+ {
+ // arrange
+ var cp = new ClaimsPrincipalBuilder().WithRole(AppRoles.RepresentativeRole)
+ .WithUsername("rep1@hmcts.net").Build();
+ _sut = SetupControllerWithClaims(cp);
+
+ var roomLabel = "RoomLabel1";
+ var individual = _testConference.Participants.Find(x => x.Role == Role.Individual);
+ var endpoint = _testConference.Endpoints[0];
+ individual.ProtectFrom.Add(endpoint.ExternalReferenceId);
+ var rep = _testConference.Participants.Find(x => x.Role == Role.Representative);
+
+ rep.CurrentRoom = new ConsultationRoom {Label = roomLabel};
+ rep.Username = "rep1@hmcts.net";
+ endpoint.CurrentRoom = new ConsultationRoom {Label = roomLabel};
+
+ var request = new InviteToConsultationRequest
+ {
+ ConferenceId = _testConference.Id,
+ RoomLabel = roomLabel,
+ ParticipantId = individual.Id
+ };
+
+ // act
+ var result = await _sut.InviteToConsultationAsync(request, CancellationToken.None);
+
+ // assert
+ result.Should().BeOfType();
+
+ _mocker.Mock().Verify(
+ x => x.NotifyConsultationRequestAsync(_testConference, roomLabel, Guid.Empty, individual.Id), Times.Never);
}
private ConsultationsController SetupControllerWithClaims(ClaimsPrincipal claimsPrincipal)
diff --git a/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/JoinPrivateConsultationTests.cs b/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/JoinPrivateConsultationTests.cs
index af965af7a7..6629c88c1c 100644
--- a/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/JoinPrivateConsultationTests.cs
+++ b/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/JoinPrivateConsultationTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Autofac.Extras.Moq;
@@ -133,6 +134,59 @@ public async Task JoinPrivateConsultation_ParticipantNotFound_ReturnNotFound()
y.RequestedFor == expectedParticipantId &&
y.RoomLabel == expectedRoomLabel)), Times.Never);
}
+
+ [Test]
+ public async Task JoinPrivateConsultation_ParticipantScreened_ReturnForbidden()
+ {
+ // Arrange
+ var expectedRoomLabel = "ExpectedRoomLabel";
+
+ var conference = new ConferenceCacheModelBuilder()
+ .Build();
+ var nonHostParticipants = conference.Participants.Where(x => !x.IsHost()).ToList();
+ var participant = nonHostParticipants[0];
+ participant.Username = ClaimsPrincipalBuilder.Username;
+
+ var patInRoom = nonHostParticipants[1];
+ var endpoint = conference.Endpoints[0];
+
+ participant.ProtectFrom.Add(endpoint.ExternalReferenceId);
+
+ var consultationRoom = new ConsultationRoom { Label = expectedRoomLabel };
+ conference.ConsultationRooms.Add(consultationRoom);
+ patInRoom.CurrentRoom = consultationRoom;
+ endpoint.CurrentRoom = consultationRoom;
+
+ var expectedConferenceId = conference.Id;
+ var expectedParticipantId = participant.Id;
+
+ _mocker.Mock().Setup(x => x.GetConference(It.Is(y => y == expectedConferenceId), It.IsAny())).ReturnsAsync(conference);
+
+ JoinPrivateConsultationRequest request = new JoinPrivateConsultationRequest()
+ {
+ ConferenceId = expectedConferenceId,
+ ParticipantId = expectedParticipantId,
+ RoomLabel = expectedRoomLabel
+ };
+
+ // Act
+ var result = await _consultationsController.JoinPrivateConsultation(request, CancellationToken.None);
+
+ // Assert
+ result.Should().BeAssignableTo();
+
+ _mocker.Mock().Verify(x => x.NotifyParticipantTransferring(
+ It.Is(c => c == conference),
+ It.Is(id => id == expectedParticipantId),
+ It.Is(room => room == expectedRoomLabel)), Times.Never);
+
+ _mocker.Mock().Verify(x => x.RespondToConsultationRequestAsync(It.Is(y =>
+ y.Answer == ConsultationAnswer.Accepted &&
+ y.ConferenceId == expectedConferenceId &&
+ y.RequestedBy == expectedParticipantId &&
+ y.RequestedFor == expectedParticipantId &&
+ y.RoomLabel == expectedRoomLabel)), Times.Never);
+ }
[Test]
public async Task JoinPrivateConsultation_VideoApiException_ReturnStatusCode()
diff --git a/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/StartConsultationTest.cs b/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/StartConsultationTest.cs
index 1b260f09a7..8e278edf97 100644
--- a/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/StartConsultationTest.cs
+++ b/VideoWeb/VideoWeb.UnitTests/Controllers/ConsultationController/StartConsultationTest.cs
@@ -21,6 +21,7 @@
using VideoApi.Contract.Responses;
using VideoApi.Contract.Requests;
using VideoWeb.Common;
+using VideoWeb.Contract.Enums;
using VideoWeb.EventHub.Services;
using VideoWeb.UnitTests.Builders;
@@ -227,6 +228,34 @@ await _controller.StartConsultationAsync(
var typedResult = (StatusCodeResult) result;
typedResult.StatusCode.Should().Be((int) HttpStatusCode.BadRequest);
}
+
+ [Test]
+ public async Task Should_return_forbidden_when_participants_are_screened()
+ {
+ // arrange
+ var individual = _testConference.Participants.Find(x => x.Role == Role.Individual);
+ var endpoint = _testConference.Endpoints[0];
+ individual.ProtectFrom.Add(endpoint.ExternalReferenceId);
+ var rep = _testConference.Participants.Find(x => x.Role == Role.Representative);
+
+ var request = new StartPrivateConsultationRequest()
+ {
+ ConferenceId = _testConference.Id,
+ RequestedBy = rep.Id,
+ RoomType = VirtualCourtRoomType.Participant,
+ InviteEndpoints = [endpoint.Id],
+ InviteParticipants = [individual.Id, rep.Id]
+ };
+
+ // act
+ var result = await _controller.StartConsultationAsync(request, CancellationToken.None);
+
+ // assert
+ result.Should().BeOfType();
+
+ _mocker.Mock()
+ .Verify(x => x.StartPrivateConsultationAsync(It.IsAny(), It.IsAny()), Times.Never);
+ }
[Test]
public async Task Should_return_exception()
diff --git a/VideoWeb/VideoWeb.UnitTests/Models/Conference/AreEntitiesScreenedFromEachOtherTests.cs b/VideoWeb/VideoWeb.UnitTests/Models/Conference/AreEntitiesScreenedFromEachOtherTests.cs
new file mode 100644
index 0000000000..bb1d0a5e93
--- /dev/null
+++ b/VideoWeb/VideoWeb.UnitTests/Models/Conference/AreEntitiesScreenedFromEachOtherTests.cs
@@ -0,0 +1,55 @@
+using System.Linq;
+using FluentAssertions;
+using NUnit.Framework;
+using VideoWeb.Common.Models;
+using VideoWeb.UnitTests.Builders;
+
+namespace VideoWeb.UnitTests.Models.Conference;
+
+public class AreEntitiesScreenedFromEachOtherTests
+{
+ [Test]
+ public void should_return_false_if_no_participants_or_endpoints_have_screening_requirements()
+ {
+ var conference = new ConferenceCacheModelBuilder()
+ .Build();
+
+ var participantIds = conference.Participants.Select(x => x.Id).ToList();
+ var endpointIds = conference.Endpoints.Select(x => x.Id).ToList();
+
+ conference.AreEntitiesScreenedFromEachOther(participantIds, endpointIds).Should().BeFalse();
+ }
+
+ [Test]
+ public void should_return_true_if_any_participant_is_to_be_screened_from_another_entity_in_the_consultation_invite_list()
+ {
+ var conference = new ConferenceCacheModelBuilder()
+ .Build();
+
+ var participant = conference.Participants.Find(x => x.Role == Role.Individual);
+ var endpoint = conference.Endpoints[0];
+ participant.ProtectFrom.Add(endpoint.ExternalReferenceId);
+
+ var participantIds = conference.Participants.Select(x => x.Id).ToList();
+ var endpointIds = conference.Endpoints.Select(x => x.Id).ToList();
+
+ conference.AreEntitiesScreenedFromEachOther(participantIds, endpointIds).Should().BeTrue();
+ }
+
+ [Test]
+ public void
+ should_return_true_if_any_endpoint_is_to_be_screened_from_another_entity_in_the_consultation_invite_list()
+ {
+ var conference = new ConferenceCacheModelBuilder()
+ .Build();
+
+ var participant = conference.Participants.Find(x => x.Role == Role.Individual);
+ var endpoint = conference.Endpoints[0];
+ participant.ProtectFrom.Add(endpoint.ExternalReferenceId);
+
+ var participantIds = conference.Participants.Select(x => x.Id).ToList();
+ var endpointIds = conference.Endpoints.Select(x => x.Id).ToList();
+
+ conference.AreEntitiesScreenedFromEachOther(participantIds, endpointIds).Should().BeTrue();
+ }
+}
diff --git a/VideoWeb/VideoWeb.UnitTests/Models/Conference/CanEndpointJoinConsultationRoomTests.cs b/VideoWeb/VideoWeb.UnitTests/Models/Conference/CanEndpointJoinConsultationRoomTests.cs
new file mode 100644
index 0000000000..c28848d819
--- /dev/null
+++ b/VideoWeb/VideoWeb.UnitTests/Models/Conference/CanEndpointJoinConsultationRoomTests.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Linq;
+using FluentAssertions;
+using NUnit.Framework;
+using VideoWeb.Common.Models;
+using VideoWeb.UnitTests.Builders;
+
+namespace VideoWeb.UnitTests.Models.Conference;
+
+public class CanEndpointJoinConsultationRoomTests
+{
+ [Test]
+ public void ReturnsTrue_WhenEndpointCanJoin()
+ {
+ // Arrange
+ var conference = new ConferenceCacheModelBuilder()
+ .Build();
+ var nonHostParticipants = conference.Participants.Where(x => !x.IsHost()).ToList();
+ var endpoint = conference.Endpoints[0];
+
+ var patInRoom = nonHostParticipants[1];
+ var endpointInRoom = conference.Endpoints[1];
+
+ var roomLabel = "Room1";
+ var consultationRoom = new ConsultationRoom { Label = roomLabel };
+ conference.ConsultationRooms.Add(consultationRoom);
+ patInRoom.CurrentRoom = consultationRoom;
+ endpointInRoom.CurrentRoom = consultationRoom;
+
+ // Act
+ var result = conference.CanEndpointJoinConsultationRoom(roomLabel, endpoint.Id);
+
+ // Assert
+ result.Should().BeTrue();
+ }
+
+ [Test]
+ public void ReturnFalse_WhenEndpointCannotJoin()
+ {
+ // Arrange
+ var conference = new ConferenceCacheModelBuilder()
+ .Build();
+ var nonHostParticipants = conference.Participants.Where(x => !x.IsHost()).ToList();
+
+ var endpoint = conference.Endpoints[0];
+
+ var patInRoom = nonHostParticipants[1];
+ var endpointInRoom = conference.Endpoints[1];
+
+ patInRoom.ProtectFrom.Add(endpointInRoom.ExternalReferenceId);
+
+ var roomLabel = "Room1";
+ var consultationRoom = new ConsultationRoom { Label = roomLabel };
+ conference.ConsultationRooms.Add(consultationRoom);
+ patInRoom.CurrentRoom = consultationRoom;
+ endpointInRoom.CurrentRoom = consultationRoom;
+
+ // Act
+ var result = conference.CanEndpointJoinConsultationRoom(roomLabel, endpoint.Id);
+
+ // Assert
+ result.Should().BeFalse();
+ }
+}
diff --git a/VideoWeb/VideoWeb.UnitTests/Models/Conference/CanParticipantJoinConsultationRoomTests.cs b/VideoWeb/VideoWeb.UnitTests/Models/Conference/CanParticipantJoinConsultationRoomTests.cs
new file mode 100644
index 0000000000..7562774cc5
--- /dev/null
+++ b/VideoWeb/VideoWeb.UnitTests/Models/Conference/CanParticipantJoinConsultationRoomTests.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Linq;
+using FluentAssertions;
+using NUnit.Framework;
+using VideoWeb.Common.Models;
+using VideoWeb.UnitTests.Builders;
+
+namespace VideoWeb.UnitTests.Models.Conference;
+
+public class CanParticipantJoinConsultationRoomTests
+{
+ [Test]
+ public void ReturnsTrue_WhenParticipantCanJoin()
+ {
+ // Arrange
+ var conference = new ConferenceCacheModelBuilder()
+ .Build();
+ var nonHostParticipants = conference.Participants.Where(x => !x.IsHost()).ToList();
+ var participant = nonHostParticipants[0];
+
+ var patInRoom = nonHostParticipants[1];
+ var endpoint = conference.Endpoints[0];
+
+ var roomLabel = "Room1";
+ var consultationRoom = new ConsultationRoom { Label = roomLabel };
+ conference.ConsultationRooms.Add(consultationRoom);
+ patInRoom.CurrentRoom = consultationRoom;
+ endpoint.CurrentRoom = consultationRoom;
+
+ // Act
+ var result = conference.CanParticipantJoinConsultationRoom(roomLabel, participant.Id);
+
+ // Assert
+ result.Should().BeTrue();
+ }
+
+ [Test]
+ public void ReturnsFalse_WhenParticipantCannotJoin()
+ {
+ // Arrange
+ var conference = new ConferenceCacheModelBuilder()
+ .Build();
+ var nonHostParticipants = conference.Participants.Where(x => !x.IsHost()).ToList();
+ var participant = nonHostParticipants[0];
+
+
+ var patInRoom = nonHostParticipants[1];
+ var endpoint = conference.Endpoints[0];
+
+ participant.ProtectFrom.Add(endpoint.ExternalReferenceId);
+
+ var roomLabel = "Room1";
+ var consultationRoom = new ConsultationRoom { Label = roomLabel };
+ conference.ConsultationRooms.Add(consultationRoom);
+ patInRoom.CurrentRoom = consultationRoom;
+ endpoint.CurrentRoom = consultationRoom;
+
+ // Act
+ var result = conference.CanParticipantJoinConsultationRoom(roomLabel, participant.Id);
+
+ // Assert
+ result.Should().BeFalse();
+ }
+}
diff --git a/VideoWeb/VideoWeb/Controllers/ConsultationsController.cs b/VideoWeb/VideoWeb/Controllers/ConsultationsController.cs
index 6047c8decc..924fcdde06 100644
--- a/VideoWeb/VideoWeb/Controllers/ConsultationsController.cs
+++ b/VideoWeb/VideoWeb/Controllers/ConsultationsController.cs
@@ -116,6 +116,7 @@ public async Task RespondToConsultationRequestAsync(PrivateConsul
[ProducesResponseType((int)HttpStatusCode.Accepted)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
+ [ProducesResponseType((int)HttpStatusCode.Forbidden)]
public async Task JoinPrivateConsultation(JoinPrivateConsultationRequest request, CancellationToken cancellationToken)
{
try
@@ -132,6 +133,11 @@ public async Task JoinPrivateConsultation(JoinPrivateConsultation
return NotFound("Couldn't find participant.");
}
+ if (!conference.CanParticipantJoinConsultationRoom(request.RoomLabel, request.ParticipantId))
+ {
+ return Forbid("Participant is not allowed to join the consultation room with a participant they are screened from");
+ }
+
var mappedRequest = JoinPrivateConsultationRequestMapper.Map(request);
await videoApiClient.RespondToConsultationRequestAsync(mappedRequest, cancellationToken);
@@ -158,6 +164,12 @@ public async Task StartConsultationAsync(StartPrivateConsultation
{
var username = User.Identity?.Name?.Trim() ?? throw new UnauthorizedAccessException("No username found in claims");
var conference = await conferenceService.GetConference(request.ConferenceId, cancellationToken);
+
+ if (conference.AreEntitiesScreenedFromEachOther(request.InviteParticipants.ToList(),
+ request.InviteEndpoints.ToList()))
+ {
+ return Forbid("Cannot start consultation with participants or endpoints that are screened from each other");
+ }
var requestedBy = conference.Participants?.SingleOrDefault(x => x.Id == request.RequestedBy && x.Username.Trim().Equals(username, StringComparison.CurrentCultureIgnoreCase));
if (requestedBy == null)
@@ -279,6 +291,7 @@ await consultationNotifier.NotifyRoomUpdateAsync(conference,
[ProducesResponseType((int)HttpStatusCode.Accepted)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
+ [ProducesResponseType((int)HttpStatusCode.Forbidden)]
public async Task InviteToConsultationAsync(InviteToConsultationRequest request, CancellationToken cancellationToken)
{
var conference = await conferenceService.GetConference(request.ConferenceId, cancellationToken);
@@ -289,6 +302,11 @@ public async Task InviteToConsultationAsync(InviteToConsultationR
{
return Unauthorized("You must be a VHO or a member of the conference");
}
+
+ if (!conference.CanParticipantJoinConsultationRoom(request.RoomLabel, request.ParticipantId))
+ {
+ return Forbid("Participant is not allowed to join the consultation room with a participant they are screened from");
+ }
await consultationNotifier.NotifyConsultationRequestAsync(conference, request.RoomLabel, requestedBy?.Id ?? Guid.Empty, request.ParticipantId);
@@ -310,6 +328,11 @@ public async Task AddEndpointToConsultationAsync(AddEndpointConsu
{
return Unauthorized("You must be a VHO or a member of the conference");
}
+
+ if (!conference.CanEndpointJoinConsultationRoom(request.RoomLabel, request.EndpointId))
+ {
+ return Forbid("Endpoint is not allowed to join the consultation room with a participant they are screened from");
+ }
try
{