diff --git a/internal/restrict/collection/mediafile_test.go b/internal/restrict/collection/mediafile_test.go index de58c780..f5709d9e 100644 --- a/internal/restrict/collection/mediafile_test.go +++ b/internal/restrict/collection/mediafile_test.go @@ -230,7 +230,12 @@ func TestMediafileModeA(t *testing.T) { meeting_mediafile/2: meeting_id: 7 projection_ids: [4] - projection/4/current_projector_id: 5 + + projection/4: + current_projector_id: 5 + meeting_id: 7 + + projector/5/meeting_id: 7 meeting/7: committee_id: 404 diff --git a/internal/restrict/collection/meeting_mediafile.go b/internal/restrict/collection/meeting_mediafile.go index 82a604a9..784aaa43 100644 --- a/internal/restrict/collection/meeting_mediafile.go +++ b/internal/restrict/collection/meeting_mediafile.go @@ -14,7 +14,7 @@ import ( // // The user is an admin of the meeting. // The user can see the meeting and used_as_logo_*_in_meeting_id or used_as_font_*_in_meeting_id is not empty. -// The user has projector.can_see and one of the projections linked with meeting_mediafile/projection_ids has projection/current_projector_id set. +// The user can see a projection linked in `meeting_mediafile/projection_ids`. // The user has mediafile.can_see and either: // meeting_mediafile/is_public is true, or // the user has groups in common with meeting_mediafile/inherited_access_group_ids. @@ -47,6 +47,8 @@ func (m MeetingMediafile) Modes(mode string) FieldRestricter { } func (m MeetingMediafile) see(ctx context.Context, ds *dsfetch.Fetch, meetingMediafileIDs ...int) ([]int, error) { + projectionRestrictor := Collection(ctx, "projection").Modes("A") + return eachMeeting(ctx, ds, m, meetingMediafileIDs, func(meetingID int, ids []int) ([]int, error) { canSeeMeeting, err := Collection(ctx, Meeting{}.Name()).Modes("B")(ctx, ds, meetingID) if err != nil { @@ -76,22 +78,18 @@ func (m MeetingMediafile) see(ctx context.Context, ds *dsfetch.Fetch, meetingMed return true, nil } - if perms.Has(perm.ProjectorCanSee) { - p7onIDs, err := ds.MeetingMediafile_ProjectionIDs(meetingMediafileID).Value(ctx) - if err != nil { - return false, fmt.Errorf("getting projection ids: %w", err) - } + p7onIDs, err := ds.MeetingMediafile_ProjectionIDs(meetingMediafileID).Value(ctx) + if err != nil { + return false, fmt.Errorf("getting projection ids: %w", err) + } - for _, p7onID := range p7onIDs { - value, err := ds.Projection_CurrentProjectorID(p7onID).Value(ctx) - if err != nil { - return false, fmt.Errorf("getting current projector id: %w", err) - } + allowedP7ons, err := projectionRestrictor(ctx, ds, p7onIDs...) + if err != nil { + return false, fmt.Errorf("checking p7on restriction: %w", err) + } - if !value.Null() { - return true, nil - } - } + if len(allowedP7ons) > 0 { + return true, nil } if perms.Has(perm.MediafileCanSee) { diff --git a/internal/restrict/collection/meeting_mediafile_test.go b/internal/restrict/collection/meeting_mediafile_test.go index 910079d8..8630f47f 100644 --- a/internal/restrict/collection/meeting_mediafile_test.go +++ b/internal/restrict/collection/meeting_mediafile_test.go @@ -111,7 +111,12 @@ func TestMeetingMediafileModeA(t *testing.T) { meeting_mediafile/1: meeting_id: 7 projection_ids: [4] - projection/4/current_projector_id: 5 + + projection/4: + current_projector_id: 5 + meeting_id: 7 + + projector/5/meeting_id: 7 meeting/7: committee_id: 404 @@ -161,11 +166,39 @@ func TestMeetingMediafileModeA(t *testing.T) { group/2/meeting_user_ids: [10] meeting_user/10/user_id: 1 - projection/4/id: 4 + projection/4/meeting_id: 7 `, withPerms(7, perm.ProjectorCanSee), ) + testCase( + "On autopilot projector with meeting.can_see_autopilot", + t, + m.Modes("A"), + true, + `--- + meeting_mediafile/1: + meeting_id: 30 + projection_ids: [4] + + projection/4: + current_projector_id: 7 + meeting_id: 30 + + projector/7: + used_as_reference_projector_meeting_id: 30 + meeting_id: 30 + + meeting/30: + committee_id: 404 + group_ids: [2] + + group/2/meeting_user_ids: [10] + meeting_user/10/user_id: 1 + `, + withPerms(30, perm.MeetingCanSeeAutopilot), + ) + testCase( "meeting mediafile can_manage", t, diff --git a/internal/restrict/collection/projection.go b/internal/restrict/collection/projection.go index d505914a..c1e11536 100644 --- a/internal/restrict/collection/projection.go +++ b/internal/restrict/collection/projection.go @@ -10,7 +10,9 @@ import ( // Projection handels the restriction for the projection collection. // -// The user can see a projection, if the user has projector.can_see. +// The user can see a projection, +// * if he has projector.can_manage or +// * he can see the projector linked in projection/current_projector_id. // // Mode A: The user can see the projection. type Projection struct{} @@ -40,5 +42,49 @@ func (p Projection) Modes(mode string) FieldRestricter { } func (p Projection) see(ctx context.Context, ds *dsfetch.Fetch, projectionIDs ...int) ([]int, error) { - return meetingPerm(ctx, ds, p, projectionIDs, perm.ProjectorCanSee) + projectorRestrictor := Collection(ctx, "projector").Modes("A") + + return eachMeeting(ctx, ds, p, projectionIDs, func(meetingID int, ids []int) ([]int, error) { + perms, err := perm.FromContext(ctx, meetingID) + if err != nil { + return nil, fmt.Errorf("getting permission: %w", err) + } + + if perms.Has(perm.ProjectorCanManage) { + return ids, nil + } + + currentProjector := make([]dsfetch.Maybe[int], len(ids)) + for i, id := range ids { + ds.Projection_CurrentProjectorID(id).Lazy(¤tProjector[i]) + } + + if err := ds.Execute(ctx); err != nil { + return nil, fmt.Errorf("reading current_projector_id") + } + + var allowed []int + for i, maybeID := range currentProjector { + projectorID, hasCurrent := maybeID.Value() + if !hasCurrent { + continue + } + + // This chekcs each projector by its own. But the result should be + // in the cache anyway. So this should be more performent, then + // putting many projector-ids in a set. + canSeeProjector, err := projectorRestrictor(ctx, ds, projectorID) + if err != nil { + return nil, fmt.Errorf("checking projector restrictor: %w", err) + } + + if len(canSeeProjector) == 0 { + continue + } + + allowed = append(allowed, ids[i]) + } + + return allowed, nil + }) } diff --git a/internal/restrict/collection/projection_test.go b/internal/restrict/collection/projection_test.go index fcbb50a8..388c6b48 100644 --- a/internal/restrict/collection/projection_test.go +++ b/internal/restrict/collection/projection_test.go @@ -11,12 +11,12 @@ func TestProjectionModeA(t *testing.T) { f := collection.Projection{}.Modes("A") testCase( - "can see", + "manager", t, f, true, "projection/1/meeting_id: 30", - withPerms(30, perm.ProjectorCanSee), + withPerms(30, perm.ProjectorCanManage), ) testCase( @@ -26,4 +26,52 @@ func TestProjectionModeA(t *testing.T) { false, "projection/1/meeting_id: 30", ) + + testCase( + "linked on reference projector with no perms", + t, + f, + false, + ` + projection/1: + meeting_id: 30 + current_projector_id: 7 + + projector/7: + used_as_reference_projector_meeting_id: 30 + meeting_id: 30 + `, + ) + + testCase( + "linked on reference projector with meeting.can_see_autopilote", + t, + f, + true, + ` + projection/1: + meeting_id: 30 + current_projector_id: 7 + + projector/7: + used_as_reference_projector_meeting_id: 30 + meeting_id: 30 + `, + withPerms(30, perm.MeetingCanSeeAutopilot), + ) + + testCase( + "linked on normal projector with projector.can_see", + t, + f, + true, + ` + projection/1: + meeting_id: 30 + current_projector_id: 7 + + projector/7/meeting_id: 30 + `, + withPerms(30, perm.ProjectorCanSee), + ) } diff --git a/internal/restrict/collection/projector.go b/internal/restrict/collection/projector.go index 4d02ba73..4f1c6b38 100644 --- a/internal/restrict/collection/projector.go +++ b/internal/restrict/collection/projector.go @@ -10,7 +10,9 @@ import ( // Projector handels the restriction for the projector collection. // -// The user can see a projector, if the user has projector.can_see. +// The user can see a projector, +// * if the user has projector.can_see or +// * the projector is the reference projector and the user has meeting.can_see_autopilot. // // If the projector has internal=true, then the user needs projector.can_manage // @@ -52,13 +54,11 @@ func (p Projector) see(ctx context.Context, ds *dsfetch.Fetch, projectorIDs ...i return projectorIDs, nil } - if !perms.Has(perm.ProjectorCanSee) { - return nil, nil - } - internalProjectorIDs := make([]bool, len(projectorIDs)) + usedAsReference := make([]dsfetch.Maybe[int], len(projectorIDs)) for i, projectorID := range projectorIDs { ds.Projector_IsInternal(projectorID).Lazy(&internalProjectorIDs[i]) + ds.Projector_UsedAsReferenceProjectorMeetingID(projectorID).Lazy(&usedAsReference[i]) } if err := ds.Execute(ctx); err != nil { @@ -67,7 +67,15 @@ func (p Projector) see(ctx context.Context, ds *dsfetch.Fetch, projectorIDs ...i var allowed []int for i, projectorID := range projectorIDs { - if !internalProjectorIDs[i] { + if internalProjectorIDs[i] { + continue + } + + if !usedAsReference[i].Null() && perms.Has(perm.MeetingCanSeeAutopilot) { + allowed = append(allowed, projectorID) + } + + if perms.Has(perm.ProjectorCanSee) { allowed = append(allowed, projectorID) } } diff --git a/internal/restrict/collection/projector_test.go b/internal/restrict/collection/projector_test.go index 338d20d2..9702aa24 100644 --- a/internal/restrict/collection/projector_test.go +++ b/internal/restrict/collection/projector_test.go @@ -27,6 +27,69 @@ func TestProjectorModeA(t *testing.T) { "projector/1/meeting_id: 30", ) + testCase( + "reference projector with projector.can_see", + t, + f, + true, + ` + projector/1: + meeting_id: 30 + used_as_reference_projector_meeting_id: 30 + `, + withPerms(30, perm.ProjectorCanSee), + ) + + testCase( + "reference projector with meeting.can_see_autopilot", + t, + f, + true, + ` + projector/1: + meeting_id: 30 + used_as_reference_projector_meeting_id: 30 + `, + withPerms(30, perm.MeetingCanSeeAutopilot), + ) + + testCase( + "reference projector with no perms", + t, + f, + false, + ` + projector/1: + meeting_id: 30 + used_as_reference_projector_meeting_id: 30 + `, + ) + + testCase( + "not reference projector with meeting.can_see_autopilot", + t, + f, + false, + ` + projector/1/meeting_id: 30 + `, + withPerms(30, perm.MeetingCanSeeAutopilot), + ) + + testCase( + "reference projector with meeting.can_see_autopilot but internal", + t, + f, + false, + ` + projector/1: + meeting_id: 30 + used_as_reference_projector_meeting_id: 30 + is_internal: true + `, + withPerms(30, perm.MeetingCanSeeAutopilot), + ) + testCase( "can see with internal", t, diff --git a/internal/restrict/restrict.go b/internal/restrict/restrict.go index 75135070..54cb9553 100644 --- a/internal/restrict/restrict.go +++ b/internal/restrict/restrict.go @@ -541,43 +541,43 @@ var collectionOrder = map[string]int{ "committee": 6, "meeting": 7, "point_of_order_category": 8, - "group": 8, - "meeting_mediafile": 9, - "mediafile": 10, - "tag": 11, - "motion/C": 12, - "motion/B": 13, - "motion_block": 14, - "motion_category": 15, - "motion_change_recommendation": 16, - "motion_comment_section": 17, - "motion_comment": 18, - "motion_state": 19, - "motion_statute_paragraph": 20, - "motion_submitter": 21, - "motion_workflow": 22, - "poll": 23, - "option": 24, - "poll_candidate_list": 25, - "poll_candidate": 26, - "vote": 27, - "organization": 28, - "organization_tag": 29, - "personal_note": 30, - "projection": 31, - "projector": 32, - "projector_countdown": 33, - "projector_message": 34, - "theme": 35, - "topic": 36, - "list_of_speakers": 37, - "speaker": 38, - "user": 39, - "meeting_user": 40, - "action_worker": 41, - "import_preview": 42, - "structure_level": 43, - "structure_level_list_of_speakers": 44, - "motion_working_group_speaker": 45, - "motion_editor": 45, + "group": 9, + "projector": 10, + "projection": 11, + "projector_countdown": 12, + "projector_message": 13, + "meeting_mediafile": 14, + "mediafile": 15, + "tag": 16, + "motion/C": 17, + "motion/B": 18, + "motion_block": 19, + "motion_category": 20, + "motion_change_recommendation": 21, + "motion_comment_section": 22, + "motion_comment": 23, + "motion_state": 24, + "motion_statute_paragraph": 25, + "motion_submitter": 26, + "motion_workflow": 27, + "poll": 28, + "option": 29, + "poll_candidate_list": 30, + "poll_candidate": 41, + "vote": 42, + "organization": 43, + "organization_tag": 44, + "personal_note": 45, + "theme": 46, + "topic": 47, + "list_of_speakers": 48, + "speaker": 49, + "user": 50, + "meeting_user": 51, + "action_worker": 52, + "import_preview": 53, + "structure_level": 54, + "structure_level_list_of_speakers": 55, + "motion_working_group_speaker": 56, + "motion_editor": 57, }