@@ -12,13 +12,19 @@ import (
12
12
"time"
13
13
14
14
"github.com/google/uuid"
15
+ "github.com/prometheus/client_golang/prometheus"
15
16
"github.com/stretchr/testify/require"
16
17
18
+ "cdr.dev/slog/sloggers/slogtest"
19
+ "github.com/coder/coder/v2/coderd/coderdtest"
17
20
"github.com/coder/coder/v2/coderd/database"
18
21
"github.com/coder/coder/v2/coderd/database/db2sdk"
22
+ "github.com/coder/coder/v2/coderd/database/dbauthz"
19
23
"github.com/coder/coder/v2/coderd/database/dbgen"
24
+ "github.com/coder/coder/v2/coderd/database/dbtestutil"
20
25
"github.com/coder/coder/v2/coderd/database/dbtime"
21
26
"github.com/coder/coder/v2/coderd/database/migrations"
27
+ "github.com/coder/coder/v2/coderd/rbac"
22
28
"github.com/coder/coder/v2/testutil"
23
29
)
24
30
@@ -767,6 +773,143 @@ func TestReadCustomRoles(t *testing.T) {
767
773
}
768
774
}
769
775
776
+ func TestAuthorizedAuditLogs (t * testing.T ) {
777
+ t .Parallel ()
778
+
779
+ var allLogs []database.AuditLog
780
+ db ,_ := dbtestutil .NewDB (t )
781
+ authz := rbac .NewAuthorizer (prometheus .NewRegistry ())
782
+ db = dbauthz .New (db ,authz ,slogtest .Make (t ,& slogtest.Options {}),coderdtest .AccessControlStorePointer ())
783
+
784
+ siteWideIDs := []uuid.UUID {uuid .New (),uuid .New ()}
785
+ for _ ,id := range siteWideIDs {
786
+ allLogs = append (allLogs ,dbgen .AuditLog (t ,db , database.AuditLog {
787
+ ID :id ,
788
+ OrganizationID :uuid .Nil ,
789
+ }))
790
+
791
+ }
792
+
793
+ orgAuditLogs := map [uuid.UUID ][]uuid.UUID {
794
+ uuid .New (): {uuid .New (),uuid .New ()},
795
+ uuid .New (): {uuid .New (),uuid .New ()},
796
+ }
797
+ orgIDs := make ([]uuid.UUID ,0 ,len (orgAuditLogs ))
798
+ for orgID := range orgAuditLogs {
799
+ orgIDs = append (orgIDs ,orgID )
800
+ }
801
+ for orgID ,ids := range orgAuditLogs {
802
+ dbgen .Organization (t ,db , database.Organization {
803
+ ID :orgID ,
804
+ })
805
+ for _ ,id := range ids {
806
+ allLogs = append (allLogs ,dbgen .AuditLog (t ,db , database.AuditLog {
807
+ ID :id ,
808
+ OrganizationID :orgID ,
809
+ }))
810
+ }
811
+ }
812
+
813
+ // Now fetch all the logs
814
+ ctx := testutil .Context (t ,testutil .WaitLong )
815
+ auditorRole ,err := rbac .RoleByName (rbac .RoleAuditor ())
816
+ require .NoError (t ,err )
817
+
818
+ memberRole ,err := rbac .RoleByName (rbac .RoleMember ())
819
+ require .NoError (t ,err )
820
+
821
+ orgAuditorRoles := func (t * testing.T ,orgID uuid.UUID ) rbac.Role {
822
+ t .Helper ()
823
+
824
+ role ,err := rbac .RoleByName (rbac .ScopedRoleOrgAuditor (orgID ))
825
+ require .NoError (t ,err )
826
+ return role
827
+ }
828
+
829
+ t .Run ("NoAccess" ,func (t * testing.T ) {
830
+ siteAuditorCtx := dbauthz .As (ctx , rbac.Subject {
831
+ FriendlyName :"member" ,
832
+ ID :uuid .NewString (),
833
+ Roles : rbac.Roles {memberRole },
834
+ Scope :rbac .ScopeAll ,
835
+ })
836
+
837
+ logs ,err := db .GetAuditLogsOffset (siteAuditorCtx , database.GetAuditLogsOffsetParams {})
838
+ require .NoError (t ,err )
839
+ require .Len (t ,logs ,0 ,"no logs should be returned" )
840
+ })
841
+
842
+ t .Run ("SiteWideAuditor" ,func (t * testing.T ) {
843
+ siteAuditorCtx := dbauthz .As (ctx , rbac.Subject {
844
+ FriendlyName :"owner" ,
845
+ ID :uuid .NewString (),
846
+ Roles : rbac.Roles {auditorRole },
847
+ Scope :rbac .ScopeAll ,
848
+ })
849
+
850
+ logs ,err := db .GetAuditLogsOffset (siteAuditorCtx , database.GetAuditLogsOffsetParams {})
851
+ require .NoError (t ,err )
852
+ require .ElementsMatch (t ,auditOnlyIDs (allLogs ),auditOnlyIDs (logs ))
853
+ })
854
+
855
+ t .Run ("SingleOrgAuditor" ,func (t * testing.T ) {
856
+ orgID := orgIDs [0 ]
857
+ siteAuditorCtx := dbauthz .As (ctx , rbac.Subject {
858
+ FriendlyName :"org-auditor" ,
859
+ ID :uuid .NewString (),
860
+ Roles : rbac.Roles {orgAuditorRoles (t ,orgID )},
861
+ Scope :rbac .ScopeAll ,
862
+ })
863
+
864
+ logs ,err := db .GetAuditLogsOffset (siteAuditorCtx , database.GetAuditLogsOffsetParams {})
865
+ require .NoError (t ,err )
866
+ require .ElementsMatch (t ,orgAuditLogs [orgID ],auditOnlyIDs (logs ))
867
+ })
868
+
869
+ t .Run ("TwoOrgAuditors" ,func (t * testing.T ) {
870
+ first := orgIDs [0 ]
871
+ second := orgIDs [1 ]
872
+ siteAuditorCtx := dbauthz .As (ctx , rbac.Subject {
873
+ FriendlyName :"org-auditor" ,
874
+ ID :uuid .NewString (),
875
+ Roles : rbac.Roles {orgAuditorRoles (t ,first ),orgAuditorRoles (t ,second )},
876
+ Scope :rbac .ScopeAll ,
877
+ })
878
+
879
+ logs ,err := db .GetAuditLogsOffset (siteAuditorCtx , database.GetAuditLogsOffsetParams {})
880
+ require .NoError (t ,err )
881
+ require .ElementsMatch (t ,append (orgAuditLogs [first ],orgAuditLogs [second ]... ),auditOnlyIDs (logs ))
882
+ })
883
+
884
+ t .Run ("ErroneousOrg" ,func (t * testing.T ) {
885
+ siteAuditorCtx := dbauthz .As (ctx , rbac.Subject {
886
+ FriendlyName :"org-auditor" ,
887
+ ID :uuid .NewString (),
888
+ Roles : rbac.Roles {orgAuditorRoles (t ,uuid .New ())},
889
+ Scope :rbac .ScopeAll ,
890
+ })
891
+
892
+ logs ,err := db .GetAuditLogsOffset (siteAuditorCtx , database.GetAuditLogsOffsetParams {})
893
+ require .NoError (t ,err )
894
+ require .Len (t ,logs ,0 ,"no logs should be returned" )
895
+ })
896
+ }
897
+
898
+ func auditOnlyIDs [T database.AuditLog | database.GetAuditLogsOffsetRow ](logs []T ) []uuid.UUID {
899
+ ids := make ([]uuid.UUID ,0 ,len (logs ))
900
+ for _ ,log := range logs {
901
+ switch log := any (log ).(type ) {
902
+ case database.AuditLog :
903
+ ids = append (ids ,log .ID )
904
+ case database.GetAuditLogsOffsetRow :
905
+ ids = append (ids ,log .AuditLog .ID )
906
+ default :
907
+ panic ("unreachable" )
908
+ }
909
+ }
910
+ return ids
911
+ }
912
+
770
913
type tvArgs struct {
771
914
Status database.ProvisionerJobStatus
772
915
// CreateWorkspace is true if we should create a workspace for the template version