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 {