You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/perms/output_fields_test.go

354 lines
8.6 KiB

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: "bar", Type: resource.Role},
action: action.Read,
grants: []string{"id=bar;actions=read,update"},
authorized: true,
},
{
name: "single value",
resource: Resource{ScopeId: "o_myorg", Id: "bar", Type: resource.Role},
grants: []string{"id=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: "bar", Type: resource.Role},
grants: []string{
"id=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: "bar", Type: resource.Role},
grants: []string{
"id=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: "bar", Type: resource.Role},
grants: []string{
"id=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: "bar", Type: resource.Role},
grants: []string{
"id=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: "bar", Type: resource.Role},
grants: []string{
"id=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: "bar", Type: resource.Role},
grants: []string{
"id=bar;actions=read,update;output_fields=version",
"id=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: "bar", Type: resource.Role},
grants: []string{
"id=bar;actions=read:self,update;output_fields=version",
"id=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: "bar", Type: resource.Role},
grants: []string{
"id=bar;output_fields=*",
"id=bar;actions=delete;output_fields=id",
},
action: action.Delete,
fields: []string{"*"},
authorized: true,
},
{
name: "unauthorized id only",
resource: Resource{ScopeId: "o_myorg", Id: "bar", Type: resource.Role},
grants: []string{
"id=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))
})
}
}