Files
threshold/internal/perms/grants_test.go
T
Jeff Mitchell 62d4f8453b Don't explode grants from the DB (#5104)
This removes the Cartesian product in the DB for GrantsForUser in favor
of returning the actual grant scope information and dealing with it in
application code.

(cherry picked from commit 913cd536823d73994acc4f4d833208c1cc05f91b)
2024-09-24 14:18:53 +00:00

1145 lines
34 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package perms
import (
"context"
"fmt"
"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_ActionParsingValidation(t *testing.T) {
t.Parallel()
ctx := context.Background()
type input struct {
name string
input Grant
errResult string
result Grant
}
tests := []input{
{
name: "no actions",
errResult: "perms.(Grant).parseAndValidateActions: missing actions: parameter violation: error #100",
},
{
name: "empty action",
input: Grant{
actionsBeingParsed: []string{"create", "", "read"},
},
errResult: "perms.(Grant).parseAndValidateActions: empty action found: parameter violation: error #100",
},
{
name: "empty action with output fields",
input: Grant{
OutputFields: &OutputFields{
fields: map[string]bool{
"id": true,
},
},
},
result: Grant{
OutputFields: &OutputFields{
fields: map[string]bool{
"id": true,
},
},
},
},
{
name: "unknown action",
input: Grant{
actionsBeingParsed: []string{"create", "foobar", "read"},
},
errResult: `perms.(Grant).parseAndValidateActions: unknown action "foobar": parameter violation: error #100`,
},
{
name: "all",
input: Grant{
actionsBeingParsed: []string{"*"},
},
result: Grant{
actions: map[action.Type]bool{
action.All: true,
},
},
},
{
name: "all valid plus all",
input: Grant{
actionsBeingParsed: []string{"list", "create", "update", "*", "read", "delete", "authenticate", "authorize-session"},
},
errResult: `perms.(Grant).parseAndValidateActions: "*" cannot be specified with other actions: parameter violation: error #100`,
},
{
name: "all valid",
input: Grant{
actionsBeingParsed: []string{"list", "create", "update", "read", "delete", "authenticate", "authorize-session"},
},
result: Grant{
actions: map[action.Type]bool{
action.List: true,
action.Create: true,
action.Update: true,
action.Read: true,
action.Delete: true,
action.Authenticate: true,
action.AuthorizeSession: true,
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.input.parseAndValidateActions(ctx)
if test.errResult == "" {
require.NoError(t, err)
assert.Equal(t, test.result, test.input)
} else {
require.Error(t, err)
assert.Equal(t, test.errResult, err.Error())
}
})
}
}
func Test_ValidateType(t *testing.T) {
t.Parallel()
ctx := context.Background()
var g Grant
for i := resource.Unknown; i <= resource.Alias; i++ {
g.typ = i
if i == resource.Controller {
assert.Error(t, g.validateType(ctx))
} else {
assert.NoError(t, g.validateType(ctx))
}
}
}
func Test_MarshalingAndCloning(t *testing.T) {
t.Parallel()
type input struct {
name string
input Grant
jsonOutput string
canonicalString string
}
tests := []input{
{
name: "empty",
jsonOutput: `{}`,
canonicalString: ``,
},
{
name: "type and id",
input: Grant{
id: "baz",
typ: resource.Group,
},
jsonOutput: `{"id":"baz","type":"group"}`,
canonicalString: `id=baz;type=group`,
},
{
name: "type and ids",
input: Grant{
ids: []string{"baz", "bop"},
typ: resource.Group,
},
jsonOutput: `{"ids":["baz","bop"],"type":"group"}`,
canonicalString: `ids=baz,bop;type=group`,
},
{
name: "type and ids single id",
input: Grant{
ids: []string{"baz"},
typ: resource.Group,
},
jsonOutput: `{"ids":["baz"],"type":"group"}`,
canonicalString: `ids=baz;type=group`,
},
{
name: "output fields id",
input: Grant{
id: "baz",
typ: resource.Group,
OutputFields: &OutputFields{
fields: map[string]bool{
"name": true,
"version": true,
"id": true,
},
},
},
jsonOutput: `{"id":"baz","output_fields":["id","name","version"],"type":"group"}`,
canonicalString: `id=baz;type=group;output_fields=id,name,version`,
},
{
name: "output fields ids",
input: Grant{
ids: []string{"baz", "bop"},
typ: resource.Group,
OutputFields: &OutputFields{
fields: map[string]bool{
"name": true,
"version": true,
"id": true,
},
},
},
jsonOutput: `{"ids":["baz","bop"],"output_fields":["id","name","version"],"type":"group"}`,
canonicalString: `ids=baz,bop;type=group;output_fields=id,name,version`,
},
{
name: "everything id",
input: Grant{
id: "baz",
typ: resource.Group,
actions: map[action.Type]bool{
action.Create: true,
action.Read: true,
},
actionsBeingParsed: []string{"create", "read"},
OutputFields: &OutputFields{
fields: map[string]bool{
"name": true,
"version": true,
"id": true,
},
},
},
jsonOutput: `{"actions":["create","read"],"id":"baz","output_fields":["id","name","version"],"type":"group"}`,
canonicalString: `id=baz;type=group;actions=create,read;output_fields=id,name,version`,
},
{
name: "everything ids",
input: Grant{
ids: []string{"baz", "bop"},
typ: resource.Group,
actions: map[action.Type]bool{
action.Create: true,
action.Read: true,
},
actionsBeingParsed: []string{"create", "read"},
OutputFields: &OutputFields{
fields: map[string]bool{
"name": true,
"version": true,
"ids": true,
},
},
},
jsonOutput: `{"actions":["create","read"],"ids":["baz","bop"],"output_fields":["ids","name","version"],"type":"group"}`,
canonicalString: `ids=baz,bop;type=group;actions=create,read;output_fields=ids,name,version`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
output, err := test.input.MarshalJSON()
require.NoError(t, err)
assert.Equal(t, test.jsonOutput, string(output))
assert.Equal(t, test.canonicalString, test.input.CanonicalString())
assert.Equal(t, &test.input, test.input.clone())
})
}
}
func Test_Unmarshaling(t *testing.T) {
t.Parallel()
ctx := context.Background()
type input struct {
name string
jsonInput string
textInput string
jsonErr string
textErr string
expected Grant
}
tests := []input{
{
name: "empty",
expected: Grant{},
jsonInput: `{}`,
textInput: ``,
textErr: `segment "" not formatted correctly, wrong number of equal signs`,
},
{
name: "bad json",
jsonInput: `w329uf`,
jsonErr: "perms.(Grant).unmarshalJSON: error occurred during decode, encoding issue: error #303: invalid character 'w' looking for beginning of value",
},
{
name: "bad segment id",
jsonInput: `{"id":true}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "id" as string: parameter violation: error #100`,
textInput: `id=`,
textErr: `perms.(Grant).unmarshalText: segment "id=" not formatted correctly, missing value: parameter violation: error #100`,
},
{
name: "bad segment id comma",
jsonInput: `{"id":","}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
textInput: `id=,`,
textErr: `perms.(Grant).unmarshalText: ID cannot contain a comma: parameter violation: error #100`,
},
{
name: "bad segment id start with comma",
jsonInput: `{"id":",something"}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
textInput: `id=,something`,
textErr: `perms.(Grant).unmarshalText: ID cannot contain a comma: parameter violation: error #100`,
},
{
name: "bad segment id with comma",
jsonInput: `{"id":"some,thing"}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
textInput: `id=some,thing`,
textErr: `perms.(Grant).unmarshalText: ID cannot contain a comma: parameter violation: error #100`,
},
{
name: "bad segment id end with comma",
jsonInput: `{"id":"something,"}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
textInput: `id=something,`,
textErr: `perms.(Grant).unmarshalText: ID cannot contain a comma: parameter violation: error #100`,
},
{
name: "bad segment ids",
jsonInput: `{"ids":true}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "ids" as array: parameter violation: error #100`,
textInput: `ids=`,
textErr: `perms.(Grant).unmarshalText: segment "ids=" not formatted correctly, missing value: parameter violation: error #100`,
},
{
name: "empty segment ids",
jsonInput: `{"ids":[""]}`,
jsonErr: `perms.(Grant).unmarshalJSON: empty ID provided: parameter violation: error #100`,
textInput: `ids=,`,
textErr: `perms.(Grant).unmarshalText: empty ID provided: parameter violation: error #100`,
},
{
name: "segment ids comma",
jsonInput: `{"ids":[","]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids starting with comma",
jsonInput: `{"ids":[",something"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids with comma",
jsonInput: `{"ids":["some,thing"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids ending with comma",
jsonInput: `{"ids":["something,"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids semicolon",
jsonInput: `{"ids":[","]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids starting with semicolon",
jsonInput: `{"ids":[";something"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids with semicolon",
jsonInput: `{"ids":["some;thing"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids ending with semicolon",
jsonInput: `{"ids":["something;"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids equals sign",
jsonInput: `{"ids":["="]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids starting with equals sign",
jsonInput: `{"ids":["=something"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids with equals sign",
jsonInput: `{"ids":["some=thing"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids ending with equals sign",
jsonInput: `{"ids":["something="]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "good id",
expected: Grant{
id: "foobar",
},
jsonInput: `{"id":"foobar"}`,
textInput: `id=foobar`,
},
{
name: "good ids",
expected: Grant{
ids: []string{"foobar"},
},
jsonInput: `{"ids":["foobar"]}`,
textInput: `ids=foobar`,
},
{
name: "bad id",
jsonInput: `{"id":true}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "id" as string: parameter violation: error #100`,
textInput: `=id`,
textErr: `perms.(Grant).unmarshalText: segment "=id" not formatted correctly, missing key: parameter violation: error #100`,
},
{
name: "bad ids",
jsonInput: `{"ids":true}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "ids" as array: parameter violation: error #100`,
textInput: `=ids`,
textErr: `perms.(Grant).unmarshalText: segment "=ids" not formatted correctly, missing key: parameter violation: error #100`,
},
{
name: "good type",
expected: Grant{
typ: resource.HostCatalog,
},
jsonInput: `{"type":"host-catalog"}`,
textInput: `type=host-catalog`,
},
{
name: "bad type",
jsonInput: `{"type":true}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "type" as string: parameter violation: error #100`,
textInput: `type=host-catalog=id`,
textErr: `perms.(Grant).unmarshalText: segment "type=host-catalog=id" not formatted correctly, wrong number of equal signs: parameter violation: error #100`,
},
{
name: "good output fields id",
expected: Grant{
OutputFields: &OutputFields{
fields: map[string]bool{
"name": true,
"version": true,
"id": true,
},
},
},
jsonInput: `{"output_fields":["id","name","version"]}`,
textInput: `output_fields=id,version,name`,
},
{
name: "good output fields ids",
expected: Grant{
OutputFields: &OutputFields{
fields: map[string]bool{
"name": true,
"version": true,
"ids": true,
},
},
},
jsonInput: `{"output_fields":["ids","name","version"]}`,
textInput: `output_fields=ids,version,name`,
},
{
name: "bad output fields id",
jsonInput: `{"output_fields":true}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "output_fields" as array: parameter violation: error #100`,
textInput: `output_fields=id=version,name`,
textErr: `perms.(Grant).unmarshalText: segment "output_fields=id=version,name" not formatted correctly, wrong number of equal signs: parameter violation: error #100`,
},
{
name: "bad output fields ids",
jsonInput: `{"output_fields":true}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "output_fields" as array: parameter violation: error #100`,
textInput: `output_fields=ids=version,name`,
textErr: `perms.(Grant).unmarshalText: segment "output_fields=ids=version,name" not formatted correctly, wrong number of equal signs: parameter violation: error #100`,
},
{
name: "bad output fields comma",
jsonInput: `{"output_fields":[","]}`,
jsonErr: `perms.(Grant).unmarshalJSON: output fields cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "output fields starting with comma",
jsonInput: `{"output_fields":[",something"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: output fields cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "output fields with comma",
jsonInput: `{"output_fields":["some,thing"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: output fields cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "output fields ending with comma",
jsonInput: `{"output_fields":["something,"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: output fields cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids semicolon",
jsonInput: `{"ids":[";"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids starting with semicolon",
jsonInput: `{"ids":[";something"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids with semicolon",
jsonInput: `{"ids":["some;thing"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids ending with semicolon",
jsonInput: `{"ids":["something;"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids equals sign",
jsonInput: `{"ids":["="]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids starting with equals sign",
jsonInput: `{"ids":["=something"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids with equals sign",
jsonInput: `{"ids":["some=thing"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment ids ending with equals sign",
jsonInput: `{"ids":["something="]}`,
jsonErr: `perms.(Grant).unmarshalJSON: ID cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "good actions",
expected: Grant{
actionsBeingParsed: []string{"create", "read"},
},
jsonInput: `{"actions":["create","read"]}`,
textInput: `actions=create,read`,
},
{
name: "bad actions",
jsonInput: `{"actions":true}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "actions" as array: parameter violation: error #100`,
textInput: `type=host-catalog=id`,
textErr: `perms.(Grant).unmarshalText: segment "type=host-catalog=id" not formatted correctly, wrong number of equal signs: parameter violation: error #100`,
},
{
name: "empty actions",
jsonInput: `{"actions":[""]}`,
jsonErr: `perms.(Grant).unmarshalJSON: empty action found: parameter violation: error #100`,
textInput: `actions=,`,
textErr: `perms.(Grant).unmarshalText: empty action found: parameter violation: error #100`,
},
{
name: "bad json action",
jsonInput: `{"actions":[1, true]}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret 1 in actions array as string: parameter violation: error #100`,
},
{
name: "segment actions comma",
jsonInput: `{"actions":[","]}`,
jsonErr: `perms.(Grant).unmarshalJSON: action cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment actions starting with comma",
jsonInput: `{"actions":[",something"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: action cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment actions with comma",
jsonInput: `{"actions":["some,thing"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: action cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
{
name: "segment actions ending with comma",
jsonInput: `{"actions":["something,"]}`,
jsonErr: `perms.(Grant).unmarshalJSON: action cannot contain a comma, semicolon or equals sign: parameter violation: error #100`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
var g Grant
if test.jsonInput != "" {
err := g.unmarshalJSON(ctx, []byte(test.jsonInput))
if test.jsonErr != "" {
require.Error(err)
assert.Equal(test.jsonErr, err.Error())
} else {
require.NoError(err)
assert.Equal(test.expected, g)
}
}
g = Grant{}
if test.textInput != "" {
err := g.unmarshalText(ctx, test.textInput)
if test.textErr != "" {
require.Error(err)
assert.Equal(test.textErr, err.Error())
} else {
require.NoError(err)
assert.Equal(test.expected, g)
}
}
})
}
}
func Test_Parse(t *testing.T) {
t.Parallel()
ctx := context.Background()
type input struct {
name string
input string
userId string
accountId string
err string
scopeOverride string
expected Grant
}
tests := []input{
{
name: "empty output fields",
input: "id=*;type=*;actions=read,list;output_fields=",
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
id: "*",
typ: resource.All,
actions: map[action.Type]bool{
action.Read: true,
action.List: true,
},
OutputFields: &OutputFields{
fields: make(map[string]bool),
},
},
},
{
name: "empty output fields json",
input: `{"id": "*", "type": "*", "actions": ["read", "list"], "output_fields": []}`,
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
id: "*",
typ: resource.All,
actions: map[action.Type]bool{
action.Read: true,
action.List: true,
},
OutputFields: &OutputFields{
fields: make(map[string]bool),
},
},
},
{
name: "wildcard id and type and actions with list",
input: "id=*;type=*;actions=read,list",
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
id: "*",
typ: resource.All,
actions: map[action.Type]bool{
action.Read: true,
action.List: true,
},
},
},
{
name: "wildcard ids and type and actions with list",
input: "ids=*;type=*;actions=read,list",
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
ids: []string{"*"},
typ: resource.All,
actions: map[action.Type]bool{
action.Read: true,
action.List: true,
},
},
},
{
name: "good json type",
input: `{"type":"host-catalog","actions":["create"]}`,
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
typ: resource.HostCatalog,
actions: map[action.Type]bool{
action.Create: true,
},
},
},
{
name: "good json id",
input: `{"id":"u_foobar","actions":["read"]}`,
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
id: "u_foobar",
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
},
},
{
name: "good json ids",
input: `{"ids":["hcst_foobar", "hcst_foobaz"],"actions":["read"]}`,
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
ids: []string{"hcst_foobar", "hcst_foobaz"},
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
},
},
{
name: "good json output fields id",
input: `{"id":"u_foobar","actions":["read"],"output_fields":["version","id","name"]}`,
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
id: "u_foobar",
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
OutputFields: &OutputFields{
fields: map[string]bool{
"version": true,
"id": true,
"name": true,
},
},
},
},
{
name: "good json output fields ids",
input: `{"ids":["u_foobar"],"actions":["read"],"output_fields":["version","ids","name"]}`,
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
ids: []string{"u_foobar"},
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
OutputFields: &OutputFields{
fields: map[string]bool{
"version": true,
"ids": true,
"name": true,
},
},
},
},
{
name: "good json output fields no action",
input: `{"id":"u_foobar","output_fields":["version","id","name"]}`,
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
id: "u_foobar",
typ: resource.Unknown,
OutputFields: &OutputFields{
fields: map[string]bool{
"version": true,
"id": true,
"name": true,
},
},
},
},
{
name: "good text type",
input: `type=host-catalog;actions=create`,
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
typ: resource.HostCatalog,
actions: map[action.Type]bool{
action.Create: true,
},
},
},
{
name: "good text id",
input: `id=u_foobar;actions=read`,
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
id: "u_foobar",
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
},
},
{
name: "good text ids",
input: `ids=hcst_foobar,hcst_foobaz;actions=read`,
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
ids: []string{"hcst_foobar", "hcst_foobaz"},
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
},
},
{
name: "good output fields id",
input: `id=u_foobar;actions=read;output_fields=version,id,name`,
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
id: "u_foobar",
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
OutputFields: &OutputFields{
fields: map[string]bool{
"version": true,
"id": true,
"name": true,
},
},
},
},
{
name: "good output fields ids",
input: `ids=hcst_foobar,hcst_foobaz;actions=read;output_fields=version,ids,name`,
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
ids: []string{"hcst_foobar", "hcst_foobaz"},
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
OutputFields: &OutputFields{
fields: map[string]bool{
"version": true,
"ids": true,
"name": true,
},
},
},
},
{
name: "default project scope",
input: `id=hcst_foobar;actions=read`,
scopeOverride: "p_1234",
expected: Grant{
roleScopeId: "p_1234",
grantScopeId: "p_1234",
id: "hcst_foobar",
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
},
},
{
name: "default org scope",
input: `id=acctpw_foobar;actions=read`,
scopeOverride: "o_1234",
expected: Grant{
roleScopeId: "o_1234",
grantScopeId: "o_1234",
id: "acctpw_foobar",
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
},
},
{
name: "default global scope",
input: `id=acctpw_foobar;actions=read`,
scopeOverride: "global",
expected: Grant{
roleScopeId: "global",
grantScopeId: "global",
id: "acctpw_foobar",
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
},
},
{
name: "bad user id template",
input: `id={{superman}};actions=create,read`,
userId: "u_abcd1234",
err: `perms.Parse: unknown template "{{superman}}" in grant "id" value: parameter violation: error #100`,
},
{
name: "bad user ids template",
input: `ids={{superman}};actions=create,read`,
userId: "u_abcd1234",
err: `perms.Parse: unknown template "{{superman}}" in grant "ids" value: parameter violation: error #100`,
},
{
name: "good user id template",
input: `id={{ user.id}};actions=read,update`,
userId: "u_abcd1234",
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
id: "u_abcd1234",
actions: map[action.Type]bool{
action.Update: true,
action.Read: true,
},
},
},
{
name: "bad old account id template",
input: `id={{superman}};actions=read`,
accountId: fmt.Sprintf("%s_1234567890", globals.PasswordAccountPreviousPrefix),
err: `perms.Parse: unknown template "{{superman}}" in grant "id" value: parameter violation: error #100`,
},
{
name: "bad old account ids template",
input: `ids={{superman}};actions=read`,
accountId: fmt.Sprintf("%s_1234567890", globals.PasswordAccountPreviousPrefix),
err: `perms.Parse: unknown template "{{superman}}" in grant "ids" value: parameter violation: error #100`,
},
{
name: "bad new account id template",
input: `id={{superman}};actions=read`,
accountId: fmt.Sprintf("%s_1234567890", globals.PasswordAccountPrefix),
err: `perms.Parse: unknown template "{{superman}}" in grant "id" value: parameter violation: error #100`,
},
{
name: "good old account id template",
input: `id={{ account.id}};actions=update,read`,
accountId: fmt.Sprintf("%s_1234567890", globals.PasswordAccountPreviousPrefix),
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
id: fmt.Sprintf("%s_1234567890", globals.PasswordAccountPreviousPrefix),
actions: map[action.Type]bool{
action.Update: true,
action.Read: true,
},
},
},
{
name: "good new account id template",
input: `id={{ account.id}};actions=update,read`,
accountId: fmt.Sprintf("%s_1234567890", globals.PasswordAccountPrefix),
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
id: fmt.Sprintf("%s_1234567890", globals.PasswordAccountPrefix),
actions: map[action.Type]bool{
action.Update: true,
action.Read: true,
},
},
},
{
name: "good ids template",
input: `ids={{ user.id}},{{ account.id}};actions=read,update`,
userId: "u_abcd1234",
accountId: fmt.Sprintf("%s_1234567890", globals.PasswordAccountPrefix),
expected: Grant{
roleScopeId: "o_scope",
grantScopeId: "o_scope",
ids: []string{"u_abcd1234", "acctpw_1234567890"},
actions: map[action.Type]bool{
action.Update: true,
action.Read: true,
},
},
},
}
_, err := Parse(ctx, GrantTuple{RoleScopeId: "", GrantScopeId: "", Grant: ""})
require.Error(t, err)
assert.Equal(t, "perms.Parse: missing grant string: parameter violation: error #100", err.Error())
_, err = Parse(ctx, GrantTuple{RoleScopeId: "", GrantScopeId: "", Grant: "{}"})
require.Error(t, err)
assert.Equal(t, "perms.Parse: missing role scope id: parameter violation: error #100", err.Error())
_, err = Parse(ctx, GrantTuple{RoleScopeId: "p_abcd", GrantScopeId: "", Grant: "{}"})
require.Error(t, err)
assert.Equal(t, "perms.Parse: missing grant scope id: parameter violation: error #100", err.Error())
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
scope := "o_scope"
if test.scopeOverride != "" {
scope = test.scopeOverride
}
grant, err := Parse(ctx, GrantTuple{RoleScopeId: scope, GrantScopeId: scope, Grant: test.input}, WithUserId(test.userId), WithAccountId(test.accountId))
if test.err != "" {
require.Error(err)
assert.Equal(test.err, err.Error())
} else {
require.NoError(err)
assert.Equal(test.expected, grant)
}
})
}
}
func TestHasActionOrSubaction(t *testing.T) {
tests := []struct {
name string
base []action.Type
act action.Type
want bool
}{
{
name: "no actions",
base: []action.Type{},
act: action.Read,
},
{
name: "has direct action",
base: []action.Type{action.Cancel, action.Read},
act: action.Read,
want: true,
},
{
name: "has parent action",
base: []action.Type{action.Cancel, action.ReadSelf},
act: action.Read,
want: true,
},
{
name: "has direct sub action",
base: []action.Type{action.Cancel, action.ReadSelf},
act: action.ReadSelf,
want: true,
},
{
name: "has sub action needs parent",
base: []action.Type{action.Cancel, action.Read},
act: action.ReadSelf,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := Grant{
actions: make(map[action.Type]bool),
}
for _, act := range tt.base {
g.actions[act] = true
}
assert.Equal(t, tt.want, g.hasActionOrSubaction(tt.act))
})
}
}
func FuzzParse(f *testing.F) {
ctx := context.Background()
tc := []string{
"id=*;type=*;actions=read,list;output_fields=",
`{"id": "*", "type": "*", "actions": ["read", "list"], "output_fields": []}`,
"id=*;type=*;actions=read,list",
"ids=*;type=*;actions=read,list",
`{"type":"host-catalog","actions":["create"]}`,
`{"id":"u_foobar","actions":["read"]}`,
`{"id":"u_foobar","actions":["read"],"output_fields":["version","id","name"]}`,
`{"id":"u_foobar","output_fields":["version","id","name"]}`,
`type=host-catalog;actions=create`,
`ids=hcst_foobar,hcst_foobaz;actions=read;output_fields=version,ids,name`,
`id=hcst_foobar;actions=read`,
`id=acctpw_foobar;actions=read`,
`id={{ user.id}};actions=read,update`,
"type=host-catalog;actions=create",
"type=*;actions=*",
"id=*;type=*;actions=*",
"ids=*;type=*;actions=*",
"id=*;type=*;actions=read,list",
"ids=*;type=*;actions=read,list",
"id=foobar;actions=read;output_fields=version,id,name",
"ids=foobar,foobaz;actions=read;output_fields=version,id,name",
"id={{account.id}};actions=update,read",
"ids={{account.id}},{{user.id}};actions=update,read",
`{"id":"foobar","type":"host-catalog","actions":["create"]}`,
`{"ids":["foobar"],"type":"host-catalog","actions":["create"]}`,
`{"ids":["\""]}`,
}
for _, tc := range tc {
f.Add(tc)
}
f.Fuzz(func(t *testing.T, grant string) {
g, err := Parse(ctx, GrantTuple{GrantScopeId: "global", Grant: grant}, WithSkipFinalValidation(true))
if err != nil {
return
}
g2, err := Parse(ctx, GrantTuple{GrantScopeId: "global", Grant: g.CanonicalString()}, WithSkipFinalValidation(true))
if err != nil {
t.Fatal("Failed to parse canonical string:", err)
}
if g.CanonicalString() != g2.CanonicalString() {
t.Errorf("grant roundtrip failed, input %q, output %q", g.CanonicalString(), g2.CanonicalString())
}
jsonBytes, err := g.MarshalJSON()
if err != nil {
t.Error("Failed to marshal JSON:", err)
}
g3, err := Parse(ctx, GrantTuple{GrantScopeId: "global", Grant: string(jsonBytes)}, WithSkipFinalValidation(true))
if err != nil {
t.Fatal("Failed to parse json string:", err)
}
if g.CanonicalString() != g3.CanonicalString() {
t.Errorf("grant JSON roundtrip failed, input %q, output %q", g.CanonicalString(), g3.CanonicalString())
}
})
}