Add ability to filter List*Requests (#952)
This commit is contained in:
1 parent
3c33d05c96
commit
97b404032b
53 files changed
+2624
-1748
No files matched your search
@@ -77,6 +77,10 @@ func (s Service) ListAccounts(ctx context.Context, req *pbs.ListAccountsRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Account, 0, len(ul))
|
||||
res := &perms.Resource{
|
||||
ScopeId: authResults.Scope.Id,
|
||||
@@ -86,7 +90,10 @@ func (s Service) ListAccounts(ctx context.Context, req *pbs.ListAccountsRequest)
|
||||
for _, item := range ul {
|
||||
item.Scope = authResults.Scope
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
@@ -502,6 +509,9 @@ func validateListRequest(req *pbs.ListAccountsRequest) error {
|
||||
if !handlers.ValidId(password.AuthMethodPrefix, req.GetAuthMethodId()) {
|
||||
badFields["auth_method_id"] = "Invalid formatted identifier."
|
||||
}
|
||||
if _, err := handlers.NewFilter(req.GetFilter()); err != nil {
|
||||
badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err)
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Error in provided request.", badFields)
|
||||
}
|
||||
|
||||
@@ -144,30 +144,51 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
authMethod string
|
||||
res *pbs.ListAccountsResponse
|
||||
err error
|
||||
name string
|
||||
req *pbs.ListAccountsRequest
|
||||
res *pbs.ListAccountsResponse
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "List Some Accounts",
|
||||
authMethod: amSomeAccounts.GetPublicId(),
|
||||
res: &pbs.ListAccountsResponse{Items: wantSomeAccounts},
|
||||
name: "List Some Accounts",
|
||||
req: &pbs.ListAccountsRequest{AuthMethodId: amSomeAccounts.GetPublicId()},
|
||||
res: &pbs.ListAccountsResponse{Items: wantSomeAccounts},
|
||||
},
|
||||
{
|
||||
name: "List Other Accounts",
|
||||
authMethod: amOtherAccounts.GetPublicId(),
|
||||
res: &pbs.ListAccountsResponse{Items: wantOtherAccounts},
|
||||
name: "List Other Accounts",
|
||||
req: &pbs.ListAccountsRequest{AuthMethodId: amOtherAccounts.GetPublicId()},
|
||||
res: &pbs.ListAccountsResponse{Items: wantOtherAccounts},
|
||||
},
|
||||
{
|
||||
name: "List No Accounts",
|
||||
authMethod: amNoAccounts.GetPublicId(),
|
||||
res: &pbs.ListAccountsResponse{},
|
||||
name: "List No Accounts",
|
||||
req: &pbs.ListAccountsRequest{AuthMethodId: amNoAccounts.GetPublicId()},
|
||||
res: &pbs.ListAccountsResponse{},
|
||||
},
|
||||
{
|
||||
name: "Unfound Auth Method",
|
||||
authMethod: password.AuthMethodPrefix + "_DoesntExis",
|
||||
err: handlers.ApiErrorWithCode(codes.NotFound),
|
||||
name: "Unfound Auth Method",
|
||||
req: &pbs.ListAccountsRequest{AuthMethodId: password.AuthMethodPrefix + "_DoesntExis"},
|
||||
err: handlers.ApiErrorWithCode(codes.NotFound),
|
||||
},
|
||||
{
|
||||
name: "Filter Some Accounts",
|
||||
req: &pbs.ListAccountsRequest{
|
||||
AuthMethodId: amSomeAccounts.GetPublicId(),
|
||||
Filter: fmt.Sprintf(`"/item/attributes/login_name"==%q`, wantSomeAccounts[1].Attributes.AsMap()["login_name"]),
|
||||
},
|
||||
res: &pbs.ListAccountsResponse{Items: wantSomeAccounts[1:2]},
|
||||
},
|
||||
{
|
||||
name: "Filter All Accounts",
|
||||
req: &pbs.ListAccountsRequest{
|
||||
AuthMethodId: amSomeAccounts.GetPublicId(),
|
||||
Filter: `"/item/id"=="noaccountmatchesthis"`,
|
||||
},
|
||||
res: &pbs.ListAccountsResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
req: &pbs.ListAccountsRequest{AuthMethodId: amSomeAccounts.GetPublicId(), Filter: `"//id/"=="bad"`},
|
||||
err: handlers.InvalidArgumentErrorf("bad format", nil),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
@@ -176,12 +197,14 @@ func TestList(t *testing.T) {
|
||||
s, err := accounts.NewService(repoFn)
|
||||
require.NoError(err, "Couldn't create new user service.")
|
||||
|
||||
got, gErr := s.ListAccounts(auth.DisabledAuthTestContext(auth.WithScopeId(o.GetPublicId())), &pbs.ListAccountsRequest{AuthMethodId: tc.authMethod})
|
||||
got, gErr := s.ListAccounts(auth.DisabledAuthTestContext(auth.WithScopeId(o.GetPublicId())), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListAccounts() with auth method %q got error %v, wanted %v", tc.authMethod, gErr, tc.err)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListAccounts() with auth method %q got error %v, wanted %v", tc.req, gErr, tc.err)
|
||||
} else {
|
||||
require.NoError(gErr)
|
||||
}
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "ListUsers() with scope %q got response %q, wanted %q", tc.authMethod, got, tc.res)
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "ListAccounts() with scope %q got response %q, wanted %q", tc.req, got, tc.res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +141,10 @@ func (s Service) ListAuthMethods(ctx context.Context, req *pbs.ListAuthMethodsRe
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.AuthMethod, 0, len(ul))
|
||||
res := &perms.Resource{
|
||||
Type: resource.AuthMethod,
|
||||
@@ -149,7 +153,10 @@ func (s Service) ListAuthMethods(ctx context.Context, req *pbs.ListAuthMethodsRe
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, item); err != nil {
|
||||
return nil, err
|
||||
@@ -605,6 +612,9 @@ func validateListRequest(req *pbs.ListAuthMethodsRequest) error {
|
||||
req.GetScopeId() != scope.Global.String() {
|
||||
badFields["scope_id"] = "This field must be 'global' or a valid org scope id."
|
||||
}
|
||||
if _, err := handlers.NewFilter(req.GetFilter()); err != nil {
|
||||
badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err)
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
|
||||
}
|
||||
|
||||
@@ -226,6 +226,24 @@ func TestList(t *testing.T) {
|
||||
Items: append(wantSomeAuthMethods, wantOtherAuthMethods...),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Filter To Some Auth Methods",
|
||||
req: &pbs.ListAuthMethodsRequest{
|
||||
ScopeId: "global", Recursive: true,
|
||||
Filter: fmt.Sprintf(`"/item/scope/id"==%q`, oWithAuthMethods.GetPublicId()),
|
||||
},
|
||||
res: &pbs.ListAuthMethodsResponse{Items: wantSomeAuthMethods},
|
||||
},
|
||||
{
|
||||
name: "Filter All Auth Methods",
|
||||
req: &pbs.ListAuthMethodsRequest{ScopeId: oWithAuthMethods.GetPublicId(), Filter: `"/item/id"=="nothingmatchesthis"`},
|
||||
res: &pbs.ListAuthMethodsResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
req: &pbs.ListAuthMethodsRequest{ScopeId: oWithAuthMethods.GetPublicId(), Filter: `"//id/"=="bad"`},
|
||||
err: handlers.InvalidArgumentErrorf("bad format", nil),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -237,6 +255,8 @@ func TestList(t *testing.T) {
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListAuthMethods() for scope %q got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
|
||||
} else {
|
||||
require.NoError(gErr)
|
||||
}
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListAuthMethods() for scope %q got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res)
|
||||
})
|
||||
|
||||
@@ -74,6 +74,10 @@ func (s Service) ListAuthTokens(ctx context.Context, req *pbs.ListAuthTokensRequ
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.AuthToken, 0, len(ul))
|
||||
res := &perms.Resource{
|
||||
Type: resource.AuthToken,
|
||||
@@ -82,7 +86,10 @@ func (s Service) ListAuthTokens(ctx context.Context, req *pbs.ListAuthTokensRequ
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
@@ -251,6 +258,9 @@ func validateListRequest(req *pbs.ListAuthTokensRequest) error {
|
||||
req.GetScopeId() != scope.Global.String() {
|
||||
badFields["scope_id"] = "This field must be 'global' or a valid org scope id."
|
||||
}
|
||||
if _, err := handlers.NewFilter(req.GetFilter()); err != nil {
|
||||
badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err)
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package authtokens_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@@ -204,6 +205,24 @@ func TestList(t *testing.T) {
|
||||
req: &pbs.ListAuthTokensRequest{ScopeId: "global", Recursive: true},
|
||||
res: &pbs.ListAuthTokensResponse{Items: allTokens},
|
||||
},
|
||||
{
|
||||
name: "Filter to Some Tokens",
|
||||
req: &pbs.ListAuthTokensRequest{
|
||||
ScopeId: "global", Recursive: true,
|
||||
Filter: fmt.Sprintf(`"/item/scope/id"==%q`, orgWithSomeTokens.GetPublicId()),
|
||||
},
|
||||
res: &pbs.ListAuthTokensResponse{Items: wantSomeTokens},
|
||||
},
|
||||
{
|
||||
name: "Filter All Tokens",
|
||||
req: &pbs.ListAuthTokensRequest{ScopeId: orgWithOtherTokens.GetPublicId(), Filter: `"/item/scope/id"=="thisdoesntmatch"`},
|
||||
res: &pbs.ListAuthTokensResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
req: &pbs.ListAuthTokensRequest{ScopeId: orgWithSomeTokens.GetPublicId(), Filter: `"//id/"=="bad"`},
|
||||
err: handlers.InvalidArgumentErrorf("bad format", nil),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -214,6 +233,8 @@ func TestList(t *testing.T) {
|
||||
if tc.err != nil {
|
||||
require.Error(t, gErr)
|
||||
assert.True(t, errors.Is(gErr, tc.err), "ListAuthTokens() with scope %q got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
|
||||
} else {
|
||||
require.NoError(t, gErr)
|
||||
}
|
||||
assert.Empty(t, cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "ListAuthTokens() with scope %q got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/go-bexpr"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
// filterItem captures all the different namespaces that can be used when
|
||||
// filtering an item.
|
||||
type filterItem struct {
|
||||
Item interface{} `json:"item"`
|
||||
}
|
||||
|
||||
type Filter struct {
|
||||
eval *bexpr.Evaluator
|
||||
}
|
||||
|
||||
// NewFilter returns a Filter which can be evluated against. An empty string paramter indicates
|
||||
// all items passed to it should succeed.
|
||||
func NewFilter(f string) (*Filter, error) {
|
||||
const op = "handlers.NewFilter"
|
||||
if f == "" {
|
||||
return &Filter{}, nil
|
||||
}
|
||||
e, err := bexpr.CreateEvaluator(f, bexpr.WithTagName("json"), bexpr.WithHookFn(wellKnownTypeFilterHook))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("couldn't build filter"), errors.WithCode(errors.InvalidParameter))
|
||||
}
|
||||
return &Filter{eval: e}, nil
|
||||
}
|
||||
|
||||
// Match returns if the provided interface matches the filter.
|
||||
// If the filter does not match the structure of the object being Matched, false is returned.
|
||||
// TODO: Support more than just matching against the item being filtered. Also allow matching against
|
||||
// values in the request or the request context itself.
|
||||
func (f *Filter) Match(item interface{}) bool {
|
||||
if f.eval == nil {
|
||||
return true
|
||||
}
|
||||
m, err := f.eval.Evaluate(filterItem{Item: item})
|
||||
// There isn't a clear way to differentiate between a JSON Pointer which doesn't represent
|
||||
// the structure of the object being Matched and a JSON Pointer which references a field which
|
||||
// is part of a sub structure that is nil in this item. Because of this, any filter which would
|
||||
// result in an error using the underlying library is simply interpreted as not a match.
|
||||
return err == nil && m
|
||||
}
|
||||
|
||||
// wellKnownTypeFilterHook is passed to bexpr to treat all proto well-known
|
||||
// types as the types they wrap for comparison.
|
||||
func wellKnownTypeFilterHook(v reflect.Value) reflect.Value {
|
||||
if !v.CanInterface() {
|
||||
return v
|
||||
}
|
||||
ret := v.Interface()
|
||||
switch pm := v.Interface().(type) {
|
||||
case *wrapperspb.BoolValue:
|
||||
ret = pm.GetValue()
|
||||
case *wrapperspb.BytesValue:
|
||||
ret = pm.GetValue()
|
||||
case *wrapperspb.StringValue:
|
||||
ret = pm.GetValue()
|
||||
case *wrapperspb.DoubleValue:
|
||||
ret = pm.GetValue()
|
||||
case *wrapperspb.FloatValue:
|
||||
ret = pm.GetValue()
|
||||
case *wrapperspb.Int32Value:
|
||||
ret = pm.GetValue()
|
||||
case *wrapperspb.Int64Value:
|
||||
ret = pm.GetValue()
|
||||
case *wrapperspb.UInt32Value:
|
||||
ret = pm.GetValue()
|
||||
case *wrapperspb.UInt64Value:
|
||||
ret = pm.GetValue()
|
||||
case *structpb.Struct:
|
||||
ret = pm.AsMap()
|
||||
case *timestamppb.Timestamp:
|
||||
ret = pm.AsTime()
|
||||
}
|
||||
return reflect.ValueOf(ret)
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
func TestNewFilter_everythingMatchesEmpty(t *testing.T) {
|
||||
f, err := NewFilter("")
|
||||
require.NoError(t, err)
|
||||
for _, v := range []interface{}{
|
||||
nil,
|
||||
"foo",
|
||||
"",
|
||||
1,
|
||||
-1,
|
||||
map[string]string{},
|
||||
[]string{"foo"},
|
||||
[]int(nil),
|
||||
(*filterItem)(nil),
|
||||
struct{ foo string }{foo: "foo"},
|
||||
filterItem{Item: struct{ foo string }{foo: "foo"}},
|
||||
} {
|
||||
assert.True(t, f.Match(v), "Trying to match %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFilter(t *testing.T) {
|
||||
type embedded struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
type multiLevel struct {
|
||||
E *embedded `json:"e"`
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
filter string
|
||||
fErr bool
|
||||
in interface{}
|
||||
match bool
|
||||
}{
|
||||
{
|
||||
name: "bad format",
|
||||
filter: `random strings that dont match a format`,
|
||||
fErr: true,
|
||||
},
|
||||
{
|
||||
name: "no leading /item",
|
||||
filter: `""=="foo"`,
|
||||
in: "foo",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
name: "simple string",
|
||||
filter: `"/item"=="foo"`,
|
||||
in: "foo",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
name: "struct",
|
||||
filter: `"/item/id"=="foo"`,
|
||||
in: struct {
|
||||
ID string `json:"id"`
|
||||
}{ID: "foo"},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
name: "doesnt match struct fields",
|
||||
filter: `"/item/name"=="foo"`,
|
||||
in: struct {
|
||||
ID string `json:"id"`
|
||||
}{ID: "foo"},
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
name: "proto well known types",
|
||||
filter: `"/item/id"=="foo"`,
|
||||
in: struct {
|
||||
ID *wrapperspb.StringValue `json:"id"`
|
||||
}{ID: wrapperspb.String("foo")},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
name: "pointer include proto well known type structure",
|
||||
filter: `"/item/id/value"=="foo"`,
|
||||
in: struct {
|
||||
ID *wrapperspb.StringValue `json:"id"`
|
||||
}{ID: wrapperspb.String("foo")},
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
name: "multi level struct",
|
||||
filter: `"/item/e/name"=="foo"`,
|
||||
in: multiLevel{E: &embedded{Name: "foo"}},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
name: "multi level struct",
|
||||
filter: `"/item/e/name"=="foo"`,
|
||||
in: multiLevel{E: nil},
|
||||
match: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
f, err := NewFilter(tc.filter)
|
||||
if tc.fErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.match, f.Match(tc.in))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWellKnownTypeFilterHook(t *testing.T) {
|
||||
conversions := map[interface{}]interface{}{
|
||||
wrapperspb.String("foo"): "foo",
|
||||
wrapperspb.UInt64(123): uint64(123),
|
||||
wrapperspb.Int64(123): int64(123),
|
||||
wrapperspb.UInt32(123): uint32(123),
|
||||
wrapperspb.Int32(123): int32(123),
|
||||
wrapperspb.Float(123): float32(123),
|
||||
wrapperspb.Double(123): float64(123),
|
||||
wrapperspb.Bytes([]byte("foo")): []byte("foo"),
|
||||
wrapperspb.Bool(true): true,
|
||||
}
|
||||
for in, out := range conversions {
|
||||
assert.Equal(t, reflect.ValueOf(out).Interface(), wellKnownTypeFilterHook(reflect.ValueOf(in)).Interface())
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,10 @@ func (s Service) ListGroups(ctx context.Context, req *pbs.ListGroupsRequest) (*p
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Group, 0, len(gl))
|
||||
res := &perms.Resource{
|
||||
Type: resource.Group,
|
||||
@@ -96,7 +100,10 @@ func (s Service) ListGroups(ctx context.Context, req *pbs.ListGroupsRequest) (*p
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
@@ -493,6 +500,9 @@ func validateListRequest(req *pbs.ListGroupsRequest) error {
|
||||
req.GetScopeId() != scope.Global.String() {
|
||||
badFields["scope_id"] = "Incorrectly formatted identifier."
|
||||
}
|
||||
if _, err := handlers.NewFilter(req.GetFilter()); err != nil {
|
||||
badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err)
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
|
||||
}
|
||||
|
||||
@@ -290,6 +290,26 @@ func TestList(t *testing.T) {
|
||||
Items: totalGroups,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Filter to org groups",
|
||||
req: &pbs.ListGroupsRequest{ScopeId: "global", Recursive: true, Filter: fmt.Sprintf(`"/item/scope/id"==%q`, oWithGroups.GetPublicId())},
|
||||
res: &pbs.ListGroupsResponse{Items: wantOrgGroups},
|
||||
},
|
||||
{
|
||||
name: "Filter to project groups",
|
||||
req: &pbs.ListGroupsRequest{ScopeId: "global", Recursive: true, Filter: fmt.Sprintf(`"/item/scope/id"==%q`, pWithGroups.GetPublicId())},
|
||||
res: &pbs.ListGroupsResponse{Items: wantProjGroups},
|
||||
},
|
||||
{
|
||||
name: "Filter to no groups",
|
||||
req: &pbs.ListGroupsRequest{ScopeId: "global", Recursive: true, Filter: `"/item/id"=="doesntmatch"`},
|
||||
res: &pbs.ListGroupsResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
req: &pbs.ListGroupsRequest{ScopeId: "global", Filter: `"//id/"=="bad"`},
|
||||
err: handlers.InvalidArgumentErrorf("bad format", nil),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
@@ -125,6 +125,10 @@ func (s Service) ListHostCatalogs(ctx context.Context, req *pbs.ListHostCatalogs
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.HostCatalog, 0, len(ul))
|
||||
res := &perms.Resource{
|
||||
Type: resource.HostCatalog,
|
||||
@@ -133,7 +137,10 @@ func (s Service) ListHostCatalogs(ctx context.Context, req *pbs.ListHostCatalogs
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, item); err != nil {
|
||||
return nil, err
|
||||
@@ -457,6 +464,9 @@ func validateListRequest(req *pbs.ListHostCatalogsRequest) error {
|
||||
!req.GetRecursive() {
|
||||
badFields["scope_id"] = "This field must be a valid project scope ID or the list operation must be recursive."
|
||||
}
|
||||
if _, err := handlers.NewFilter(req.GetFilter()); err != nil {
|
||||
badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err)
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
|
||||
}
|
||||
|
||||
@@ -232,6 +232,24 @@ func TestList(t *testing.T) {
|
||||
Items: append(wantSomeCatalogs, wantOtherCatalogs...),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Filter To Some Catalogs",
|
||||
req: &pbs.ListHostCatalogsRequest{
|
||||
ScopeId: scope.Global.String(), Recursive: true,
|
||||
Filter: fmt.Sprintf(`"/item/scope/id"==%q`, pWithCatalogs.GetPublicId()),
|
||||
},
|
||||
res: &pbs.ListHostCatalogsResponse{Items: wantSomeCatalogs},
|
||||
},
|
||||
{
|
||||
name: "Filter To No Catalogs",
|
||||
req: &pbs.ListHostCatalogsRequest{ScopeId: pWithCatalogs.GetPublicId(), Filter: `"/item/id"=="doesnt match"`},
|
||||
res: &pbs.ListHostCatalogsResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
req: &pbs.ListHostCatalogsRequest{ScopeId: pWithCatalogs.GetPublicId(), Filter: `"//id/"=="bad"`},
|
||||
err: handlers.InvalidArgumentErrorf("bad format", nil),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
@@ -79,6 +79,10 @@ func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.HostSet, 0, len(hl))
|
||||
res := &perms.Resource{
|
||||
ScopeId: authResults.Scope.Id,
|
||||
@@ -88,7 +92,10 @@ func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest)
|
||||
for _, item := range hl {
|
||||
item.Scope = authResults.Scope
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
@@ -534,6 +541,9 @@ func validateListRequest(req *pbs.ListHostSetsRequest) error {
|
||||
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
|
||||
badFields["host_catalog_id"] = "The field is incorrectly formatted."
|
||||
}
|
||||
if _, err := handlers.NewFilter(req.GetFilter()); err != nil {
|
||||
badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err)
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
|
||||
}
|
||||
|
||||
@@ -145,20 +145,35 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
hostCatalogId string
|
||||
res *pbs.ListHostSetsResponse
|
||||
err error
|
||||
name string
|
||||
req *pbs.ListHostSetsRequest
|
||||
res *pbs.ListHostSetsResponse
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "List Many Host Sets",
|
||||
hostCatalogId: hc.GetPublicId(),
|
||||
res: &pbs.ListHostSetsResponse{Items: wantHs},
|
||||
name: "List Many Host Sets",
|
||||
req: &pbs.ListHostSetsRequest{HostCatalogId: hc.GetPublicId()},
|
||||
res: &pbs.ListHostSetsResponse{Items: wantHs},
|
||||
},
|
||||
{
|
||||
name: "List No Host Sets",
|
||||
hostCatalogId: hcNoHosts.GetPublicId(),
|
||||
res: &pbs.ListHostSetsResponse{},
|
||||
name: "List No Host Sets",
|
||||
req: &pbs.ListHostSetsRequest{HostCatalogId: hcNoHosts.GetPublicId()},
|
||||
res: &pbs.ListHostSetsResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter To One Host Set",
|
||||
req: &pbs.ListHostSetsRequest{HostCatalogId: hc.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantHs[1].GetId())},
|
||||
res: &pbs.ListHostSetsResponse{Items: wantHs[1:2]},
|
||||
},
|
||||
{
|
||||
name: "Filter To No Host Sets",
|
||||
req: &pbs.ListHostSetsRequest{HostCatalogId: hc.GetPublicId(), Filter: `"/item/name"=="doesnt match"`},
|
||||
res: &pbs.ListHostSetsResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
req: &pbs.ListHostSetsRequest{HostCatalogId: hc.GetPublicId(), Filter: `"//id/"=="bad"`},
|
||||
err: handlers.InvalidArgumentErrorf("bad format", nil),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
@@ -167,12 +182,14 @@ func TestList(t *testing.T) {
|
||||
s, err := host_sets.NewService(repoFn)
|
||||
require.NoError(err, "Couldn't create new host set service.")
|
||||
|
||||
got, gErr := s.ListHostSets(auth.DisabledAuthTestContext(auth.WithScopeId(proj.GetPublicId())), &pbs.ListHostSetsRequest{HostCatalogId: tc.hostCatalogId})
|
||||
got, gErr := s.ListHostSets(auth.DisabledAuthTestContext(auth.WithScopeId(proj.GetPublicId())), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListHostSets(%q) got error %v, wanted %v", tc.hostCatalogId, gErr, tc.err)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListHostSets(%q) got error %v, wanted %v", tc.req, gErr, tc.err)
|
||||
} else {
|
||||
require.NoError(gErr)
|
||||
}
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHostSets(%q) got response %q, wanted %q", tc.hostCatalogId, got, tc.res)
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHostSets(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,10 @@ func (s Service) ListHosts(ctx context.Context, req *pbs.ListHostsRequest) (*pbs
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Host, 0, len(hl))
|
||||
res := &perms.Resource{
|
||||
ScopeId: authResults.Scope.Id,
|
||||
@@ -86,7 +90,10 @@ func (s Service) ListHosts(ctx context.Context, req *pbs.ListHostsRequest) (*pbs
|
||||
for _, item := range hl {
|
||||
item.Scope = authResults.Scope
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
@@ -460,6 +467,9 @@ func validateListRequest(req *pbs.ListHostsRequest) error {
|
||||
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
|
||||
badFields["host_catalog_id"] = "The field is incorrectly formatted."
|
||||
}
|
||||
if _, err := handlers.NewFilter(req.GetFilter()); err != nil {
|
||||
badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err)
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
|
||||
}
|
||||
|
||||
@@ -140,20 +140,35 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
hostCatalogId string
|
||||
res *pbs.ListHostsResponse
|
||||
err error
|
||||
name string
|
||||
req *pbs.ListHostsRequest
|
||||
res *pbs.ListHostsResponse
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "List Many Hosts",
|
||||
hostCatalogId: hc.GetPublicId(),
|
||||
res: &pbs.ListHostsResponse{Items: wantHs},
|
||||
name: "List Many Hosts",
|
||||
req: &pbs.ListHostsRequest{HostCatalogId: hc.GetPublicId()},
|
||||
res: &pbs.ListHostsResponse{Items: wantHs},
|
||||
},
|
||||
{
|
||||
name: "List No Hosts",
|
||||
hostCatalogId: hcNoHosts.GetPublicId(),
|
||||
res: &pbs.ListHostsResponse{},
|
||||
name: "List No Hosts",
|
||||
req: &pbs.ListHostsRequest{HostCatalogId: hcNoHosts.GetPublicId()},
|
||||
res: &pbs.ListHostsResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter to One Host",
|
||||
req: &pbs.ListHostsRequest{HostCatalogId: hc.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantHs[1].GetId())},
|
||||
res: &pbs.ListHostsResponse{Items: wantHs[1:2]},
|
||||
},
|
||||
{
|
||||
name: "Filter to No Hosts",
|
||||
req: &pbs.ListHostsRequest{HostCatalogId: hc.GetPublicId(), Filter: `"/item/id"=="doesnt match"`},
|
||||
res: &pbs.ListHostsResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
req: &pbs.ListHostsRequest{HostCatalogId: hc.GetPublicId(), Filter: `"//id/"=="bad"`},
|
||||
err: handlers.InvalidArgumentErrorf("bad format", nil),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
@@ -162,12 +177,14 @@ func TestList(t *testing.T) {
|
||||
s, err := hosts.NewService(repoFn)
|
||||
require.NoError(err, "Couldn't create new host set service.")
|
||||
|
||||
got, gErr := s.ListHosts(auth.DisabledAuthTestContext(auth.WithScopeId(proj.GetPublicId())), &pbs.ListHostsRequest{HostCatalogId: tc.hostCatalogId})
|
||||
got, gErr := s.ListHosts(auth.DisabledAuthTestContext(auth.WithScopeId(proj.GetPublicId())), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListHosts(%q) got error %v, wanted %v", tc.hostCatalogId, gErr, tc.err)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListHosts(%q) got error %v, wanted %v", tc.req, gErr, tc.err)
|
||||
} else {
|
||||
require.NoError(gErr)
|
||||
}
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHosts(%q) got response %q, wanted %q", tc.hostCatalogId, got, tc.res)
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHosts(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,10 @@ func (s Service) ListRoles(ctx context.Context, req *pbs.ListRolesRequest) (*pbs
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Role, 0, len(items))
|
||||
res := &perms.Resource{
|
||||
Type: resource.Role,
|
||||
@@ -100,7 +104,10 @@ func (s Service) ListRoles(ctx context.Context, req *pbs.ListRolesRequest) (*pbs
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
@@ -686,6 +693,9 @@ func validateListRequest(req *pbs.ListRolesRequest) error {
|
||||
req.GetScopeId() != scope.Global.String() {
|
||||
badFields["scope_id"] = "Improperly formatted field."
|
||||
}
|
||||
if _, err := handlers.NewFilter(req.GetFilter()); err != nil {
|
||||
badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err)
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
|
||||
}
|
||||
|
||||
@@ -222,6 +222,7 @@ func TestList(t *testing.T) {
|
||||
name string
|
||||
req *pbs.ListRolesRequest
|
||||
res *pbs.ListRolesResponse
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "List Many Role",
|
||||
@@ -264,6 +265,26 @@ func TestList(t *testing.T) {
|
||||
Items: totalRoles,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Filter to org roles",
|
||||
req: &pbs.ListRolesRequest{ScopeId: "global", Recursive: true, Filter: fmt.Sprintf(`"/item/scope/id"==%q`, oWithRoles.GetPublicId())},
|
||||
res: &pbs.ListRolesResponse{Items: wantOrgRoles},
|
||||
},
|
||||
{
|
||||
name: "Filter to proj roles",
|
||||
req: &pbs.ListRolesRequest{ScopeId: "global", Recursive: true, Filter: fmt.Sprintf(`"/item/scope/id"==%q`, pWithRoles.GetPublicId())},
|
||||
res: &pbs.ListRolesResponse{Items: wantProjRoles},
|
||||
},
|
||||
{
|
||||
name: "Filter to no roles",
|
||||
req: &pbs.ListRolesRequest{ScopeId: pWithRoles.GetPublicId(), Filter: `"/item/id"=="doesnt match"`},
|
||||
res: &pbs.ListRolesResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
req: &pbs.ListRolesRequest{ScopeId: pWithRoles.GetPublicId(), Filter: `"//id/"=="bad"`},
|
||||
err: handlers.InvalidArgumentErrorf("bad format", nil),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -272,7 +293,12 @@ func TestList(t *testing.T) {
|
||||
require.NoError(err, "Couldn't create new role service.")
|
||||
|
||||
got, gErr := s.ListRoles(auth.DisabledAuthTestContext(auth.WithScopeId(tc.req.GetScopeId())), tc.req)
|
||||
assert.NoError(gErr)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err))
|
||||
} else {
|
||||
require.NoError(gErr)
|
||||
}
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListRoles(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -147,6 +147,10 @@ func (s Service) ListScopes(ctx context.Context, req *pbs.ListScopesRequest) (*p
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Scope, 0, len(pl))
|
||||
res := &perms.Resource{
|
||||
Type: resource.Scope,
|
||||
@@ -155,7 +159,10 @@ func (s Service) ListScopes(ctx context.Context, req *pbs.ListScopesRequest) (*p
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, item); err != nil {
|
||||
return nil, err
|
||||
@@ -583,6 +590,9 @@ func validateListRequest(req *pbs.ListScopesRequest) error {
|
||||
if req.GetScopeId() != scope.Global.String() && !handlers.ValidId(scope.Org.Prefix(), req.GetScopeId()) {
|
||||
badFields["scope_id"] = "Must be 'global' or a valid org scope id when listing."
|
||||
}
|
||||
if _, err := handlers.NewFilter(req.GetFilter()); err != nil {
|
||||
badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err)
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Error in provided request.", badFields)
|
||||
}
|
||||
|
||||
@@ -283,6 +283,12 @@ func TestList(t *testing.T) {
|
||||
req: &pbs.ListScopesRequest{ScopeId: p1.GetPublicId()},
|
||||
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
||||
},
|
||||
{
|
||||
name: "Filter To Single Org",
|
||||
scopeId: scope.Global.String(),
|
||||
req: &pbs.ListScopesRequest{ScopeId: "global", Filter: fmt.Sprintf(`"/item/id"==%q`, initialOrgs[1].GetId())},
|
||||
res: &pbs.ListScopesResponse{Items: initialOrgs[1:2]},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -368,6 +374,23 @@ func TestList(t *testing.T) {
|
||||
req: &pbs.ListScopesRequest{ScopeId: scope.Global.String(), Recursive: true},
|
||||
res: &pbs.ListScopesResponse{Items: totalScopes},
|
||||
},
|
||||
{
|
||||
name: "Filter To Orgs",
|
||||
scopeId: scope.Global.String(),
|
||||
req: &pbs.ListScopesRequest{ScopeId: scope.Global.String(), Recursive: true, Filter: fmt.Sprintf(`"/item/scope/type"==%q`, scope.Global.String())},
|
||||
res: &pbs.ListScopesResponse{Items: wantOrgs},
|
||||
},
|
||||
{
|
||||
name: "Filter To Projects",
|
||||
scopeId: scope.Global.String(),
|
||||
req: &pbs.ListScopesRequest{ScopeId: scope.Global.String(), Recursive: true, Filter: fmt.Sprintf(`"/item/scope/type"==%q`, scope.Org.String())},
|
||||
res: &pbs.ListScopesResponse{Items: wantProjects},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
req: &pbs.ListScopesRequest{ScopeId: scope.Global.String(), Filter: `"//id/"=="bad"`},
|
||||
err: handlers.InvalidArgumentErrorf("bad format", nil),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
@@ -112,6 +112,10 @@ func (s Service) ListSessions(ctx context.Context, req *pbs.ListSessionsRequest)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Session, 0, len(seslist))
|
||||
res := &perms.Resource{
|
||||
Type: resource.Session,
|
||||
@@ -120,19 +124,25 @@ func (s Service) ListSessions(ctx context.Context, req *pbs.ListSessionsRequest)
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res))
|
||||
if len(authorizedActions) > 0 {
|
||||
onlySelf := true
|
||||
for _, v := range authorizedActions {
|
||||
if v != action.ReadSelf && v != action.CancelSelf {
|
||||
onlySelf = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !onlySelf || item.GetUserId() == authResults.UserId {
|
||||
item.AuthorizedActions = authorizedActions.Strings()
|
||||
finalItems = append(finalItems, item)
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
onlySelf := true
|
||||
for _, v := range authorizedActions {
|
||||
if v != action.ReadSelf && v != action.CancelSelf {
|
||||
onlySelf = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if onlySelf && item.GetUserId() != authResults.UserId {
|
||||
continue
|
||||
}
|
||||
|
||||
item.AuthorizedActions = authorizedActions.Strings()
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
|
||||
return &pbs.ListSessionsResponse{Items: finalItems}, nil
|
||||
@@ -322,6 +332,9 @@ func validateListRequest(req *pbs.ListSessionsRequest) error {
|
||||
!req.GetRecursive() {
|
||||
badFields["scope_id"] = "This field must be a valid project scope ID or the list operation must be recursive."
|
||||
}
|
||||
if _, err := handlers.NewFilter(req.GetFilter()); err != nil {
|
||||
badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err)
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package sessions_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -15,11 +18,13 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/host/static"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/servers"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers/sessions"
|
||||
"github.com/hashicorp/boundary/internal/session"
|
||||
"github.com/hashicorp/boundary/internal/target"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -137,6 +142,90 @@ func TestGetSession(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestList_Self(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrap := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrap)
|
||||
logger := hclog.New(nil)
|
||||
iamRepo := iam.TestRepo(t, conn, wrap)
|
||||
|
||||
rw := db.New(conn)
|
||||
|
||||
iamRepoFn := func() (*iam.Repository, error) {
|
||||
return iamRepo, nil
|
||||
}
|
||||
sessRepoFn := func() (*session.Repository, error) {
|
||||
return session.NewRepository(rw, rw, kms)
|
||||
}
|
||||
tokenRepoFn := func() (*authtoken.Repository, error) {
|
||||
return authtoken.NewRepository(rw, rw, kms)
|
||||
}
|
||||
serversRepoFn := func() (*servers.Repository, error) {
|
||||
return servers.NewRepository(rw, rw, kms)
|
||||
}
|
||||
|
||||
o, pWithSessions := iam.TestScopes(t, iamRepo)
|
||||
|
||||
at := authtoken.TestAuthToken(t, conn, kms, o.GetPublicId())
|
||||
uId := at.GetIamUserId()
|
||||
otherAt := authtoken.TestAuthToken(t, conn, kms, o.GetPublicId())
|
||||
|
||||
hc := static.TestCatalogs(t, conn, pWithSessions.GetPublicId(), 1)[0]
|
||||
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
||||
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
||||
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
||||
tar := target.TestTcpTarget(t, conn, pWithSessions.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()}))
|
||||
|
||||
// By default a user can read/cancel their own sessions.
|
||||
session.TestSession(t, conn, wrap, session.ComposedOf{
|
||||
UserId: uId,
|
||||
HostId: h.GetPublicId(),
|
||||
TargetId: tar.GetPublicId(),
|
||||
HostSetId: hs.GetPublicId(),
|
||||
AuthTokenId: at.GetPublicId(),
|
||||
ScopeId: pWithSessions.GetPublicId(),
|
||||
Endpoint: "tcp://127.0.0.1:22",
|
||||
})
|
||||
|
||||
s, err := sessions.NewService(sessRepoFn, iamRepoFn)
|
||||
require.NoError(t, err, "Couldn't create new session service.")
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
requester *authtoken.AuthToken
|
||||
count int
|
||||
}{
|
||||
{
|
||||
name: "List Self Sessions",
|
||||
requester: at,
|
||||
count: 1,
|
||||
},
|
||||
{
|
||||
name: "Can't List Others Sessions",
|
||||
requester: otherAt,
|
||||
count: 0,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Setup the auth request information
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("http://127.0.0.1/v1/sessions?scope_id=%s", pWithSessions.GetPublicId()), nil)
|
||||
requestInfo := auth.RequestInfo{
|
||||
Path: req.URL.Path,
|
||||
Method: req.Method,
|
||||
TokenFormat: auth.AuthTokenTypeBearer,
|
||||
PublicId: tc.requester.GetPublicId(),
|
||||
Token: tc.requester.GetToken(),
|
||||
}
|
||||
|
||||
ctx := auth.NewVerifierContext(context.Background(), logger, iamRepoFn, tokenRepoFn, serversRepoFn, kms, requestInfo)
|
||||
got, err := s.ListSessions(ctx, &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.count, len(got.GetItems()), got.GetItems())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrap := db.TestWrapper(t)
|
||||
@@ -270,6 +359,29 @@ func TestList(t *testing.T) {
|
||||
req: &pbs.ListSessionsRequest{ScopeId: scope.Global.String(), Recursive: true},
|
||||
res: &pbs.ListSessionsResponse{Items: totalSession},
|
||||
},
|
||||
{
|
||||
name: "Filter To Single Sessions",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, totalSession[4].Id)},
|
||||
res: &pbs.ListSessionsResponse{Items: totalSession[4:5]},
|
||||
},
|
||||
{
|
||||
name: "Filter To Many Sessions",
|
||||
req: &pbs.ListSessionsRequest{
|
||||
ScopeId: scope.Global.String(), Recursive: true,
|
||||
Filter: fmt.Sprintf(`"/item/scope/id" matches "^%s"`, pWithSessions.GetPublicId()[:8]),
|
||||
},
|
||||
res: &pbs.ListSessionsResponse{Items: wantSession},
|
||||
},
|
||||
{
|
||||
name: "Filter To Nothing",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId(), Filter: `"/item/id" == ""`},
|
||||
res: &pbs.ListSessionsResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId(), Filter: `//badformat/`},
|
||||
err: handlers.InvalidArgumentErrorf("bad format", nil),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -280,8 +392,11 @@ func TestList(t *testing.T) {
|
||||
if tc.err != nil {
|
||||
require.Error(t, gErr)
|
||||
assert.True(t, errors.Is(gErr, tc.err), "ListSessions(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
||||
} else {
|
||||
require.NoError(t, gErr)
|
||||
}
|
||||
if tc.res != nil {
|
||||
require.Equal(t, len(tc.res.GetItems()), len(got.GetItems()), "Didn't get expected number of sessions: %v", got.GetItems())
|
||||
for i, wantSess := range tc.res.GetItems() {
|
||||
assert.True(t, got.GetItems()[i].GetExpirationTime().AsTime().Sub(wantSess.GetExpirationTime().AsTime()) < 10*time.Millisecond)
|
||||
wantSess.ExpirationTime = got.GetItems()[i].GetExpirationTime()
|
||||
|
||||
@@ -136,6 +136,10 @@ func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Target, 0, len(ul))
|
||||
res := &perms.Resource{
|
||||
Type: resource.Target,
|
||||
@@ -144,7 +148,10 @@ func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) (
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
@@ -947,6 +954,9 @@ func validateListRequest(req *pbs.ListTargetsRequest) error {
|
||||
!req.GetRecursive() {
|
||||
badFields["scope_id"] = "This field must be a valid project scope ID or the list operation must be recursive."
|
||||
}
|
||||
if _, err := handlers.NewFilter(req.GetFilter()); err != nil {
|
||||
badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err)
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
|
||||
}
|
||||
|
||||
@@ -210,6 +210,21 @@ func TestList(t *testing.T) {
|
||||
req: &pbs.ListTargetsRequest{ScopeId: scope.Global.String(), Recursive: true},
|
||||
res: &pbs.ListTargetsResponse{Items: totalTars},
|
||||
},
|
||||
{
|
||||
name: "Filter To Many Targets",
|
||||
req: &pbs.ListTargetsRequest{ScopeId: scope.Global.String(), Recursive: true, Filter: fmt.Sprintf(`"/item/scope/id"==%q`, proj.GetPublicId())},
|
||||
res: &pbs.ListTargetsResponse{Items: wantTars},
|
||||
},
|
||||
{
|
||||
name: "Filter To No Targets",
|
||||
req: &pbs.ListTargetsRequest{ScopeId: proj.GetPublicId(), Filter: `"/item/id"=="doesnt match"`},
|
||||
res: &pbs.ListTargetsResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
req: &pbs.ListTargetsRequest{ScopeId: proj.GetPublicId(), Filter: `"/badformat/"`},
|
||||
err: handlers.InvalidArgumentErrorf("bad format", nil),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -221,6 +236,8 @@ func TestList(t *testing.T) {
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListTargets(%q) got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
|
||||
} else {
|
||||
require.NoError(gErr)
|
||||
}
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListTargets(%q) scope %q, got response %q, wanted %q", tc.name, tc.req.GetScopeId(), got, tc.res)
|
||||
})
|
||||
|
||||
@@ -89,6 +89,10 @@ func (s Service) ListUsers(ctx context.Context, req *pbs.ListUsersRequest) (*pbs
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.User, 0, len(ul))
|
||||
res := &perms.Resource{
|
||||
Type: resource.User,
|
||||
@@ -97,7 +101,10 @@ func (s Service) ListUsers(ctx context.Context, req *pbs.ListUsersRequest) (*pbs
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
@@ -490,6 +497,9 @@ func validateListRequest(req *pbs.ListUsersRequest) error {
|
||||
req.GetScopeId() != scope.Global.String() {
|
||||
badFields["scope_id"] = "Must be 'global' or a valid org scope id when listing."
|
||||
}
|
||||
if _, err := handlers.NewFilter(req.GetFilter()); err != nil {
|
||||
badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err)
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
|
||||
}
|
||||
|
||||
@@ -187,6 +187,21 @@ func TestList(t *testing.T) {
|
||||
req: &pbs.ListUsersRequest{ScopeId: "global", Recursive: true},
|
||||
res: &pbs.ListUsersResponse{Items: totalUsers},
|
||||
},
|
||||
{
|
||||
name: "Filter Many Users",
|
||||
req: &pbs.ListUsersRequest{ScopeId: "global", Recursive: true, Filter: fmt.Sprintf(`"/item/scope/id"==%q`, oWithUsers.GetPublicId())},
|
||||
res: &pbs.ListUsersResponse{Items: wantUsers},
|
||||
},
|
||||
{
|
||||
name: "Filter To No Users",
|
||||
req: &pbs.ListUsersRequest{ScopeId: oWithUsers.GetPublicId(), Filter: `"/item/id"=="doesntmatch"`},
|
||||
res: &pbs.ListUsersResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
req: &pbs.ListUsersRequest{ScopeId: oWithUsers.GetPublicId(), Filter: `"//id/"=="bad"`},
|
||||
err: handlers.InvalidArgumentErrorf("bad format", nil),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user