mirror of https://github.com/hashicorp/boundary
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.
1145 lines
34 KiB
1145 lines
34 KiB
// 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())
|
|
}
|
|
})
|
|
}
|