Files
threshold/internal/perms/output_fields_test.go
T
Jeff Mitchell fc664eb35f Improve grant validation (#3081)
This adds several relationships into the resource package that are then
made use of to improve the grant validation that runs during Parse. By
understanding mappings between ID and resource type the validation
checks can far better align with actual allowed grant formats so we can
find several common misunderstandings before they simply result in an
authorization failure.

In the future some of these functions could be used in the logic in the
actual ACL checks but it's not currently necessary and I don't want to
lump that change in with this.

Note that the tests have not meaningfully changed, but they _have_ had
to be updated because the stricter validation is actually catching
issues.
2023-03-16 10:11:55 -04:00

357 lines
8.7 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package perms
import (
"testing"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_OutputFields(t *testing.T) {
t.Parallel()
type input struct {
name string
fields []string
startMap *OutputFields
resMap *OutputFields
resStar bool
}
tests := []input{
{
name: "nil map, add nil",
resMap: &OutputFields{},
},
{
name: "nil map, add empty fields",
fields: []string{},
resMap: &OutputFields{
fields: map[string]bool{},
},
},
{
name: "nil map, add fields",
fields: []string{"id", "version"},
resMap: &OutputFields{
fields: map[string]bool{
"id": true,
"version": true,
},
},
},
{
name: "existing map, add nil",
startMap: &OutputFields{
fields: map[string]bool{
"id": true,
"version": true,
},
},
resMap: &OutputFields{
fields: map[string]bool{
"id": true,
"version": true,
},
},
},
{
name: "existing with star, add nil",
startMap: &OutputFields{
fields: map[string]bool{
"*": true,
},
},
resMap: &OutputFields{
fields: map[string]bool{
"*": true,
},
},
resStar: true,
},
{
name: "existing with star, add new",
fields: []string{"id", "version"},
startMap: &OutputFields{
fields: map[string]bool{
"*": true,
},
},
resMap: &OutputFields{
fields: map[string]bool{
"*": true,
},
},
resStar: true,
},
{
name: "existing without star, add new",
fields: []string{"id", "version"},
startMap: &OutputFields{
fields: map[string]bool{
"name": true,
},
},
resMap: &OutputFields{
fields: map[string]bool{
"id": true,
"version": true,
"name": true,
},
},
},
{
name: "existing without star, add star",
fields: []string{"id", "*"},
startMap: &OutputFields{
fields: map[string]bool{
"name": true,
},
},
resMap: &OutputFields{
fields: map[string]bool{
"*": true,
},
},
resStar: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert := assert.New(t)
out := test.startMap.AddFields(test.fields)
assert.True(out.Has("*") == test.resStar)
assert.Equal(test.resMap, out)
})
}
}
func Test_ACLOutputFields(t *testing.T) {
t.Parallel()
type input struct {
name string
grants []string
resource Resource
action action.Type
fields []string
authorized bool
}
tests := []input{
{
name: "default",
resource: Resource{ScopeId: "o_myorg", Id: "u_bar", Type: resource.Role},
action: action.Read,
grants: []string{"id=u_bar;actions=read,update"},
authorized: true,
},
{
name: "single value",
resource: Resource{ScopeId: "o_myorg", Id: "u_bar", Type: resource.Role},
grants: []string{"id=u_bar;actions=read,update;output_fields=id"},
action: action.Read,
fields: []string{"id"},
authorized: true,
},
{
name: "compound no overlap",
resource: Resource{ScopeId: "o_myorg", Id: "u_bar", Type: resource.Role},
grants: []string{
"id=u_bar;actions=read,update;output_fields=id",
"id=*;type=host-catalog;actions=read,update;output_fields=version",
},
action: action.Read,
fields: []string{"id"},
authorized: true,
},
{
name: "compound",
resource: Resource{ScopeId: "o_myorg", Id: "u_bar", Type: resource.Role},
grants: []string{
"id=u_bar;actions=read,update;output_fields=id",
"id=*;type=role;output_fields=version",
},
action: action.Read,
fields: []string{"id", "version"},
authorized: true,
},
{
name: "wildcard with type",
resource: Resource{ScopeId: "o_myorg", Id: "u_bar", Type: resource.Role},
grants: []string{
"id=u_bar;actions=read,update;output_fields=read",
"id=*;type=role;output_fields=*",
},
action: action.Read,
fields: []string{"*"},
authorized: true,
},
{
name: "wildcard with wildcard type",
resource: Resource{ScopeId: "o_myorg", Id: "u_bar", Type: resource.Role},
grants: []string{
"id=u_bar;actions=read,update;output_fields=read",
"id=*;type=*;output_fields=*",
},
action: action.Read,
fields: []string{"*"},
authorized: true,
},
{
name: "subaction exact",
resource: Resource{ScopeId: "o_myorg", Id: "u_bar", Type: resource.Role},
grants: []string{
"id=u_bar;actions=read:self,update;output_fields=version",
},
action: action.ReadSelf,
fields: []string{"version"},
authorized: true,
},
{
// If the action is a subaction, parent output fields will apply, in
// addition to subaction. This matches authorization.
name: "subaction parent action",
resource: Resource{ScopeId: "o_myorg", Id: "u_bar", Type: resource.Role},
grants: []string{
"id=u_bar;actions=read,update;output_fields=version",
"id=u_bar;actions=read:self;output_fields=id",
},
action: action.ReadSelf,
fields: []string{"id", "version"},
authorized: true,
},
{
// The inverse isn't true. Similarly to authorization, if you have
// specific output fields on a self action, they don't apply to
// non-self actions. This is useful to allow more visibility to self
// actions and less in the general case.
name: "subaction child action",
resource: Resource{ScopeId: "o_myorg", Id: "u_bar", Type: resource.Role},
grants: []string{
"id=u_bar;actions=read:self,update;output_fields=version",
"id=u_bar;actions=read;output_fields=id",
},
action: action.Read,
fields: []string{"id"},
authorized: true,
},
{
name: "initial grant unauthorized with star",
resource: Resource{ScopeId: "o_myorg", Id: "u_bar", Type: resource.Role},
grants: []string{
"id=u_bar;output_fields=*",
"id=u_bar;actions=delete;output_fields=id",
},
action: action.Delete,
fields: []string{"*"},
authorized: true,
},
{
name: "unauthorized id only",
resource: Resource{ScopeId: "o_myorg", Id: "u_bar", Type: resource.Role},
grants: []string{
"id=u_bar;output_fields=name",
},
action: action.Delete,
fields: []string{"name"},
},
{
name: "unauthorized type only",
resource: Resource{ScopeId: "o_myorg", Type: resource.Role},
grants: []string{
"type=role;output_fields=name",
},
action: action.List,
fields: []string{"name"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var grants []Grant
for _, g := range test.grants {
grant, err := Parse("o_myorg", g)
require.NoError(t, err)
grants = append(grants, grant)
}
acl := NewACL(grants...)
results := acl.Allowed(test.resource, test.action, globals.AnonymousUserId, WithSkipAnonymousUserRestrictions(true))
fields, _ := results.OutputFields.Fields()
assert.ElementsMatch(t, fields, test.fields)
assert.True(t, test.authorized == results.Authorized)
})
}
}
func Test_ACLSelfOrDefault(t *testing.T) {
t.Parallel()
type input struct {
name string
input *OutputFields
output *OutputFields
userId string
}
tests := []input{
{
name: "nil, no user ID, set but empty",
output: &OutputFields{
fields: map[string]bool{},
},
},
{
name: "nil, non anon id",
output: &OutputFields{
fields: map[string]bool{
"*": true,
},
},
userId: "u_abc123",
},
{
name: "nil, anon id",
output: &OutputFields{
fields: map[string]bool{
globals.IdField: true,
globals.ScopeField: true,
globals.ScopeIdField: true,
globals.PluginField: true,
globals.PluginIdField: true,
globals.NameField: true,
globals.DescriptionField: true,
globals.TypeField: true,
globals.IsPrimaryField: true,
globals.PrimaryAuthMethodIdField: true,
globals.AuthorizedActionsField: true,
globals.AuthorizedCollectionActionsField: true,
},
},
userId: globals.AnonymousUserId,
},
{
name: "not nil",
input: &OutputFields{
fields: map[string]bool{
"foo": true,
},
},
output: &OutputFields{
fields: map[string]bool{
"foo": true,
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.output, test.input.SelfOrDefaults(test.userId))
})
}
}