@@ -312,19 +312,35 @@ func TestAuthorizeDomain(t *testing.T) {
312312
313313 testAuthorize (t , "UserACLList" , user , []authTestCase {
314314 {
315- resource : ResourceWorkspace .WithOwner (unusedID .String ()).InOrg (unusedID ).WithACLUserList (map [string ][]policy.Action {
315+ resource : ResourceWorkspace .WithOwner (unusedID .String ()).InOrg (defOrg ).WithACLUserList (map [string ][]policy.Action {
316316 user .ID : ResourceWorkspace .AvailableActions (),
317317 }),
318318 actions : ResourceWorkspace .AvailableActions (),
319319 allow : true ,
320320 },
321321 {
322- resource : ResourceWorkspace .WithOwner (unusedID .String ()).InOrg (unusedID ).WithACLUserList (map [string ][]policy.Action {
322+ resource : ResourceWorkspace .WithOwner (unusedID .String ()).InOrg (defOrg ).WithACLUserList (map [string ][]policy.Action {
323323 user .ID : {policy .WildcardSymbol },
324324 }),
325325 actions : ResourceWorkspace .AvailableActions (),
326326 allow : true ,
327327 },
328+ {
329+ // User ACLs only grant permissions in organizations where the
330+ // subject is currently a member.
331+ resource : ResourceWorkspace .WithOwner (unusedID .String ()).InOrg (unusedID ).WithACLUserList (map [string ][]policy.Action {
332+ user .ID : ResourceWorkspace .AvailableActions (),
333+ }),
334+ actions : ResourceWorkspace .AvailableActions (),
335+ allow : false ,
336+ },
337+ {
338+ resource : ResourceWorkspace .WithOwner (unusedID .String ()).InOrg (unusedID ).WithACLUserList (map [string ][]policy.Action {
339+ user .ID : {policy .WildcardSymbol },
340+ }),
341+ actions : ResourceWorkspace .AvailableActions (),
342+ allow : false ,
343+ },
328344 {
329345 resource : ResourceWorkspace .WithOwner (unusedID .String ()).InOrg (unusedID ).WithACLUserList (map [string ][]policy.Action {
330346 user .ID : {policy .ActionRead , policy .ActionUpdate },
@@ -714,6 +730,93 @@ func TestAuthorizeDomain(t *testing.T) {
714730 }))
715731}
716732
733+ // TestAuthorizeUserACLOrgMembership verifies that user ACL grants require org
734+ // membership, while site-wide roles still authorize independent of ACLs.
735+ func TestAuthorizeUserACLOrgMembership (t * testing.T ) {
736+ t .Parallel ()
737+
738+ orgID := uuid .New ()
739+
740+ // Site template-admin, not a member of orgID.
741+ siteTemplateAdmin := Subject {
742+ ID : "site-template-admin" ,
743+ Scope : must (ExpandScope (ScopeAll )),
744+ Roles : Roles {
745+ must (RoleByName (RoleMember ())),
746+ must (RoleByName (RoleTemplateAdmin ())),
747+ },
748+ }
749+ testAuthorize (t , "SiteTemplateAdminNotInOrg" , siteTemplateAdmin , []authTestCase {
750+ {
751+ // Authorized by the site role, no ACL needed.
752+ resource : ResourceTemplate .InOrg (orgID ),
753+ actions : []policy.Action {policy .ActionUpdate },
754+ allow : true ,
755+ },
756+ {
757+ // Redundant ACL entry; still authorized by the site role.
758+ resource : ResourceTemplate .InOrg (orgID ).WithACLUserList (map [string ][]policy.Action {
759+ siteTemplateAdmin .ID : {policy .ActionUpdate },
760+ }),
761+ actions : []policy.Action {policy .ActionUpdate },
762+ allow : true ,
763+ },
764+ })
765+
766+ // Site user-admin (no template perms), not a member of orgID.
767+ siteUserAdmin := Subject {
768+ ID : "site-user-admin" ,
769+ Scope : must (ExpandScope (ScopeAll )),
770+ Roles : Roles {
771+ must (RoleByName (RoleMember ())),
772+ must (RoleByName (RoleUserAdmin ())),
773+ },
774+ }
775+ testAuthorize (t , "SiteUserAdminNotInOrg" , siteUserAdmin , []authTestCase {
776+ {
777+ // No template role and no ACL entry: denied.
778+ resource : ResourceTemplate .InOrg (orgID ),
779+ actions : []policy.Action {policy .ActionUpdate },
780+ allow : false ,
781+ },
782+ {
783+ // An ACL grant must not authorize a non-member.
784+ resource : ResourceTemplate .InOrg (orgID ).WithACLUserList (map [string ][]policy.Action {
785+ siteUserAdmin .ID : {policy .ActionUpdate },
786+ }),
787+ actions : []policy.Action {policy .ActionUpdate },
788+ allow : false ,
789+ },
790+ })
791+
792+ // Same site user-admin, now also a member of orgID.
793+ siteUserAdminOrgMember := Subject {
794+ ID : "site-user-admin-org-member" ,
795+ Scope : must (ExpandScope (ScopeAll )),
796+ Roles : Roles {
797+ must (RoleByName (RoleMember ())),
798+ must (RoleByName (RoleUserAdmin ())),
799+ orgMemberRole (orgID ),
800+ },
801+ }
802+ testAuthorize (t , "SiteUserAdminOrgMember" , siteUserAdminOrgMember , []authTestCase {
803+ {
804+ // Org membership alone does not grant template update.
805+ resource : ResourceTemplate .InOrg (orgID ),
806+ actions : []policy.Action {policy .ActionUpdate },
807+ allow : false ,
808+ },
809+ {
810+ // As an org member, the ACL grant takes effect.
811+ resource : ResourceTemplate .InOrg (orgID ).WithACLUserList (map [string ][]policy.Action {
812+ siteUserAdminOrgMember .ID : {policy .ActionUpdate },
813+ }),
814+ actions : []policy.Action {policy .ActionUpdate },
815+ allow : true ,
816+ },
817+ })
818+ }
819+
717820// TestAuthorizeLevels ensures level overrides are acting appropriately
718821func TestAuthorizeLevels (t * testing.T ) {
719822 t .Parallel ()
0 commit comments