fix!: require org membership for user ACLs (#26852)

This commit is contained in:
Jon Ayers
2026-07-01 02:15:08 -05:00
committed by GitHub
parent ac8cda66f7
commit 6b3341aad3
2 changed files with 108 additions and 3 deletions
+105 -2
View File
@@ -312,19 +312,35 @@ func TestAuthorizeDomain(t *testing.T) {
testAuthorize(t, "UserACLList", user, []authTestCase{
{
resource: ResourceWorkspace.WithOwner(unusedID.String()).InOrg(unusedID).WithACLUserList(map[string][]policy.Action{
resource: ResourceWorkspace.WithOwner(unusedID.String()).InOrg(defOrg).WithACLUserList(map[string][]policy.Action{
user.ID: ResourceWorkspace.AvailableActions(),
}),
actions: ResourceWorkspace.AvailableActions(),
allow: true,
},
{
resource: ResourceWorkspace.WithOwner(unusedID.String()).InOrg(unusedID).WithACLUserList(map[string][]policy.Action{
resource: ResourceWorkspace.WithOwner(unusedID.String()).InOrg(defOrg).WithACLUserList(map[string][]policy.Action{
user.ID: {policy.WildcardSymbol},
}),
actions: ResourceWorkspace.AvailableActions(),
allow: true,
},
{
// User ACLs only grant permissions in organizations where the
// subject is currently a member.
resource: ResourceWorkspace.WithOwner(unusedID.String()).InOrg(unusedID).WithACLUserList(map[string][]policy.Action{
user.ID: ResourceWorkspace.AvailableActions(),
}),
actions: ResourceWorkspace.AvailableActions(),
allow: false,
},
{
resource: ResourceWorkspace.WithOwner(unusedID.String()).InOrg(unusedID).WithACLUserList(map[string][]policy.Action{
user.ID: {policy.WildcardSymbol},
}),
actions: ResourceWorkspace.AvailableActions(),
allow: false,
},
{
resource: ResourceWorkspace.WithOwner(unusedID.String()).InOrg(unusedID).WithACLUserList(map[string][]policy.Action{
user.ID: {policy.ActionRead, policy.ActionUpdate},
@@ -714,6 +730,93 @@ func TestAuthorizeDomain(t *testing.T) {
}))
}
// TestAuthorizeUserACLOrgMembership verifies that user ACL grants require org
// membership, while site-wide roles still authorize independent of ACLs.
func TestAuthorizeUserACLOrgMembership(t *testing.T) {
t.Parallel()
orgID := uuid.New()
// Site template-admin, not a member of orgID.
siteTemplateAdmin := Subject{
ID: "site-template-admin",
Scope: must(ExpandScope(ScopeAll)),
Roles: Roles{
must(RoleByName(RoleMember())),
must(RoleByName(RoleTemplateAdmin())),
},
}
testAuthorize(t, "SiteTemplateAdminNotInOrg", siteTemplateAdmin, []authTestCase{
{
// Authorized by the site role, no ACL needed.
resource: ResourceTemplate.InOrg(orgID),
actions: []policy.Action{policy.ActionUpdate},
allow: true,
},
{
// Redundant ACL entry; still authorized by the site role.
resource: ResourceTemplate.InOrg(orgID).WithACLUserList(map[string][]policy.Action{
siteTemplateAdmin.ID: {policy.ActionUpdate},
}),
actions: []policy.Action{policy.ActionUpdate},
allow: true,
},
})
// Site user-admin (no template perms), not a member of orgID.
siteUserAdmin := Subject{
ID: "site-user-admin",
Scope: must(ExpandScope(ScopeAll)),
Roles: Roles{
must(RoleByName(RoleMember())),
must(RoleByName(RoleUserAdmin())),
},
}
testAuthorize(t, "SiteUserAdminNotInOrg", siteUserAdmin, []authTestCase{
{
// No template role and no ACL entry: denied.
resource: ResourceTemplate.InOrg(orgID),
actions: []policy.Action{policy.ActionUpdate},
allow: false,
},
{
// An ACL grant must not authorize a non-member.
resource: ResourceTemplate.InOrg(orgID).WithACLUserList(map[string][]policy.Action{
siteUserAdmin.ID: {policy.ActionUpdate},
}),
actions: []policy.Action{policy.ActionUpdate},
allow: false,
},
})
// Same site user-admin, now also a member of orgID.
siteUserAdminOrgMember := Subject{
ID: "site-user-admin-org-member",
Scope: must(ExpandScope(ScopeAll)),
Roles: Roles{
must(RoleByName(RoleMember())),
must(RoleByName(RoleUserAdmin())),
orgMemberRole(orgID),
},
}
testAuthorize(t, "SiteUserAdminOrgMember", siteUserAdminOrgMember, []authTestCase{
{
// Org membership alone does not grant template update.
resource: ResourceTemplate.InOrg(orgID),
actions: []policy.Action{policy.ActionUpdate},
allow: false,
},
{
// As an org member, the ACL grant takes effect.
resource: ResourceTemplate.InOrg(orgID).WithACLUserList(map[string][]policy.Action{
siteUserAdminOrgMember.ID: {policy.ActionUpdate},
}),
actions: []policy.Action{policy.ActionUpdate},
allow: true,
},
})
}
// TestAuthorizeLevels ensures level overrides are acting appropriately
func TestAuthorizeLevels(t *testing.T) {
t.Parallel()
+3 -1
View File
@@ -330,7 +330,9 @@ object_is_included_in_scope_allow_list if {
# ACL for users
acl_allow if {
# TODO: Should you have to be a member of the org too?
# The subject must be a member of the object's organization for a
# user ACL grant to apply.
is_org_member
perms := input.object.acl_user_list[input.subject.id]
# Check if either the action or * is allowed