chore: create type for unique role names (#13506)

* chore: create type for unique role names

Using `string` was confusing when something should be combined with
org context, and when not to. Naming this new name, "RoleIdentifier"
This commit is contained in:
Steven Masley
2024-06-11 08:55:28 -05:00
committed by GitHub
parent c9cca9d56e
commit 5ccf5084e8
50 changed files with 553 additions and 458 deletions
+10 -4
View File
@@ -110,13 +110,13 @@ func (s Subject) SafeScopeName() string {
if s.Scope == nil {
return "no-scope"
}
return s.Scope.Name()
return s.Scope.Name().String()
}
// SafeRoleNames prevent nil pointer dereference.
func (s Subject) SafeRoleNames() []string {
func (s Subject) SafeRoleNames() []RoleIdentifier {
if s.Roles == nil {
return []string{}
return []RoleIdentifier{}
}
return s.Roles.Names()
}
@@ -707,9 +707,15 @@ func (c *authCache) Prepare(ctx context.Context, subject Subject, action policy.
// rbacTraceAttributes are the attributes that are added to all spans created by
// the rbac package. These attributes should help to debug slow spans.
func rbacTraceAttributes(actor Subject, action policy.Action, objectType string, extra ...attribute.KeyValue) trace.SpanStartOption {
uniqueRoleNames := actor.SafeRoleNames()
roleStrings := make([]string, 0, len(uniqueRoleNames))
for _, roleName := range uniqueRoleNames {
roleName := roleName
roleStrings = append(roleStrings, roleName.String())
}
return trace.WithAttributes(
append(extra,
attribute.StringSlice("subject_roles", actor.SafeRoleNames()),
attribute.StringSlice("subject_roles", roleStrings),
attribute.Int("num_subject_roles", len(actor.SafeRoleNames())),
attribute.Int("num_groups", len(actor.Groups)),
attribute.String("scope", actor.SafeScopeName()),
+19 -19
View File
@@ -56,7 +56,7 @@ func TestFilterError(t *testing.T) {
auth := NewAuthorizer(prometheus.NewRegistry())
subject := Subject{
ID: uuid.NewString(),
Roles: RoleNames{},
Roles: RoleIdentifiers{},
Groups: []string{},
Scope: ScopeAll,
}
@@ -77,7 +77,7 @@ func TestFilterError(t *testing.T) {
subject := Subject{
ID: uuid.NewString(),
Roles: RoleNames{
Roles: RoleIdentifiers{
RoleOwner(),
},
Groups: []string{},
@@ -159,7 +159,7 @@ func TestFilter(t *testing.T) {
Name: "NoRoles",
Actor: Subject{
ID: userIDs[0].String(),
Roles: RoleNames{},
Roles: RoleIdentifiers{},
},
ObjectType: ResourceWorkspace.Type,
Action: policy.ActionRead,
@@ -168,7 +168,7 @@ func TestFilter(t *testing.T) {
Name: "Admin",
Actor: Subject{
ID: userIDs[0].String(),
Roles: RoleNames{ScopedRoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
Roles: RoleIdentifiers{ScopedRoleOrgMember(orgIDs[0]), RoleAuditor(), RoleOwner(), RoleMember()},
},
ObjectType: ResourceWorkspace.Type,
Action: policy.ActionRead,
@@ -177,7 +177,7 @@ func TestFilter(t *testing.T) {
Name: "OrgAdmin",
Actor: Subject{
ID: userIDs[0].String(),
Roles: RoleNames{ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgAdmin(orgIDs[0]), RoleMember()},
Roles: RoleIdentifiers{ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgAdmin(orgIDs[0]), RoleMember()},
},
ObjectType: ResourceWorkspace.Type,
Action: policy.ActionRead,
@@ -186,7 +186,7 @@ func TestFilter(t *testing.T) {
Name: "OrgMember",
Actor: Subject{
ID: userIDs[0].String(),
Roles: RoleNames{ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgMember(orgIDs[1]), RoleMember()},
Roles: RoleIdentifiers{ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgMember(orgIDs[1]), RoleMember()},
},
ObjectType: ResourceWorkspace.Type,
Action: policy.ActionRead,
@@ -195,7 +195,7 @@ func TestFilter(t *testing.T) {
Name: "ManyRoles",
Actor: Subject{
ID: userIDs[0].String(),
Roles: RoleNames{
Roles: RoleIdentifiers{
ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgAdmin(orgIDs[0]),
ScopedRoleOrgMember(orgIDs[1]), ScopedRoleOrgAdmin(orgIDs[1]),
ScopedRoleOrgMember(orgIDs[2]), ScopedRoleOrgAdmin(orgIDs[2]),
@@ -211,7 +211,7 @@ func TestFilter(t *testing.T) {
Name: "SiteMember",
Actor: Subject{
ID: userIDs[0].String(),
Roles: RoleNames{RoleMember()},
Roles: RoleIdentifiers{RoleMember()},
},
ObjectType: ResourceUser.Type,
Action: policy.ActionRead,
@@ -220,7 +220,7 @@ func TestFilter(t *testing.T) {
Name: "ReadOrgs",
Actor: Subject{
ID: userIDs[0].String(),
Roles: RoleNames{
Roles: RoleIdentifiers{
ScopedRoleOrgMember(orgIDs[0]),
ScopedRoleOrgMember(orgIDs[1]),
ScopedRoleOrgMember(orgIDs[2]),
@@ -235,7 +235,7 @@ func TestFilter(t *testing.T) {
Name: "ScopeApplicationConnect",
Actor: Subject{
ID: userIDs[0].String(),
Roles: RoleNames{ScopedRoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
Roles: RoleIdentifiers{ScopedRoleOrgMember(orgIDs[0]), RoleAuditor(), RoleOwner(), RoleMember()},
},
ObjectType: ResourceWorkspace.Type,
Action: policy.ActionRead,
@@ -394,7 +394,7 @@ func TestAuthorizeDomain(t *testing.T) {
ID: "me",
Scope: must(ExpandScope(ScopeAll)),
Roles: Roles{{
Name: "deny-all",
Identifier: RoleIdentifier{Name: "deny-all"},
// List out deny permissions explicitly
Site: []Permission{
{
@@ -607,8 +607,8 @@ func TestAuthorizeDomain(t *testing.T) {
Scope: must(ExpandScope(ScopeAll)),
Roles: Roles{
{
Name: "ReadOnlyOrgAndUser",
Site: []Permission{},
Identifier: RoleIdentifier{Name: "ReadOnlyOrgAndUser"},
Site: []Permission{},
Org: map[string][]Permission{
defOrg.String(): {{
Negate: false,
@@ -701,7 +701,7 @@ func TestAuthorizeLevels(t *testing.T) {
Roles: Roles{
must(RoleByName(RoleOwner())),
{
Name: "org-deny:" + defOrg.String(),
Identifier: RoleIdentifier{Name: "org-deny:", OrganizationID: defOrg},
Org: map[string][]Permission{
defOrg.String(): {
{
@@ -713,7 +713,7 @@ func TestAuthorizeLevels(t *testing.T) {
},
},
{
Name: "user-deny-all",
Identifier: RoleIdentifier{Name: "user-deny-all"},
// List out deny permissions explicitly
User: []Permission{
{
@@ -761,7 +761,7 @@ func TestAuthorizeLevels(t *testing.T) {
Scope: must(ExpandScope(ScopeAll)),
Roles: Roles{
{
Name: "site-noise",
Identifier: RoleIdentifier{Name: "site-noise"},
Site: []Permission{
{
Negate: true,
@@ -772,7 +772,7 @@ func TestAuthorizeLevels(t *testing.T) {
},
must(RoleByName(ScopedRoleOrgAdmin(defOrg))),
{
Name: "user-deny-all",
Identifier: RoleIdentifier{Name: "user-deny-all"},
// List out deny permissions explicitly
User: []Permission{
{
@@ -896,7 +896,7 @@ func TestAuthorizeScope(t *testing.T) {
},
Scope: Scope{
Role: Role{
Name: "workspace_agent",
Identifier: RoleIdentifier{Name: "workspace_agent"},
DisplayName: "Workspace Agent",
Site: Permissions(map[string][]policy.Action{
// Only read access for workspaces.
@@ -985,7 +985,7 @@ func TestAuthorizeScope(t *testing.T) {
},
Scope: Scope{
Role: Role{
Name: "create_workspace",
Identifier: RoleIdentifier{Name: "create_workspace"},
DisplayName: "Create Workspace",
Site: Permissions(map[string][]policy.Action{
// Only read access for workspaces.
+11 -11
View File
@@ -41,7 +41,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
Name: "NoRoles",
Actor: rbac.Subject{
ID: user.String(),
Roles: rbac.RoleNames{},
Roles: rbac.RoleIdentifiers{},
Scope: rbac.ScopeAll,
},
},
@@ -49,7 +49,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
Name: "Admin",
Actor: rbac.Subject{
// Give some extra roles that an admin might have
Roles: rbac.RoleNames{rbac.ScopedRoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
Roles: rbac.RoleIdentifiers{rbac.ScopedRoleOrgMember(orgs[0]), rbac.RoleAuditor(), rbac.RoleOwner(), rbac.RoleMember()},
ID: user.String(),
Scope: rbac.ScopeAll,
Groups: noiseGroups,
@@ -58,7 +58,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
{
Name: "OrgAdmin",
Actor: rbac.Subject{
Roles: rbac.RoleNames{rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgAdmin(orgs[0]), rbac.RoleMember()},
Roles: rbac.RoleIdentifiers{rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgAdmin(orgs[0]), rbac.RoleMember()},
ID: user.String(),
Scope: rbac.ScopeAll,
Groups: noiseGroups,
@@ -68,7 +68,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
Name: "OrgMember",
Actor: rbac.Subject{
// Member of 2 orgs
Roles: rbac.RoleNames{rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgMember(orgs[1]), rbac.RoleMember()},
Roles: rbac.RoleIdentifiers{rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgMember(orgs[1]), rbac.RoleMember()},
ID: user.String(),
Scope: rbac.ScopeAll,
Groups: noiseGroups,
@@ -78,7 +78,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
Name: "ManyRoles",
Actor: rbac.Subject{
// Admin of many orgs
Roles: rbac.RoleNames{
Roles: rbac.RoleIdentifiers{
rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgAdmin(orgs[0]),
rbac.ScopedRoleOrgMember(orgs[1]), rbac.ScopedRoleOrgAdmin(orgs[1]),
rbac.ScopedRoleOrgMember(orgs[2]), rbac.ScopedRoleOrgAdmin(orgs[2]),
@@ -93,7 +93,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
Name: "ManyRolesCachedSubject",
Actor: rbac.Subject{
// Admin of many orgs
Roles: rbac.RoleNames{
Roles: rbac.RoleIdentifiers{
rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgAdmin(orgs[0]),
rbac.ScopedRoleOrgMember(orgs[1]), rbac.ScopedRoleOrgAdmin(orgs[1]),
rbac.ScopedRoleOrgMember(orgs[2]), rbac.ScopedRoleOrgAdmin(orgs[2]),
@@ -108,7 +108,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
Name: "AdminWithScope",
Actor: rbac.Subject{
// Give some extra roles that an admin might have
Roles: rbac.RoleNames{rbac.ScopedRoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
Roles: rbac.RoleIdentifiers{rbac.ScopedRoleOrgMember(orgs[0]), rbac.RoleAuditor(), rbac.RoleOwner(), rbac.RoleMember()},
ID: user.String(),
Scope: rbac.ScopeApplicationConnect,
Groups: noiseGroups,
@@ -119,8 +119,8 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
Name: "StaticRoles",
Actor: rbac.Subject{
// Give some extra roles that an admin might have
Roles: rbac.RoleNames{
"auditor", rbac.RoleOwner(), rbac.RoleMember(),
Roles: rbac.RoleIdentifiers{
rbac.RoleAuditor(), rbac.RoleOwner(), rbac.RoleMember(),
rbac.RoleTemplateAdmin(), rbac.RoleUserAdmin(),
},
ID: user.String(),
@@ -133,8 +133,8 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
Name: "StaticRolesWithCache",
Actor: rbac.Subject{
// Give some extra roles that an admin might have
Roles: rbac.RoleNames{
"auditor", rbac.RoleOwner(), rbac.RoleMember(),
Roles: rbac.RoleIdentifiers{
rbac.RoleAuditor(), rbac.RoleOwner(), rbac.RoleMember(),
rbac.RoleTemplateAdmin(), rbac.RoleUserAdmin(),
},
ID: user.String(),
+127 -129
View File
@@ -1,6 +1,7 @@
package rbac
import (
"encoding/json"
"errors"
"sort"
"strings"
@@ -34,41 +35,98 @@ func init() {
ReloadBuiltinRoles(nil)
}
// RoleNames is a list of user assignable role names. The role names must be
// RoleIdentifiers is a list of user assignable role names. The role names must be
// in the builtInRoles map. Any non-user assignable roles will generate an
// error on Expand.
type RoleNames []string
type RoleIdentifiers []RoleIdentifier
func (names RoleNames) Expand() ([]Role, error) {
func (names RoleIdentifiers) Expand() ([]Role, error) {
return rolesByNames(names)
}
func (names RoleNames) Names() []string {
func (names RoleIdentifiers) Names() []RoleIdentifier {
return names
}
// RoleIdentifier contains both the name of the role, and any organizational scope.
// Both fields are required to be globally unique and identifiable.
type RoleIdentifier struct {
Name string
// OrganizationID is uuid.Nil for unscoped roles (aka deployment wide)
OrganizationID uuid.UUID
}
func (r RoleIdentifier) IsOrgRole() bool {
return r.OrganizationID != uuid.Nil
}
// RoleNameFromString takes a formatted string '<role_name>[:org_id]'.
func RoleNameFromString(input string) (RoleIdentifier, error) {
var role RoleIdentifier
arr := strings.Split(input, ":")
if len(arr) > 2 {
return role, xerrors.Errorf("too many colons in role name")
}
if len(arr) == 0 {
return role, xerrors.Errorf("empty string not a valid role")
}
if arr[0] == "" {
return role, xerrors.Errorf("role cannot be the empty string")
}
role.Name = arr[0]
if len(arr) == 2 {
orgID, err := uuid.Parse(arr[1])
if err != nil {
return role, xerrors.Errorf("%q not a valid uuid: %w", arr[1], err)
}
role.OrganizationID = orgID
}
return role, nil
}
func (r RoleIdentifier) String() string {
if r.OrganizationID != uuid.Nil {
return r.Name + ":" + r.OrganizationID.String()
}
return r.Name
}
func (r *RoleIdentifier) MarshalJSON() ([]byte, error) {
return json.Marshal(r.String())
}
func (r *RoleIdentifier) UnmarshalJSON(data []byte) error {
var str string
err := json.Unmarshal(data, &str)
if err != nil {
return err
}
v, err := RoleNameFromString(str)
if err != nil {
return err
}
*r = v
return nil
}
// The functions below ONLY need to exist for roles that are "defaulted" in some way.
// Any other roles (like auditor), can be listed and let the user select/assigned.
// Once we have a database implementation, the "default" roles can be defined on the
// site and orgs, and these functions can be removed.
func RoleOwner() string {
return RoleName(owner, "")
}
func CustomSiteRole() string { return RoleName(customSiteRole, "") }
func RoleTemplateAdmin() string {
return RoleName(templateAdmin, "")
}
func RoleUserAdmin() string {
return RoleName(userAdmin, "")
}
func RoleMember() string {
return RoleName(member, "")
}
func RoleOwner() RoleIdentifier { return RoleIdentifier{Name: owner} }
func CustomSiteRole() RoleIdentifier { return RoleIdentifier{Name: customSiteRole} }
func RoleTemplateAdmin() RoleIdentifier { return RoleIdentifier{Name: templateAdmin} }
func RoleUserAdmin() RoleIdentifier { return RoleIdentifier{Name: userAdmin} }
func RoleMember() RoleIdentifier { return RoleIdentifier{Name: member} }
func RoleAuditor() RoleIdentifier { return RoleIdentifier{Name: auditor} }
func RoleOrgAdmin() string {
return orgAdmin
@@ -81,15 +139,15 @@ func RoleOrgMember() string {
// ScopedRoleOrgAdmin is the org role with the organization ID
// Deprecated This was used before organization scope was included as a
// field in all user facing APIs. Usage of 'ScopedRoleOrgAdmin()' is preferred.
func ScopedRoleOrgAdmin(organizationID uuid.UUID) string {
return RoleName(orgAdmin, organizationID.String())
func ScopedRoleOrgAdmin(organizationID uuid.UUID) RoleIdentifier {
return RoleIdentifier{Name: orgAdmin, OrganizationID: organizationID}
}
// ScopedRoleOrgMember is the org role with the organization ID
// Deprecated This was used before organization scope was included as a
// field in all user facing APIs. Usage of 'ScopedRoleOrgMember()' is preferred.
func ScopedRoleOrgMember(organizationID uuid.UUID) string {
return RoleName(orgMember, organizationID.String())
func ScopedRoleOrgMember(organizationID uuid.UUID) RoleIdentifier {
return RoleIdentifier{Name: orgMember, OrganizationID: organizationID}
}
func allPermsExcept(excepts ...Objecter) []Permission {
@@ -127,7 +185,7 @@ func allPermsExcept(excepts ...Objecter) []Permission {
//
// This map will be replaced by database storage defined by this ticket.
// https://github.com/coder/coder/issues/1194
var builtInRoles map[string]func(orgID string) Role
var builtInRoles map[string]func(orgID uuid.UUID) Role
type RoleOptions struct {
NoOwnerWorkspaceExec bool
@@ -158,7 +216,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
// on every authorize call. 'withCachedRegoValue' can be used as well to
// preallocate the rego value that is used by the rego eval engine.
ownerRole := Role{
Name: owner,
Identifier: RoleOwner(),
DisplayName: "Owner",
Site: append(
// Workspace dormancy and workspace are omitted.
@@ -174,7 +232,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
}.withCachedRegoValue()
memberRole := Role{
Name: member,
Identifier: RoleMember(),
DisplayName: "Member",
Site: Permissions(map[string][]policy.Action{
ResourceAssignRole.Type: {policy.ActionRead},
@@ -200,7 +258,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
}.withCachedRegoValue()
auditorRole := Role{
Name: auditor,
Identifier: RoleAuditor(),
DisplayName: "Auditor",
Site: Permissions(map[string][]policy.Action{
// Should be able to read all template details, even in orgs they
@@ -220,7 +278,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
}.withCachedRegoValue()
templateAdminRole := Role{
Name: templateAdmin,
Identifier: RoleTemplateAdmin(),
DisplayName: "Template Admin",
Site: Permissions(map[string][]policy.Action{
ResourceTemplate.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete, policy.ActionViewInsights},
@@ -241,7 +299,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
}.withCachedRegoValue()
userAdminRole := Role{
Name: userAdmin,
Identifier: RoleUserAdmin(),
DisplayName: "User Admin",
Site: Permissions(map[string][]policy.Action{
ResourceAssignRole.Type: {policy.ActionAssign, policy.ActionDelete, policy.ActionRead},
@@ -257,42 +315,42 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
User: []Permission{},
}.withCachedRegoValue()
builtInRoles = map[string]func(orgID string) Role{
builtInRoles = map[string]func(orgID uuid.UUID) Role{
// admin grants all actions to all resources.
owner: func(_ string) Role {
owner: func(_ uuid.UUID) Role {
return ownerRole
},
// member grants all actions to all resources owned by the user
member: func(_ string) Role {
member: func(_ uuid.UUID) Role {
return memberRole
},
// auditor provides all permissions required to effectively read and understand
// audit log events.
// TODO: Finish the auditor as we add resources.
auditor: func(_ string) Role {
auditor: func(_ uuid.UUID) Role {
return auditorRole
},
templateAdmin: func(_ string) Role {
templateAdmin: func(_ uuid.UUID) Role {
return templateAdminRole
},
userAdmin: func(_ string) Role {
userAdmin: func(_ uuid.UUID) Role {
return userAdminRole
},
// orgAdmin returns a role with all actions allows in a given
// organization scope.
orgAdmin: func(organizationID string) Role {
orgAdmin: func(organizationID uuid.UUID) Role {
return Role{
Name: RoleName(orgAdmin, organizationID),
Identifier: RoleIdentifier{Name: orgAdmin, OrganizationID: organizationID},
DisplayName: "Organization Admin",
Site: []Permission{},
Org: map[string][]Permission{
// Org admins should not have workspace exec perms.
organizationID: append(allPermsExcept(ResourceWorkspace, ResourceWorkspaceDormant), Permissions(map[string][]policy.Action{
organizationID.String(): append(allPermsExcept(ResourceWorkspace, ResourceWorkspaceDormant), Permissions(map[string][]policy.Action{
ResourceWorkspaceDormant.Type: {policy.ActionRead, policy.ActionDelete, policy.ActionCreate, policy.ActionUpdate, policy.ActionWorkspaceStop},
ResourceWorkspace.Type: slice.Omit(ResourceWorkspace.AvailableActions(), policy.ActionApplicationConnect, policy.ActionSSH),
})...),
@@ -303,13 +361,13 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
// orgMember has an empty set of permissions, this just implies their membership
// in an organization.
orgMember: func(organizationID string) Role {
orgMember: func(organizationID uuid.UUID) Role {
return Role{
Name: RoleName(orgMember, organizationID),
Identifier: RoleIdentifier{Name: orgMember, OrganizationID: organizationID},
DisplayName: "",
Site: []Permission{},
Org: map[string][]Permission{
organizationID: {
organizationID.String(): {
{
// All org members can read the organization
ResourceType: ResourceOrganization.Type,
@@ -370,7 +428,7 @@ var assignRoles = map[string]map[string]bool{
}
// ExpandableRoles is any type that can be expanded into a []Role. This is implemented
// as an interface so we can have RoleNames for user defined roles, and implement
// as an interface so we can have RoleIdentifiers for user defined roles, and implement
// custom ExpandableRoles for system type users (eg autostart/autostop system role).
// We want a clear divide between the two types of roles so users have no codepath
// to interact or assign system roles.
@@ -381,7 +439,7 @@ type ExpandableRoles interface {
Expand() ([]Role, error)
// Names is for logging and tracing purposes, we want to know the human
// names of the expanded roles.
Names() []string
Names() []RoleIdentifier
}
// Permission is the format passed into the rego.
@@ -424,7 +482,7 @@ func (perm Permission) Valid() error {
// Users of this package should instead **only** use the role names, and
// this package will expand the role names into their json payloads.
type Role struct {
Name string `json:"name"`
Identifier RoleIdentifier `json:"name"`
// DisplayName is used for UI purposes. If the role has no display name,
// that means the UI should never display it.
DisplayName string `json:"display_name"`
@@ -474,10 +532,10 @@ func (roles Roles) Expand() ([]Role, error) {
return roles, nil
}
func (roles Roles) Names() []string {
names := make([]string, 0, len(roles))
func (roles Roles) Names() []RoleIdentifier {
names := make([]RoleIdentifier, 0, len(roles))
for _, r := range roles {
names = append(names, r.Name)
names = append(names, r.Identifier)
}
return names
}
@@ -485,32 +543,22 @@ func (roles Roles) Names() []string {
// CanAssignRole is a helper function that returns true if the user can assign
// the specified role. This also can be used for removing a role.
// This is a simple implementation for now.
func CanAssignRole(expandable ExpandableRoles, assignedRole string) bool {
func CanAssignRole(subjectHasRoles ExpandableRoles, assignedRole RoleIdentifier) bool {
// For CanAssignRole, we only care about the names of the roles.
roles := expandable.Names()
roles := subjectHasRoles.Names()
assigned, assignedOrg, err := RoleSplit(assignedRole)
if err != nil {
return false
}
for _, longRole := range roles {
role, orgID, err := RoleSplit(longRole)
if err != nil {
continue
}
if orgID != "" && orgID != assignedOrg {
for _, myRole := range roles {
if myRole.OrganizationID != uuid.Nil && myRole.OrganizationID != assignedRole.OrganizationID {
// Org roles only apply to the org they are assigned to.
continue
}
allowed, ok := assignRoles[role]
allowedAssignList, ok := assignRoles[myRole.Name]
if !ok {
continue
}
if allowed[assigned] {
if allowedAssignList[assignedRole.Name] {
return true
}
}
@@ -523,29 +571,24 @@ func CanAssignRole(expandable ExpandableRoles, assignedRole string) bool {
// This function is exported so that the Display name can be returned to the
// api. We should maybe make an exported function that returns just the
// human-readable content of the Role struct (name + display name).
func RoleByName(name string) (Role, error) {
roleName, orgID, err := RoleSplit(name)
if err != nil {
return Role{}, xerrors.Errorf("parse role name: %w", err)
}
roleFunc, ok := builtInRoles[roleName]
func RoleByName(name RoleIdentifier) (Role, error) {
roleFunc, ok := builtInRoles[name.Name]
if !ok {
// No role found
return Role{}, xerrors.Errorf("role %q not found", roleName)
return Role{}, xerrors.Errorf("role %q not found", name.String())
}
// Ensure all org roles are properly scoped a non-empty organization id.
// This is just some defensive programming.
role := roleFunc(orgID)
if len(role.Org) > 0 && orgID == "" {
return Role{}, xerrors.Errorf("expect a org id for role %q", roleName)
role := roleFunc(name.OrganizationID)
if len(role.Org) > 0 && name.OrganizationID == uuid.Nil {
return Role{}, xerrors.Errorf("expect a org id for role %q", name.String())
}
return role, nil
}
func rolesByNames(roleNames []string) ([]Role, error) {
func rolesByNames(roleNames []RoleIdentifier) ([]Role, error) {
roles := make([]Role, 0, len(roleNames))
for _, n := range roleNames {
r, err := RoleByName(n)
@@ -557,14 +600,6 @@ func rolesByNames(roleNames []string) ([]Role, error) {
return roles, nil
}
func IsOrgRole(roleName string) (string, bool) {
_, orgID, err := RoleSplit(roleName)
if err == nil && orgID != "" {
return orgID, true
}
return "", false
}
// OrganizationRoles lists all roles that can be applied to an organization user
// in the given organization. This is the list of available roles,
// and specific to an organization.
@@ -574,13 +609,8 @@ func IsOrgRole(roleName string) (string, bool) {
func OrganizationRoles(organizationID uuid.UUID) []Role {
var roles []Role
for _, roleF := range builtInRoles {
role := roleF(organizationID.String())
_, scope, err := RoleSplit(role.Name)
if err != nil {
// This should never happen
continue
}
if scope == organizationID.String() {
role := roleF(organizationID)
if role.Identifier.OrganizationID == organizationID {
roles = append(roles, role)
}
}
@@ -595,13 +625,9 @@ func OrganizationRoles(organizationID uuid.UUID) []Role {
func SiteRoles() []Role {
var roles []Role
for _, roleF := range builtInRoles {
role := roleF("random")
_, scope, err := RoleSplit(role.Name)
if err != nil {
// This should never happen
continue
}
if scope == "" {
// Must provide some non-nil uuid to filter out org roles.
role := roleF(uuid.New())
if !role.Identifier.IsOrgRole() {
roles = append(roles, role)
}
}
@@ -613,8 +639,8 @@ func SiteRoles() []Role {
// removing roles. This set determines the changes, so that the appropriate
// RBAC checks can be applied using "ActionCreate" and "ActionDelete" for
// "added" and "removed" roles respectively.
func ChangeRoleSet(from []string, to []string) (added []string, removed []string) {
has := make(map[string]struct{})
func ChangeRoleSet(from []RoleIdentifier, to []RoleIdentifier) (added []RoleIdentifier, removed []RoleIdentifier) {
has := make(map[RoleIdentifier]struct{})
for _, exists := range from {
has[exists] = struct{}{}
}
@@ -639,34 +665,6 @@ func ChangeRoleSet(from []string, to []string) (added []string, removed []string
return added, removed
}
// RoleName is a quick helper function to return
//
// role_name:scopeID
//
// If no scopeID is required, only 'role_name' is returned
func RoleName(name string, orgID string) string {
if orgID == "" {
return name
}
return name + ":" + orgID
}
func RoleSplit(role string) (name string, orgID string, err error) {
arr := strings.Split(role, ":")
if len(arr) > 2 {
return "", "", xerrors.Errorf("too many colons in role name")
}
if arr[0] == "" {
return "", "", xerrors.Errorf("role cannot be the empty string")
}
if len(arr) == 2 {
return arr[0], arr[1], nil
}
return arr[0], "", nil
}
// Permissions is just a helper function to make building roles that list out resources
// and actions a bit easier.
func Permissions(perms map[string][]policy.Action) []Permission {
+18 -21
View File
@@ -20,7 +20,7 @@ import (
// A possible large improvement would be to implement the ast.Value interface directly.
func BenchmarkRBACValueAllocation(b *testing.B) {
actor := Subject{
Roles: RoleNames{ScopedRoleOrgMember(uuid.New()), ScopedRoleOrgAdmin(uuid.New()), RoleMember()},
Roles: RoleIdentifiers{ScopedRoleOrgMember(uuid.New()), ScopedRoleOrgAdmin(uuid.New()), RoleMember()},
ID: uuid.NewString(),
Scope: ScopeAll,
Groups: []string{uuid.NewString(), uuid.NewString(), uuid.NewString()},
@@ -73,7 +73,7 @@ func TestRegoInputValue(t *testing.T) {
// Expand all roles and make sure we have a good copy.
// This is because these tests modify the roles, and we don't want to
// modify the original roles.
roles, err := RoleNames{ScopedRoleOrgMember(uuid.New()), ScopedRoleOrgAdmin(uuid.New()), RoleMember()}.Expand()
roles, err := RoleIdentifiers{ScopedRoleOrgMember(uuid.New()), ScopedRoleOrgAdmin(uuid.New()), RoleMember()}.Expand()
require.NoError(t, err, "failed to expand roles")
for i := range roles {
// If all cached values are nil, then the role will not use
@@ -213,25 +213,25 @@ func TestRoleByName(t *testing.T) {
testCases := []struct {
Role Role
}{
{Role: builtInRoles[owner]("")},
{Role: builtInRoles[member]("")},
{Role: builtInRoles[templateAdmin]("")},
{Role: builtInRoles[userAdmin]("")},
{Role: builtInRoles[auditor]("")},
{Role: builtInRoles[owner](uuid.Nil)},
{Role: builtInRoles[member](uuid.Nil)},
{Role: builtInRoles[templateAdmin](uuid.Nil)},
{Role: builtInRoles[userAdmin](uuid.Nil)},
{Role: builtInRoles[auditor](uuid.Nil)},
{Role: builtInRoles[orgAdmin]("4592dac5-0945-42fd-828d-a903957d3dbb")},
{Role: builtInRoles[orgAdmin]("24c100c5-1920-49c0-8c38-1b640ac4b38c")},
{Role: builtInRoles[orgAdmin]("4a00f697-0040-4079-b3ce-d24470281a62")},
{Role: builtInRoles[orgAdmin](uuid.New())},
{Role: builtInRoles[orgAdmin](uuid.New())},
{Role: builtInRoles[orgAdmin](uuid.New())},
{Role: builtInRoles[orgMember]("3293c50e-fa5d-414f-a461-01112a4dfb6f")},
{Role: builtInRoles[orgMember]("f88dd23d-bdbd-469d-b82e-36ee06c3d1e1")},
{Role: builtInRoles[orgMember]("02cfd2a5-016c-4d8d-8290-301f5f18023d")},
{Role: builtInRoles[orgMember](uuid.New())},
{Role: builtInRoles[orgMember](uuid.New())},
{Role: builtInRoles[orgMember](uuid.New())},
}
for _, c := range testCases {
c := c
t.Run(c.Role.Name, func(t *testing.T) {
role, err := RoleByName(c.Role.Name)
t.Run(c.Role.Identifier.String(), func(t *testing.T) {
role, err := RoleByName(c.Role.Identifier)
require.NoError(t, err, "role exists")
equalRoles(t, c.Role, role)
})
@@ -242,20 +242,17 @@ func TestRoleByName(t *testing.T) {
t.Run("Errors", func(t *testing.T) {
var err error
_, err = RoleByName("")
_, err = RoleByName(RoleIdentifier{})
require.Error(t, err, "empty role")
_, err = RoleByName("too:many:colons")
require.Error(t, err, "too many colons")
_, err = RoleByName(orgMember)
_, err = RoleByName(RoleIdentifier{Name: orgMember})
require.Error(t, err, "expect orgID")
})
}
// SameAs compares 2 roles for equality.
func equalRoles(t *testing.T, a, b Role) {
require.Equal(t, a.Name, b.Name, "role names")
require.Equal(t, a.Identifier, b.Identifier, "role names")
require.Equal(t, a.DisplayName, b.DisplayName, "role display names")
require.ElementsMatch(t, a.Site, b.Site, "site permissions")
require.ElementsMatch(t, a.User, b.User, "user permissions")
+42 -43
View File
@@ -26,7 +26,7 @@ func TestBuiltInRoles(t *testing.T) {
t.Parallel()
for _, r := range rbac.SiteRoles() {
r := r
t.Run(r.Name, func(t *testing.T) {
t.Run(r.Identifier.String(), func(t *testing.T) {
t.Parallel()
require.NoError(t, r.Valid(), "invalid role")
})
@@ -34,7 +34,7 @@ func TestBuiltInRoles(t *testing.T) {
for _, r := range rbac.OrganizationRoles(uuid.New()) {
r := r
t.Run(r.Name, func(t *testing.T) {
t.Run(r.Identifier.String(), func(t *testing.T) {
t.Parallel()
require.NoError(t, r.Valid(), "invalid role")
})
@@ -45,7 +45,7 @@ func TestBuiltInRoles(t *testing.T) {
func TestOwnerExec(t *testing.T) {
owner := rbac.Subject{
ID: uuid.NewString(),
Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOwner()},
Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleOwner()},
Scope: rbac.ScopeAll,
}
@@ -98,17 +98,17 @@ func TestRolePermissions(t *testing.T) {
apiKeyID := uuid.New()
// Subjects to user
memberMe := authSubject{Name: "member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleNames{rbac.RoleMember()}}}
orgMemberMe := authSubject{Name: "org_member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID)}}}
memberMe := authSubject{Name: "member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember()}}}
orgMemberMe := authSubject{Name: "org_member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID)}}}
owner := authSubject{Name: "owner", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOwner()}}}
orgAdmin := authSubject{Name: "org_admin", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID), rbac.ScopedRoleOrgAdmin(orgID)}}}
owner := authSubject{Name: "owner", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleOwner()}}}
orgAdmin := authSubject{Name: "org_admin", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID), rbac.ScopedRoleOrgAdmin(orgID)}}}
otherOrgMember := authSubject{Name: "org_member_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg)}}}
otherOrgAdmin := authSubject{Name: "org_admin_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg), rbac.ScopedRoleOrgAdmin(otherOrg)}}}
otherOrgMember := authSubject{Name: "org_member_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg)}}}
otherOrgAdmin := authSubject{Name: "org_admin_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg), rbac.ScopedRoleOrgAdmin(otherOrg)}}}
templateAdmin := authSubject{Name: "template-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleTemplateAdmin()}}}
userAdmin := authSubject{Name: "user-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleUserAdmin()}}}
templateAdmin := authSubject{Name: "template-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleTemplateAdmin()}}}
userAdmin := authSubject{Name: "user-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleUserAdmin()}}}
// requiredSubjects are required to be asserted in each test case. This is
// to make sure one is not forgotten.
@@ -616,50 +616,40 @@ func TestIsOrgRole(t *testing.T) {
require.NoError(t, err)
testCases := []struct {
RoleName string
OrgRole bool
OrgID string
Identifier rbac.RoleIdentifier
OrgRole bool
OrgID uuid.UUID
}{
// Not org roles
{RoleName: rbac.RoleOwner()},
{RoleName: rbac.RoleMember()},
{RoleName: "auditor"},
{Identifier: rbac.RoleOwner()},
{Identifier: rbac.RoleMember()},
{Identifier: rbac.RoleAuditor()},
{
RoleName: "a:bad:role",
OrgRole: false,
},
{
RoleName: "",
OrgRole: false,
Identifier: rbac.RoleIdentifier{},
OrgRole: false,
},
// Org roles
{
RoleName: rbac.ScopedRoleOrgAdmin(randomUUID),
OrgRole: true,
OrgID: randomUUID.String(),
Identifier: rbac.ScopedRoleOrgAdmin(randomUUID),
OrgRole: true,
OrgID: randomUUID,
},
{
RoleName: rbac.ScopedRoleOrgMember(randomUUID),
OrgRole: true,
OrgID: randomUUID.String(),
},
{
RoleName: "test:example",
OrgRole: true,
OrgID: "example",
Identifier: rbac.ScopedRoleOrgMember(randomUUID),
OrgRole: true,
OrgID: randomUUID,
},
}
// nolint:paralleltest
for _, c := range testCases {
c := c
t.Run(c.RoleName, func(t *testing.T) {
t.Run(c.Identifier.String(), func(t *testing.T) {
t.Parallel()
orgID, ok := rbac.IsOrgRole(c.RoleName)
ok := c.Identifier.IsOrgRole()
require.Equal(t, c.OrgRole, ok, "match expected org role")
require.Equal(t, c.OrgID, orgID, "match expected org id")
require.Equal(t, c.OrgID, c.Identifier.OrganizationID, "match expected org id")
})
}
}
@@ -670,7 +660,7 @@ func TestListRoles(t *testing.T) {
siteRoles := rbac.SiteRoles()
siteRoleNames := make([]string, 0, len(siteRoles))
for _, role := range siteRoles {
siteRoleNames = append(siteRoleNames, role.Name)
siteRoleNames = append(siteRoleNames, role.Identifier.Name)
}
// If this test is ever failing, just update the list to the roles
@@ -690,7 +680,7 @@ func TestListRoles(t *testing.T) {
orgRoles := rbac.OrganizationRoles(orgID)
orgRoleNames := make([]string, 0, len(orgRoles))
for _, role := range orgRoles {
orgRoleNames = append(orgRoleNames, role.Name)
orgRoleNames = append(orgRoleNames, role.Identifier.String())
}
require.ElementsMatch(t, []string{
@@ -738,13 +728,22 @@ func TestChangeSet(t *testing.T) {
},
}
convert := func(s []string) rbac.RoleIdentifiers {
tmp := make([]rbac.RoleIdentifier, 0, len(s))
for _, e := range s {
tmp = append(tmp, rbac.RoleIdentifier{Name: e})
}
return tmp
}
for _, c := range testCases {
c := c
t.Run(c.Name, func(t *testing.T) {
t.Parallel()
add, remove := rbac.ChangeRoleSet(c.From, c.To)
require.ElementsMatch(t, c.ExpAdd, add, "expect added")
require.ElementsMatch(t, c.ExpRemove, remove, "expect removed")
add, remove := rbac.ChangeRoleSet(convert(c.From), convert(c.To))
require.ElementsMatch(t, convert(c.ExpAdd), add, "expect added")
require.ElementsMatch(t, convert(c.ExpRemove), remove, "expect removed")
})
}
}
+7 -26
View File
@@ -39,14 +39,14 @@ func roleCache(ctx context.Context) *syncmap.Map[string, rbac.Role] {
}
// Expand will expand built in roles, and fetch custom roles from the database.
func Expand(ctx context.Context, db database.Store, names []string) (rbac.Roles, error) {
func Expand(ctx context.Context, db database.Store, names []rbac.RoleIdentifier) (rbac.Roles, error) {
if len(names) == 0 {
// That was easy
return []rbac.Role{}, nil
}
cache := roleCache(ctx)
lookup := make([]string, 0)
lookup := make([]rbac.RoleIdentifier, 0)
roles := make([]rbac.Role, 0, len(names))
for _, name := range names {
@@ -58,7 +58,7 @@ func Expand(ctx context.Context, db database.Store, names []string) (rbac.Roles,
}
// Check custom role cache
customRole, ok := cache.Load(name)
customRole, ok := cache.Load(name.String())
if ok {
roles = append(roles, customRole)
continue
@@ -69,26 +69,11 @@ func Expand(ctx context.Context, db database.Store, names []string) (rbac.Roles,
}
if len(lookup) > 0 {
// The set of roles coming in are formatted as 'rolename[:<org_id>]'.
// In the database, org roles are scoped with an organization column.
lookupArgs := make([]database.NameOrganizationPair, 0, len(lookup))
for _, name := range lookup {
roleName, orgID, err := rbac.RoleSplit(name)
if err != nil {
continue
}
parsedOrgID := uuid.Nil // Default to no org ID
if orgID != "" {
parsedOrgID, err = uuid.Parse(orgID)
if err != nil {
continue
}
}
lookupArgs = append(lookupArgs, database.NameOrganizationPair{
Name: roleName,
OrganizationID: parsedOrgID,
Name: name.Name,
OrganizationID: name.OrganizationID,
})
}
@@ -111,7 +96,7 @@ func Expand(ctx context.Context, db database.Store, names []string) (rbac.Roles,
return nil, xerrors.Errorf("convert db role %q: %w", dbrole.Name, err)
}
roles = append(roles, converted)
cache.Store(dbrole.Name, converted)
cache.Store(dbrole.RoleIdentifier().String(), converted)
}
}
@@ -133,12 +118,8 @@ func convertPermissions(dbPerms []database.CustomRolePermission) []rbac.Permissi
// ConvertDBRole should not be used by any human facing apis. It is used
// for authz purposes.
func ConvertDBRole(dbRole database.CustomRole) (rbac.Role, error) {
name := dbRole.Name
if dbRole.OrganizationID.Valid {
name = rbac.RoleName(dbRole.Name, dbRole.OrganizationID.UUID.String())
}
role := rbac.Role{
Name: name,
Identifier: dbRole.RoleIdentifier(),
DisplayName: dbRole.DisplayName,
Site: convertPermissions(dbRole.SitePermissions),
Org: nil,
+1 -1
View File
@@ -35,7 +35,7 @@ func TestExpandCustomRoleRoles(t *testing.T) {
})
ctx := testutil.Context(t, testutil.WaitShort)
roles, err := rolestore.Expand(ctx, db, []string{rbac.RoleName(roleName, org.ID.String())})
roles, err := rolestore.Expand(ctx, db, []rbac.RoleIdentifier{{Name: roleName, OrganizationID: org.ID}})
require.NoError(t, err)
require.Len(t, roles, 1, "role found")
}
+7 -7
View File
@@ -58,7 +58,7 @@ var builtinScopes = map[ScopeName]Scope{
// authorize checks it is usually not used directly and skips scope checks.
ScopeAll: {
Role: Role{
Name: fmt.Sprintf("Scope_%s", ScopeAll),
Identifier: RoleIdentifier{Name: fmt.Sprintf("Scope_%s", ScopeAll)},
DisplayName: "All operations",
Site: Permissions(map[string][]policy.Action{
ResourceWildcard.Type: {policy.WildcardSymbol},
@@ -71,7 +71,7 @@ var builtinScopes = map[ScopeName]Scope{
ScopeApplicationConnect: {
Role: Role{
Name: fmt.Sprintf("Scope_%s", ScopeApplicationConnect),
Identifier: RoleIdentifier{Name: fmt.Sprintf("Scope_%s", ScopeApplicationConnect)},
DisplayName: "Ability to connect to applications",
Site: Permissions(map[string][]policy.Action{
ResourceWorkspace.Type: {policy.ActionApplicationConnect},
@@ -87,7 +87,7 @@ type ExpandableScope interface {
Expand() (Scope, error)
// Name is for logging and tracing purposes, we want to know the human
// name of the scope.
Name() string
Name() RoleIdentifier
}
type ScopeName string
@@ -96,8 +96,8 @@ func (name ScopeName) Expand() (Scope, error) {
return ExpandScope(name)
}
func (name ScopeName) Name() string {
return string(name)
func (name ScopeName) Name() RoleIdentifier {
return RoleIdentifier{Name: string(name)}
}
// Scope acts the exact same as a Role with the addition that is can also
@@ -114,8 +114,8 @@ func (s Scope) Expand() (Scope, error) {
return s, nil
}
func (s Scope) Name() string {
return s.Role.Name
func (s Scope) Name() RoleIdentifier {
return s.Role.Identifier
}
func ExpandScope(scope ScopeName) (Scope, error) {
+7 -7
View File
@@ -24,13 +24,13 @@ func TestSubjectEqual(t *testing.T) {
Name: "Same",
A: rbac.Subject{
ID: "id",
Roles: rbac.RoleNames{rbac.RoleMember()},
Roles: rbac.RoleIdentifiers{rbac.RoleMember()},
Groups: []string{"group"},
Scope: rbac.ScopeAll,
},
B: rbac.Subject{
ID: "id",
Roles: rbac.RoleNames{rbac.RoleMember()},
Roles: rbac.RoleIdentifiers{rbac.RoleMember()},
Groups: []string{"group"},
Scope: rbac.ScopeAll,
},
@@ -49,7 +49,7 @@ func TestSubjectEqual(t *testing.T) {
{
Name: "RolesNilVs0",
A: rbac.Subject{
Roles: rbac.RoleNames{},
Roles: rbac.RoleIdentifiers{},
},
B: rbac.Subject{
Roles: nil,
@@ -69,20 +69,20 @@ func TestSubjectEqual(t *testing.T) {
{
Name: "DifferentRoles",
A: rbac.Subject{
Roles: rbac.RoleNames{rbac.RoleMember()},
Roles: rbac.RoleIdentifiers{rbac.RoleMember()},
},
B: rbac.Subject{
Roles: rbac.RoleNames{rbac.RoleOwner()},
Roles: rbac.RoleIdentifiers{rbac.RoleOwner()},
},
Expected: false,
},
{
Name: "Different#Roles",
A: rbac.Subject{
Roles: rbac.RoleNames{rbac.RoleMember()},
Roles: rbac.RoleIdentifiers{rbac.RoleMember()},
},
B: rbac.Subject{
Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOwner()},
Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleOwner()},
},
Expected: false,
},