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.
3191 lines
100 KiB
3191 lines
100 KiB
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package scopes_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
"unicode"
|
|
|
|
"github.com/golang/protobuf/ptypes/wrappers"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/hashicorp/boundary/globals"
|
|
"github.com/hashicorp/boundary/internal/auth/ldap"
|
|
"github.com/hashicorp/boundary/internal/auth/oidc"
|
|
"github.com/hashicorp/boundary/internal/authtoken"
|
|
"github.com/hashicorp/boundary/internal/daemon/controller"
|
|
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
|
|
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
|
|
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/scopes"
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
|
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
|
|
"github.com/hashicorp/boundary/internal/iam"
|
|
"github.com/hashicorp/boundary/internal/kms"
|
|
"github.com/hashicorp/boundary/internal/perms"
|
|
"github.com/hashicorp/boundary/internal/requests"
|
|
"github.com/hashicorp/boundary/internal/server"
|
|
"github.com/hashicorp/boundary/internal/types/scope"
|
|
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
|
|
wrappingKms "github.com/hashicorp/go-kms-wrapping/extras/kms/v2"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/genproto/protobuf/field_mask"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/testing/protocmp"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
)
|
|
|
|
var (
|
|
testAuthorizedOrgActions = []string{"no-op", "read", "update", "delete", "attach-storage-policy", "detach-storage-policy"}
|
|
testAuthorizedPrjActions = []string{"no-op", "read", "update", "delete"}
|
|
testAuthorizedGlobalActions = []string{"no-op", "read", "update", "attach-storage-policy", "detach-storage-policy"}
|
|
)
|
|
|
|
func createDefaultScopesRepoAndKms(t *testing.T) (*iam.Scope, *iam.Scope, func() (*iam.Repository, error), *kms.Kms) {
|
|
t.Helper()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
wrap := db.TestWrapper(t)
|
|
iamRepo := iam.TestRepo(t, conn, wrap)
|
|
repoFn := func() (*iam.Repository, error) {
|
|
return iamRepo, nil
|
|
}
|
|
kms := kms.TestKms(t, conn, wrap)
|
|
|
|
oRes, pRes := iam.TestScopes(t, iamRepo)
|
|
|
|
oRes.Name = "defaultOrg"
|
|
oRes.Description = "defaultOrg"
|
|
repo, err := repoFn()
|
|
require.NoError(t, err)
|
|
oRes, _, err = repo.UpdateScope(context.Background(), oRes, 1, []string{"Name", "Description"})
|
|
require.NoError(t, err)
|
|
|
|
pRes.Name = "defaultProj"
|
|
pRes.Description = "defaultProj"
|
|
repo, err = repoFn()
|
|
require.NoError(t, err)
|
|
pRes, _, err = repo.UpdateScope(context.Background(), pRes, 1, []string{"Name", "Description"})
|
|
require.NoError(t, err)
|
|
return oRes, pRes, repoFn, kms
|
|
}
|
|
|
|
var globalAuthorizedCollectionActions = map[string]*structpb.ListValue{
|
|
"aliases": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"auth-methods": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"auth-tokens": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"groups": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"policies": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"roles": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"scopes": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("destroy-key-version"),
|
|
structpb.NewStringValue("list"),
|
|
structpb.NewStringValue("list-key-version-destruction-jobs"),
|
|
structpb.NewStringValue("list-keys"),
|
|
structpb.NewStringValue("rotate-keys"),
|
|
},
|
|
},
|
|
"session-recordings": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"storage-buckets": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"users": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"workers": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create:controller-led"),
|
|
structpb.NewStringValue("create:worker-led"),
|
|
structpb.NewStringValue("list"),
|
|
structpb.NewStringValue("read-certificate-authority"),
|
|
structpb.NewStringValue("reinitialize-certificate-authority"),
|
|
},
|
|
},
|
|
}
|
|
|
|
var orgAuthorizedCollectionActions = map[string]*structpb.ListValue{
|
|
"auth-methods": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"auth-tokens": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"groups": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"policies": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"roles": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"scopes": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("destroy-key-version"),
|
|
structpb.NewStringValue("list"),
|
|
structpb.NewStringValue("list-key-version-destruction-jobs"),
|
|
structpb.NewStringValue("list-keys"),
|
|
structpb.NewStringValue("rotate-keys"),
|
|
},
|
|
},
|
|
"session-recordings": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"storage-buckets": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"users": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
}
|
|
|
|
var projectAuthorizedCollectionActions = map[string]*structpb.ListValue{
|
|
"credential-stores": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"groups": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"host-catalogs": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"roles": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"sessions": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"scopes": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("destroy-key-version"),
|
|
structpb.NewStringValue("list-key-version-destruction-jobs"),
|
|
structpb.NewStringValue("list-keys"),
|
|
structpb.NewStringValue("rotate-keys"),
|
|
},
|
|
},
|
|
"targets": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("create"),
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestGet(t *testing.T) {
|
|
org, proj, repoFn, kms := createDefaultScopesRepoAndKms(t)
|
|
toMerge := &pbs.GetScopeRequest{
|
|
Id: proj.GetPublicId(),
|
|
}
|
|
|
|
oScope := &pb.Scope{
|
|
Id: org.GetPublicId(),
|
|
ScopeId: org.GetParentId(),
|
|
Scope: &pb.ScopeInfo{Id: "global", Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
|
|
Name: &wrapperspb.StringValue{Value: org.GetName()},
|
|
Description: &wrapperspb.StringValue{Value: org.GetDescription()},
|
|
CreatedTime: org.CreateTime.GetTimestamp(),
|
|
UpdatedTime: org.UpdateTime.GetTimestamp(),
|
|
Version: 2,
|
|
Type: scope.Org.String(),
|
|
AuthorizedActions: testAuthorizedOrgActions,
|
|
AuthorizedCollectionActions: orgAuthorizedCollectionActions,
|
|
}
|
|
|
|
pScope := &pb.Scope{
|
|
Id: proj.GetPublicId(),
|
|
ScopeId: proj.GetParentId(),
|
|
Scope: &pb.ScopeInfo{Id: oScope.Id, Type: scope.Org.String(), ParentScopeId: scope.Global.String(), Name: "defaultOrg", Description: "defaultOrg"},
|
|
Name: &wrapperspb.StringValue{Value: proj.GetName()},
|
|
Description: &wrapperspb.StringValue{Value: proj.GetDescription()},
|
|
CreatedTime: proj.CreateTime.GetTimestamp(),
|
|
UpdatedTime: proj.UpdateTime.GetTimestamp(),
|
|
Version: 2,
|
|
Type: scope.Project.String(),
|
|
AuthorizedActions: testAuthorizedPrjActions,
|
|
AuthorizedCollectionActions: projectAuthorizedCollectionActions,
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
scopeId string
|
|
req *pbs.GetScopeRequest
|
|
res *pbs.GetScopeResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "Get an existing org",
|
|
scopeId: "global",
|
|
req: &pbs.GetScopeRequest{Id: org.GetPublicId()},
|
|
res: &pbs.GetScopeResponse{Item: oScope},
|
|
},
|
|
{
|
|
name: "Get a non existing org",
|
|
scopeId: "global",
|
|
req: &pbs.GetScopeRequest{Id: "o_DoesntExis"},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Get an existing project",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.GetScopeRequest{Id: proj.GetPublicId()},
|
|
res: &pbs.GetScopeResponse{Item: pScope},
|
|
},
|
|
{
|
|
name: "Get a non existing project",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.GetScopeRequest{Id: "p_DoesntExist"},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Wrong id prefix",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.GetScopeRequest{Id: "j_1234567890"},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "space in id",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.GetScopeRequest{Id: "p_1 23456789"},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
req := proto.Clone(toMerge).(*pbs.GetScopeRequest)
|
|
proto.Merge(req, tc.req)
|
|
|
|
s, err := scopes.NewServiceFn(context.Background(), repoFn, kms, 1000)
|
|
require.NoError(err, "Couldn't create new project service.")
|
|
|
|
got, gErr := s.GetScope(auth.DisabledAuthTestContext(repoFn, tc.scopeId), req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "GetScope(%+v) got error\n%v, wanted\n%v", req, gErr, tc.err)
|
|
}
|
|
assert.Empty(cmp.Diff(
|
|
tc.res,
|
|
got,
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
cmpopts.SortSlices(func(a, b protocmp.Message) bool {
|
|
return a.String() < b.String()
|
|
}),
|
|
), "GetScope(%q) got response\n%q, wanted\n%q", req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestList(t *testing.T) {
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
wrap := db.TestWrapper(t)
|
|
iamRepo := iam.TestRepo(t, conn, wrap)
|
|
repoFn := func() (*iam.Repository, error) {
|
|
return iamRepo, nil
|
|
}
|
|
repo, err := repoFn()
|
|
require.NoError(t, err)
|
|
kms := kms.TestKms(t, conn, wrap)
|
|
|
|
oNoProjects, p1 := iam.TestScopes(t, repo)
|
|
_, err = repo.DeleteScope(context.Background(), p1.GetPublicId())
|
|
require.NoError(t, err)
|
|
oWithProjects, p2 := iam.TestScopes(t, repo)
|
|
_, err = repo.DeleteScope(context.Background(), p2.GetPublicId())
|
|
require.NoError(t, err)
|
|
|
|
outputFields := new(perms.OutputFields).SelfOrDefaults(globals.AnyAuthenticatedUserId)
|
|
var initialOrgs []*pb.Scope
|
|
globalScope := &pb.ScopeInfo{Id: "global", Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"}
|
|
oNoProjectsProto, err := scopes.ToProto(context.Background(), oNoProjects, handlers.WithOutputFields(outputFields))
|
|
require.NoError(t, err)
|
|
oNoProjectsProto.Scope = globalScope
|
|
oNoProjectsProto.AuthorizedActions = testAuthorizedOrgActions
|
|
oNoProjectsProto.AuthorizedCollectionActions = orgAuthorizedCollectionActions
|
|
oWithProjectsProto, err := scopes.ToProto(context.Background(), oWithProjects, handlers.WithOutputFields(outputFields))
|
|
require.NoError(t, err)
|
|
oWithProjectsProto.Scope = globalScope
|
|
oWithProjectsProto.AuthorizedActions = testAuthorizedOrgActions
|
|
oWithProjectsProto.AuthorizedCollectionActions = orgAuthorizedCollectionActions
|
|
initialOrgs = append(initialOrgs, oNoProjectsProto, oWithProjectsProto)
|
|
|
|
// Reverse slice since we order by create time (newest first)
|
|
slices.Reverse(initialOrgs)
|
|
|
|
cases := []struct {
|
|
name string
|
|
scopeId string
|
|
req *pbs.ListScopesRequest
|
|
res *pbs.ListScopesResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "List initial orgs",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.ListScopesRequest{ScopeId: "global"},
|
|
res: &pbs.ListScopesResponse{
|
|
Items: initialOrgs,
|
|
EstItemCount: 2,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "List No Projects",
|
|
scopeId: oNoProjects.GetPublicId(),
|
|
req: &pbs.ListScopesRequest{ScopeId: oNoProjects.GetPublicId()},
|
|
res: &pbs.ListScopesResponse{
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "Cant List Project Scopes",
|
|
scopeId: p1.GetPublicId(),
|
|
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],
|
|
EstItemCount: 1,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
s, err := scopes.NewServiceFn(context.Background(), repoFn, kms, 1000)
|
|
require.NoError(err, "Couldn't create new role service.")
|
|
|
|
// Test with non-anonymous listing first
|
|
got, gErr := s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "ListScopes(%+v) got error\n%v, wanted\n%v", tc.req, gErr, tc.err)
|
|
return
|
|
}
|
|
assert.Empty(
|
|
cmp.Diff(
|
|
got,
|
|
tc.res,
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
cmpopts.SortSlices(func(a, b protocmp.Message) bool {
|
|
return a.String() < b.String()
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListScopesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Now test with anonymous listing
|
|
got, gErr = s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId, auth.WithUserId(globals.AnonymousUserId)), tc.req)
|
|
require.NoError(gErr)
|
|
assert.Len(got.Items, len(tc.res.Items))
|
|
for _, item := range got.GetItems() {
|
|
assert.Nil(item.CreatedTime)
|
|
assert.Nil(item.UpdatedTime)
|
|
assert.Empty(item.Version)
|
|
}
|
|
})
|
|
}
|
|
|
|
var wantOrgs []*pb.Scope
|
|
for i := 0; i < 10; i++ {
|
|
newO, err := iam.NewOrg(ctx)
|
|
require.NoError(t, err)
|
|
o, err := repo.CreateScope(ctx, newO, "")
|
|
require.NoError(t, err)
|
|
wantOrgs = append(wantOrgs, &pb.Scope{
|
|
Id: o.GetPublicId(),
|
|
ScopeId: globalScope.GetId(),
|
|
Scope: globalScope,
|
|
CreatedTime: o.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: o.GetUpdateTime().GetTimestamp(),
|
|
Version: 1,
|
|
Type: scope.Org.String(),
|
|
AuthorizedActions: testAuthorizedOrgActions,
|
|
AuthorizedCollectionActions: orgAuthorizedCollectionActions,
|
|
})
|
|
}
|
|
|
|
// Reverse slice since we order by create time (newest first)
|
|
slices.Reverse(wantOrgs)
|
|
|
|
wantOrgs = append(wantOrgs, initialOrgs...)
|
|
|
|
var wantProjects []*pb.Scope
|
|
for i := 0; i < 10; i++ {
|
|
newP, err := iam.NewProject(ctx, oWithProjects.GetPublicId())
|
|
require.NoError(t, err)
|
|
p, err := repo.CreateScope(ctx, newP, "")
|
|
require.NoError(t, err)
|
|
wantProjects = append(wantProjects, &pb.Scope{
|
|
Id: p.GetPublicId(),
|
|
ScopeId: oWithProjects.GetPublicId(),
|
|
Scope: &pb.ScopeInfo{Id: oWithProjects.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
CreatedTime: p.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: p.GetUpdateTime().GetTimestamp(),
|
|
Version: 1,
|
|
Type: scope.Project.String(),
|
|
AuthorizedActions: testAuthorizedPrjActions,
|
|
AuthorizedCollectionActions: projectAuthorizedCollectionActions,
|
|
})
|
|
}
|
|
|
|
// Reverse slice since we order by create time (newest first)
|
|
slices.Reverse(wantProjects)
|
|
|
|
totalScopes := append(wantOrgs, wantProjects...)
|
|
|
|
cases = []struct {
|
|
name string
|
|
scopeId string
|
|
req *pbs.ListScopesRequest
|
|
res *pbs.ListScopesResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "List Many Orgs",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.ListScopesRequest{ScopeId: "global"},
|
|
res: &pbs.ListScopesResponse{
|
|
Items: wantOrgs,
|
|
EstItemCount: 12,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "List Many Projects",
|
|
scopeId: oWithProjects.GetPublicId(),
|
|
req: &pbs.ListScopesRequest{ScopeId: oWithProjects.GetPublicId()},
|
|
res: &pbs.ListScopesResponse{
|
|
Items: wantProjects,
|
|
EstItemCount: 10,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "List Global Recursively",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.ListScopesRequest{ScopeId: scope.Global.String(), Recursive: true},
|
|
res: &pbs.ListScopesResponse{
|
|
Items: totalScopes,
|
|
EstItemCount: 22,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
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,
|
|
EstItemCount: 12,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
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,
|
|
EstItemCount: 10,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
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) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
s, err := scopes.NewServiceFn(context.Background(), repoFn, kms, 1000)
|
|
require.NoError(err, "Couldn't create new scope service.")
|
|
|
|
// Test with non-anonymous listing first
|
|
got, gErr := s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "ListScopes(%+v) got error\n%v, wanted\n%v", tc.req, gErr, tc.err)
|
|
return
|
|
}
|
|
require.NoError(gErr)
|
|
assert.Empty(
|
|
cmp.Diff(
|
|
got,
|
|
tc.res,
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
cmpopts.SortSlices(func(a, b protocmp.Message) bool {
|
|
return a.String() < b.String()
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListScopesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Now test with anonymous listing
|
|
got, gErr = s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId, auth.WithUserId(globals.AnonymousUserId)), tc.req)
|
|
require.NoError(gErr)
|
|
assert.Len(got.Items, len(tc.res.Items))
|
|
for _, item := range got.GetItems() {
|
|
assert.Nil(item.CreatedTime)
|
|
assert.Nil(item.UpdatedTime)
|
|
assert.Empty(item.Version)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListPagination(t *testing.T) {
|
|
// Set database read timeout to avoid duplicates in response
|
|
oldReadTimeout := globals.RefreshReadLookbackDuration
|
|
globals.RefreshReadLookbackDuration = 0
|
|
t.Cleanup(func() {
|
|
globals.RefreshReadLookbackDuration = oldReadTimeout
|
|
})
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
sqlDb, err := conn.SqlDB(ctx)
|
|
require.NoError(t, err)
|
|
wrapper := db.TestWrapper(t)
|
|
kms := kms.TestKms(t, conn, wrapper)
|
|
rw := db.New(conn)
|
|
|
|
repoFn := func() (*iam.Repository, error) {
|
|
return iam.TestRepo(t, conn, wrapper), nil
|
|
}
|
|
repo, err := repoFn()
|
|
require.NoError(t, err)
|
|
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return repo, nil
|
|
}
|
|
iamRepo, err := iamRepoFn()
|
|
require.NoError(t, err)
|
|
|
|
tokenRepoFn := func() (*authtoken.Repository, error) {
|
|
return authtoken.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
tokenRepo, err := tokenRepoFn()
|
|
require.NoError(t, err)
|
|
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return server.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
|
|
oWithProjects, p2 := iam.TestScopes(t, repo, iam.WithSkipDefaultRoleCreation(true))
|
|
_, err = repo.DeleteScope(context.Background(), p2.GetPublicId())
|
|
require.NoError(t, err)
|
|
|
|
paginationAuthorizedCollectionActions := map[string]*structpb.ListValue{
|
|
"sessions": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
"targets": {
|
|
Values: []*structpb.Value{
|
|
structpb.NewStringValue("list"),
|
|
},
|
|
},
|
|
}
|
|
|
|
var wantProjects []*pb.Scope
|
|
for i := 0; i < 10; i++ {
|
|
newP, err := iam.NewProject(ctx, oWithProjects.GetPublicId())
|
|
require.NoError(t, err)
|
|
p, err := repo.CreateScope(ctx, newP, "")
|
|
require.NoError(t, err)
|
|
wantProjects = append(wantProjects, &pb.Scope{
|
|
Id: p.GetPublicId(),
|
|
ScopeId: oWithProjects.GetPublicId(),
|
|
Scope: &pb.ScopeInfo{Id: oWithProjects.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
CreatedTime: p.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: p.GetUpdateTime().GetTimestamp(),
|
|
Version: 1,
|
|
Type: scope.Project.String(),
|
|
AuthorizedActions: testAuthorizedPrjActions,
|
|
AuthorizedCollectionActions: paginationAuthorizedCollectionActions,
|
|
})
|
|
}
|
|
|
|
// Reverse slice since we order by create time (newest first)
|
|
slices.Reverse(wantProjects)
|
|
|
|
// Run analyze to update scope estimate
|
|
_, err = sqlDb.ExecContext(ctx, "analyze")
|
|
require.NoError(t, err)
|
|
|
|
authMethod := ldap.TestAuthMethod(t, conn, wrapper, oWithProjects.PublicId, []string{"ldaps://no-managed-groups"})
|
|
acct := ldap.TestAccount(t, conn, authMethod, "test-login-last")
|
|
u := iam.TestUser(t, iamRepo, oWithProjects.GetPublicId(), iam.WithAccountIds(acct.PublicId))
|
|
|
|
// privProjRole := iam.TestRole(t, conn, pwt.GetPublicId())
|
|
// iam.TestRoleGrant(t, conn, privProjRole.GetPublicId(), "ids=*;type=*;actions=*")
|
|
// iam.TestUserRole(t, conn, privProjRole.GetPublicId(), u.GetPublicId())
|
|
privOrgRole := iam.TestRole(t, conn, oWithProjects.GetPublicId())
|
|
iam.TestRoleGrant(t, conn, privOrgRole.GetPublicId(), "ids=*;type=*;actions=*")
|
|
iam.TestUserRole(t, conn, privOrgRole.GetPublicId(), u.GetPublicId())
|
|
|
|
at, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
|
|
|
requestInfo := authpb.RequestInfo{
|
|
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
|
PublicId: at.GetPublicId(),
|
|
Token: at.GetToken(),
|
|
}
|
|
requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
|
ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
|
|
|
|
req := &pbs.ListScopesRequest{
|
|
ScopeId: oWithProjects.GetPublicId(),
|
|
Filter: "",
|
|
ListToken: "",
|
|
PageSize: 2,
|
|
}
|
|
|
|
s, err := scopes.NewServiceFn(ctx, repoFn, kms, 1000)
|
|
require.NoError(t, err)
|
|
|
|
got, err := s.ListScopes(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 2)
|
|
|
|
// all comparisons will be done without refresh token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListScopesResponse{
|
|
Items: wantProjects[0:2],
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 12,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
cmpopts.SortSlices(func(a, b protocmp.Message) bool {
|
|
return a.String() < b.String()
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListScopesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// second page
|
|
req.ListToken = got.ListToken
|
|
got, err = s.ListScopes(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 2)
|
|
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListScopesResponse{
|
|
Items: wantProjects[2:4],
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 12,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
cmpopts.SortSlices(func(a, b protocmp.Message) bool {
|
|
return a.String() < b.String()
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListScopesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// remainder of results
|
|
req.ListToken = got.ListToken
|
|
req.PageSize = 6
|
|
got, err = s.ListScopes(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 6)
|
|
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListScopesResponse{
|
|
Items: wantProjects[4:],
|
|
ResponseType: "complete",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 12,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
cmpopts.SortSlices(func(a, b protocmp.Message) bool {
|
|
return a.String() < b.String()
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListScopesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// create another scope
|
|
newP, err := iam.NewProject(ctx, oWithProjects.GetPublicId())
|
|
require.NoError(t, err)
|
|
p, err := repo.CreateScope(ctx, newP, "")
|
|
require.NoError(t, err)
|
|
newScope := &pb.Scope{
|
|
Id: p.GetPublicId(),
|
|
ScopeId: oWithProjects.GetPublicId(),
|
|
Scope: &pb.ScopeInfo{Id: oWithProjects.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
CreatedTime: p.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: p.GetUpdateTime().GetTimestamp(),
|
|
Version: 1,
|
|
Type: scope.Project.String(),
|
|
AuthorizedActions: testAuthorizedPrjActions,
|
|
AuthorizedCollectionActions: paginationAuthorizedCollectionActions,
|
|
}
|
|
// Add to the front of the slice since it's the most recently updated
|
|
wantProjects = append([]*pb.Scope{newScope}, wantProjects...)
|
|
|
|
// delete different scope
|
|
_, err = repo.DeleteScope(ctx, wantProjects[len(wantProjects)-1].Id)
|
|
wantProjects = wantProjects[:len(wantProjects)-1]
|
|
require.NoError(t, err)
|
|
|
|
// Run analyze to update postgres estimates
|
|
_, err = sqlDb.ExecContext(ctx, "analyze")
|
|
require.NoError(t, err)
|
|
|
|
// request updated results
|
|
// since both creating and deleting scopes will affect the grantsHash
|
|
// we expect this to error
|
|
req.ListToken = got.ListToken
|
|
_, err = s.ListScopes(ctx, req)
|
|
require.Error(t, err)
|
|
require.True(t, errors.Match(errors.T(errors.InvalidListToken), err))
|
|
|
|
// clear the refresh token
|
|
req.ListToken = ""
|
|
req.PageSize = 2
|
|
got, err = s.ListScopes(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 2)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListScopesResponse{
|
|
Items: wantProjects[0:2],
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 12,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
cmpopts.SortSlices(func(a, b protocmp.Message) bool {
|
|
return a.String() < b.String()
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListScopesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Request new page with filter requiring looping
|
|
// to fill the page.
|
|
req.ListToken = ""
|
|
req.PageSize = 1
|
|
req.Filter = fmt.Sprintf(`"/item/id"==%q or "/item/id"==%q`, wantProjects[len(wantProjects)-2].Id, wantProjects[len(wantProjects)-1].Id)
|
|
got, err = s.ListScopes(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListScopesResponse{
|
|
Items: []*pb.Scope{wantProjects[len(wantProjects)-2]},
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
// Should be empty again
|
|
RemovedIds: nil,
|
|
EstItemCount: 12,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
cmpopts.SortSlices(func(a, b protocmp.Message) bool {
|
|
return a.String() < b.String()
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListScopesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
req.ListToken = got.ListToken
|
|
// Get the second page
|
|
got, err = s.ListScopes(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListScopesResponse{
|
|
Items: []*pb.Scope{wantProjects[len(wantProjects)-1]},
|
|
ResponseType: "complete",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 12,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
cmpopts.SortSlices(func(a, b protocmp.Message) bool {
|
|
return a.String() < b.String()
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListScopesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Create unauthenticated user
|
|
unauthAt := authtoken.TestAuthToken(t, conn, kms, oWithProjects.GetPublicId())
|
|
unauthR := iam.TestRole(t, conn, p.GetPublicId())
|
|
_ = iam.TestUserRole(t, conn, unauthR.GetPublicId(), unauthAt.GetIamUserId())
|
|
|
|
// Make a request with the unauthenticated user,
|
|
// ensure the response contains the pagination parameters.
|
|
requestInfo = authpb.RequestInfo{
|
|
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
|
PublicId: unauthAt.GetPublicId(),
|
|
Token: unauthAt.GetToken(),
|
|
}
|
|
requestContext = context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
|
ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
|
|
|
|
_, err = s.ListScopes(ctx, &pbs.ListScopesRequest{
|
|
ScopeId: "global",
|
|
Recursive: true,
|
|
})
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, handlers.ForbiddenError(), err)
|
|
}
|
|
|
|
func TestDelete(t *testing.T) {
|
|
org, proj, repoFn, kms := createDefaultScopesRepoAndKms(t)
|
|
|
|
s, err := scopes.NewServiceFn(context.Background(), repoFn, kms, 1000)
|
|
require.NoError(t, err, "Error when getting new project service.")
|
|
|
|
cases := []struct {
|
|
name string
|
|
scopeId string
|
|
req *pbs.DeleteScopeRequest
|
|
res *pbs.DeleteScopeResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "Delete an Existing Project",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.DeleteScopeRequest{
|
|
Id: proj.GetPublicId(),
|
|
},
|
|
},
|
|
{
|
|
name: "Delete bad project id Project",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.DeleteScopeRequest{
|
|
Id: "p_DoesntExist",
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Bad Project Id formatting",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.DeleteScopeRequest{
|
|
Id: "bad_format",
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Delete an Existing Org",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.DeleteScopeRequest{
|
|
Id: org.GetPublicId(),
|
|
},
|
|
},
|
|
{
|
|
name: "Delete bad org id Org",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.DeleteScopeRequest{
|
|
Id: "p_DoesntExist",
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Bad Org Id formatting",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.DeleteScopeRequest{
|
|
Id: "bad_format",
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
got, gErr := s.DeleteScope(auth.DisabledAuthTestContext(repoFn, tc.scopeId), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "DeleteScope(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
}
|
|
assert.EqualValuesf(tc.res, got, "DeleteScope(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDelete_twice(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
org, proj, repoFn, kms := createDefaultScopesRepoAndKms(t)
|
|
|
|
s, err := scopes.NewServiceFn(context.Background(), repoFn, kms, 1000)
|
|
require.NoError(err, "Error when getting new scopes service")
|
|
ctx := auth.DisabledAuthTestContext(repoFn, org.GetPublicId())
|
|
req := &pbs.DeleteScopeRequest{
|
|
Id: proj.GetPublicId(),
|
|
}
|
|
_, gErr := s.DeleteScope(ctx, req)
|
|
assert.NoError(gErr, "First attempt")
|
|
_, gErr = s.DeleteScope(ctx, req)
|
|
assert.Error(gErr, "Second attempt")
|
|
assert.True(errors.Is(gErr, handlers.ApiErrorWithCode(codes.NotFound)), "Expected not found for the second delete.")
|
|
|
|
ctx = auth.DisabledAuthTestContext(repoFn, scope.Global.String())
|
|
req = &pbs.DeleteScopeRequest{
|
|
Id: org.GetPublicId(),
|
|
}
|
|
_, gErr = s.DeleteScope(ctx, req)
|
|
assert.NoError(gErr, "First attempt")
|
|
_, gErr = s.DeleteScope(ctx, req)
|
|
assert.Error(gErr, "Second attempt")
|
|
assert.True(errors.Is(gErr, handlers.ApiErrorWithCode(codes.NotFound)), "Expected not found for the second delete.")
|
|
}
|
|
|
|
func TestCreate(t *testing.T) {
|
|
ctx := context.Background()
|
|
defaultOrg, defaultProj, repoFn, kms := createDefaultScopesRepoAndKms(t)
|
|
defaultProjCreated := defaultProj.GetCreateTime().GetTimestamp().AsTime()
|
|
toMerge := &pbs.CreateScopeRequest{}
|
|
|
|
repo, err := repoFn()
|
|
require.NoError(t, err)
|
|
globalUser, err := iam.NewUser(ctx, scope.Global.String())
|
|
require.NoError(t, err)
|
|
globalUser, err = repo.CreateUser(ctx, globalUser)
|
|
require.NoError(t, err)
|
|
orgUser, err := iam.NewUser(ctx, defaultOrg.GetPublicId())
|
|
require.NoError(t, err)
|
|
orgUser, err = repo.CreateUser(ctx, orgUser)
|
|
require.NoError(t, err)
|
|
|
|
cases := []struct {
|
|
name string
|
|
scopeId string
|
|
req *pbs.CreateScopeRequest
|
|
res *pbs.CreateScopeResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "Create a valid Project",
|
|
scopeId: defaultOrg.GetPublicId(),
|
|
req: &pbs.CreateScopeRequest{
|
|
Item: &pb.Scope{
|
|
ScopeId: defaultOrg.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "name"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
},
|
|
},
|
|
res: &pbs.CreateScopeResponse{
|
|
Uri: "scopes/p_",
|
|
Item: &pb.Scope{
|
|
ScopeId: defaultOrg.GetPublicId(),
|
|
Scope: &pb.ScopeInfo{Id: defaultOrg.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String(), Name: "defaultOrg", Description: "defaultOrg"},
|
|
Name: &wrapperspb.StringValue{Value: "name"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Version: 1,
|
|
Type: scope.Project.String(),
|
|
AuthorizedActions: testAuthorizedPrjActions,
|
|
AuthorizedCollectionActions: projectAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Create a valid Org",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.CreateScopeRequest{
|
|
Item: &pb.Scope{
|
|
ScopeId: scope.Global.String(),
|
|
Name: &wrapperspb.StringValue{Value: "name"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
},
|
|
},
|
|
res: &pbs.CreateScopeResponse{
|
|
Uri: "scopes/o_",
|
|
Item: &pb.Scope{
|
|
ScopeId: scope.Global.String(),
|
|
Scope: &pb.ScopeInfo{Id: scope.Global.String(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
|
|
Name: &wrapperspb.StringValue{Value: "name"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Version: 1,
|
|
Type: scope.Org.String(),
|
|
AuthorizedActions: testAuthorizedOrgActions,
|
|
AuthorizedCollectionActions: orgAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Create a valid Project with type specified",
|
|
scopeId: defaultOrg.GetPublicId(),
|
|
req: &pbs.CreateScopeRequest{
|
|
Item: &pb.Scope{
|
|
ScopeId: defaultOrg.GetPublicId(),
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: scope.Project.String(),
|
|
},
|
|
},
|
|
res: &pbs.CreateScopeResponse{
|
|
Uri: "scopes/p_",
|
|
Item: &pb.Scope{
|
|
ScopeId: defaultOrg.GetPublicId(),
|
|
Scope: &pb.ScopeInfo{Id: defaultOrg.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String(), Name: "defaultOrg", Description: "defaultOrg"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Version: 1,
|
|
Type: scope.Project.String(),
|
|
AuthorizedActions: testAuthorizedPrjActions,
|
|
AuthorizedCollectionActions: projectAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Create a valid Org with type specified",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.CreateScopeRequest{
|
|
Item: &pb.Scope{
|
|
ScopeId: scope.Global.String(),
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: scope.Org.String(),
|
|
},
|
|
},
|
|
res: &pbs.CreateScopeResponse{
|
|
Uri: "scopes/o_",
|
|
Item: &pb.Scope{
|
|
ScopeId: scope.Global.String(),
|
|
Scope: &pb.ScopeInfo{Id: scope.Global.String(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Version: 1,
|
|
Type: scope.Org.String(),
|
|
AuthorizedActions: testAuthorizedOrgActions,
|
|
AuthorizedCollectionActions: orgAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Create a valid org with leading and trailing whitespace on name and description",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.CreateScopeRequest{
|
|
Item: &pb.Scope{
|
|
ScopeId: scope.Global.String(),
|
|
Description: &wrapperspb.StringValue{Value: " test description with whitespace "},
|
|
Type: scope.Org.String(),
|
|
Name: &wrapperspb.StringValue{Value: " test org name with whitespace "},
|
|
},
|
|
},
|
|
res: &pbs.CreateScopeResponse{
|
|
Uri: "scopes/o_",
|
|
Item: &pb.Scope{
|
|
ScopeId: scope.Global.String(),
|
|
Scope: &pb.ScopeInfo{Id: scope.Global.String(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
|
|
Description: &wrapperspb.StringValue{Value: "test description with whitespace"}, // assert the whitespace is trimmed
|
|
Name: &wrapperspb.StringValue{Value: "test org name with whitespace"}, // assert the whitespace is trimmed
|
|
Version: 1,
|
|
Type: scope.Org.String(),
|
|
AuthorizedActions: testAuthorizedOrgActions,
|
|
AuthorizedCollectionActions: orgAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Create a global type scope",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.CreateScopeRequest{
|
|
Item: &pb.Scope{
|
|
ScopeId: scope.Global.String(),
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: scope.Global.String(),
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Create with a custom version number",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.CreateScopeRequest{
|
|
Item: &pb.Scope{
|
|
ScopeId: scope.Global.String(),
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: scope.Org.String(),
|
|
Version: 5,
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Project with bad type specified",
|
|
scopeId: defaultOrg.GetPublicId(),
|
|
req: &pbs.CreateScopeRequest{
|
|
Item: &pb.Scope{
|
|
ScopeId: defaultOrg.GetPublicId(),
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: scope.Org.String(),
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Org with bad type specified",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.CreateScopeRequest{
|
|
Item: &pb.Scope{
|
|
ScopeId: scope.Global.String(),
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: scope.Project.String(),
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Can't specify Id",
|
|
scopeId: defaultOrg.GetPublicId(),
|
|
req: &pbs.CreateScopeRequest{Item: &pb.Scope{
|
|
Id: "not allowed to be set",
|
|
}},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Can't specify Created Time",
|
|
scopeId: defaultOrg.GetPublicId(),
|
|
req: &pbs.CreateScopeRequest{Item: &pb.Scope{
|
|
CreatedTime: timestamppb.Now(),
|
|
}},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Can't specify Update Time",
|
|
scopeId: defaultOrg.GetPublicId(),
|
|
req: &pbs.CreateScopeRequest{Item: &pb.Scope{
|
|
UpdatedTime: timestamppb.Now(),
|
|
}},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
for _, withUserId := range []bool{false, true} {
|
|
t.Run(fmt.Sprintf("%s-userid-%t", tc.name, withUserId), func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
var name string
|
|
if tc.req != nil && tc.req.GetItem() != nil && tc.req.GetItem().GetName() != nil {
|
|
name = tc.req.GetItem().GetName().GetValue()
|
|
localName := name
|
|
defer func() {
|
|
tc.res.GetItem().GetName().Value = localName
|
|
}()
|
|
}
|
|
req := proto.Clone(toMerge).(*pbs.CreateScopeRequest)
|
|
proto.Merge(req, tc.req)
|
|
|
|
s, err := scopes.NewServiceFn(context.Background(), repoFn, kms, 1000)
|
|
require.NoError(err, "Error when getting new project service.")
|
|
|
|
if name != "" {
|
|
// Test cases can specify names with leading and trailing
|
|
// whitespace to test for Boundary's whitespace removal.
|
|
// Since we're adding to the name before we send the request
|
|
// off, we need to make sure we keep it as we found it.
|
|
|
|
// Find where leading whitespace ends, add withUserId there.
|
|
idx := 0
|
|
for i, r := range name {
|
|
if !unicode.IsSpace(r) {
|
|
idx = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if idx == 0 {
|
|
name = fmt.Sprintf("%t-%s", withUserId, name)
|
|
} else {
|
|
name = fmt.Sprintf("%s%t-%s", name[:idx], withUserId, name[idx:])
|
|
}
|
|
|
|
req.GetItem().GetName().Value = name
|
|
}
|
|
var userId string
|
|
if withUserId {
|
|
if tc.scopeId == scope.Global.String() {
|
|
userId = globalUser.GetPublicId()
|
|
} else {
|
|
userId = orgUser.GetPublicId()
|
|
}
|
|
assert.NotEmpty(userId)
|
|
}
|
|
got, gErr := s.CreateScope(auth.DisabledAuthTestContext(repoFn, tc.scopeId, auth.WithUserId(userId)), req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "CreateScope(%+v) got error %v, wanted %v", req, gErr, tc.err)
|
|
}
|
|
if got != nil {
|
|
assert.Contains(got.GetUri(), tc.res.Uri)
|
|
switch tc.scopeId {
|
|
case "global":
|
|
assert.True(strings.HasPrefix(got.GetItem().GetId(), "o_"))
|
|
default:
|
|
assert.True(strings.HasPrefix(got.GetItem().GetId(), "p_"))
|
|
}
|
|
gotCreateTime := got.GetItem().GetCreatedTime().AsTime()
|
|
gotUpdateTime := got.GetItem().GetUpdatedTime().AsTime()
|
|
// Verify it is a project created after the test setup's default project
|
|
assert.True(gotCreateTime.After(defaultProjCreated), "New scope should have been created after default project. Was created %v, which is after %v", gotCreateTime, defaultProjCreated)
|
|
assert.True(gotUpdateTime.After(defaultProjCreated), "New scope should have been updated after default project. Was updated %v, which is after %v", gotUpdateTime, defaultProjCreated)
|
|
|
|
if withUserId {
|
|
repo, err := repoFn()
|
|
require.NoError(err)
|
|
noopFilter := func(ctx context.Context, item *iam.Role) (bool, error) {
|
|
return true, nil
|
|
}
|
|
roles, err := iam.ListRoles(ctx, []byte("test"), globals.DefaultMaxPageSize, noopFilter, repo, []string{got.GetItem().GetId()})
|
|
require.NoError(err)
|
|
switch tc.scopeId {
|
|
case defaultOrg.PublicId:
|
|
require.Len(roles.Items, 2)
|
|
case "global":
|
|
require.Len(roles.Items, 2)
|
|
}
|
|
for _, role := range roles.Items {
|
|
switch role.GetName() {
|
|
case "Administration":
|
|
assert.Equal(fmt.Sprintf("Role created for administration of scope %s by user %s at its creation time", got.GetItem().GetId(), userId), role.GetDescription())
|
|
case "Login and Default Grants":
|
|
assert.Equal(fmt.Sprintf("Role created for login capability, account self-management, and other default grants for users of scope %s at its creation time", got.GetItem().GetId()), role.GetDescription())
|
|
case "Default Grants":
|
|
assert.Equal(fmt.Sprintf("Role created to provide default grants to users of scope %s at its creation time", got.GetItem().GetId()), role.GetDescription())
|
|
default:
|
|
t.Fatal("unexpected role name", role.GetName())
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear all values which are hard to compare against.
|
|
assert.Equal(strings.TrimSpace(name), got.GetItem().GetName().GetValue())
|
|
got.Item.Name = tc.res.GetItem().GetName()
|
|
got.Uri, tc.res.Uri = "", ""
|
|
got.Item.Id, tc.res.Item.Id = "", ""
|
|
got.Item.CreatedTime, got.Item.UpdatedTime, tc.res.Item.CreatedTime, tc.res.Item.UpdatedTime = nil, nil, nil, nil
|
|
}
|
|
assert.Empty(cmp.Diff(
|
|
tc.res,
|
|
got,
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
cmpopts.SortSlices(func(a, b protocmp.Message) bool {
|
|
return a.String() < b.String()
|
|
}),
|
|
), "CreateScope(%q) got response %q, wanted %q", req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpdate(t *testing.T) {
|
|
org, proj, repoFn, kms := createDefaultScopesRepoAndKms(t)
|
|
tested, err := scopes.NewServiceFn(context.Background(), repoFn, kms, 1000)
|
|
require.NoError(t, err, "Error when getting new project service.")
|
|
|
|
iamRepo, err := repoFn()
|
|
require.NoError(t, err)
|
|
global, err := iamRepo.LookupScope(context.Background(), "global")
|
|
require.NoError(t, err)
|
|
|
|
var orgVersion uint32 = 2
|
|
var projVersion uint32 = 2
|
|
globalVersion := global.Version
|
|
|
|
resetOrg := func() {
|
|
orgVersion++
|
|
repo, err := repoFn()
|
|
require.NoError(t, err, "Couldn't get a new repo")
|
|
org, _, err = repo.UpdateScope(context.Background(), org, orgVersion, []string{"Name", "Description"})
|
|
require.NoError(t, err, "Failed to reset the org")
|
|
orgVersion++
|
|
}
|
|
|
|
resetProject := func() {
|
|
projVersion++
|
|
repo, err := repoFn()
|
|
require.NoError(t, err, "Couldn't get a new repo")
|
|
proj, _, err = repo.UpdateScope(context.Background(), proj, projVersion, []string{"Name", "Description"})
|
|
require.NoError(t, err, "Failed to reset the project")
|
|
projVersion++
|
|
}
|
|
|
|
resetGlobal := func() {
|
|
repo, err := repoFn()
|
|
require.NoError(t, err, "Couldn't get a new repo")
|
|
globalScope := iam.AllocScope()
|
|
globalScope.PublicId = "global"
|
|
global, _, err = repo.UpdateScope(context.Background(), &globalScope, globalVersion, []string{"Name", "Description"})
|
|
require.NoError(t, err, "Failed to reset the global scope")
|
|
globalVersion = global.Version
|
|
}
|
|
|
|
projCreated := proj.GetCreateTime().GetTimestamp().AsTime()
|
|
projToMerge := &pbs.UpdateScopeRequest{
|
|
Id: proj.GetPublicId(),
|
|
}
|
|
|
|
orgToMerge := &pbs.UpdateScopeRequest{
|
|
Id: org.GetPublicId(),
|
|
}
|
|
|
|
globalToMerge := &pbs.UpdateScopeRequest{
|
|
Id: "global",
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
scopeId string
|
|
req *pbs.UpdateScopeRequest
|
|
res *pbs.UpdateScopeResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "Update an Existing Project",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name", "description"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: scope.Project.String(),
|
|
},
|
|
},
|
|
res: &pbs.UpdateScopeResponse{
|
|
Item: &pb.Scope{
|
|
Id: proj.GetPublicId(),
|
|
ScopeId: org.GetPublicId(),
|
|
Scope: &pb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String(), Name: "defaultOrg", Description: "defaultOrg"},
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
CreatedTime: proj.GetCreateTime().GetTimestamp(),
|
|
Type: scope.Project.String(),
|
|
AuthorizedActions: testAuthorizedPrjActions,
|
|
AuthorizedCollectionActions: projectAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update an Existing Org",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name", "description"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: scope.Org.String(),
|
|
},
|
|
},
|
|
res: &pbs.UpdateScopeResponse{
|
|
Item: &pb.Scope{
|
|
Id: org.GetPublicId(),
|
|
ScopeId: scope.Global.String(),
|
|
Scope: &pb.ScopeInfo{Id: scope.Global.String(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
CreatedTime: org.GetCreateTime().GetTimestamp(),
|
|
Type: scope.Org.String(),
|
|
AuthorizedActions: testAuthorizedOrgActions,
|
|
AuthorizedCollectionActions: orgAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update org name and description with whitespace",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name", "description"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: " new name "},
|
|
Description: &wrapperspb.StringValue{Value: " new desc "},
|
|
Type: scope.Org.String(),
|
|
},
|
|
},
|
|
res: &pbs.UpdateScopeResponse{
|
|
Item: &pb.Scope{
|
|
Id: org.GetPublicId(),
|
|
ScopeId: scope.Global.String(),
|
|
Scope: &pb.ScopeInfo{Id: scope.Global.String(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
|
|
Name: &wrapperspb.StringValue{Value: "new name"},
|
|
Description: &wrapperspb.StringValue{Value: "new desc"},
|
|
CreatedTime: org.GetCreateTime().GetTimestamp(),
|
|
Type: scope.Org.String(),
|
|
AuthorizedActions: testAuthorizedOrgActions,
|
|
AuthorizedCollectionActions: orgAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update global",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name", "description"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: scope.Global.String(),
|
|
},
|
|
},
|
|
res: &pbs.UpdateScopeResponse{
|
|
Item: &pb.Scope{
|
|
Id: scope.Global.String(),
|
|
Scope: &pb.ScopeInfo{Id: scope.Global.String(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
CreatedTime: global.GetCreateTime().GetTimestamp(),
|
|
Type: scope.Global.String(),
|
|
AuthorizedActions: testAuthorizedGlobalActions,
|
|
AuthorizedCollectionActions: globalAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Multiple Paths in single string",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name,description"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
},
|
|
},
|
|
res: &pbs.UpdateScopeResponse{
|
|
Item: &pb.Scope{
|
|
Id: proj.GetPublicId(),
|
|
ScopeId: org.GetPublicId(),
|
|
Scope: &pb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String(), Name: "defaultOrg", Description: "defaultOrg"},
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
CreatedTime: proj.GetCreateTime().GetTimestamp(),
|
|
Type: scope.Project.String(),
|
|
AuthorizedActions: testAuthorizedPrjActions,
|
|
AuthorizedCollectionActions: projectAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "No Update Mask",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Cant modify type",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name", "type"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Type: scope.Org.String(),
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "No Paths in Mask",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Invalidly formatted scope id - org scope",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
Id: "o_!@£$*",
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Invalidly formatted scope id - proj scope",
|
|
scopeId: proj.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
Id: "p_!@£$*",
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Invalidly formatted scope id - unknown scope",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
Id: "bla_123",
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Unknown scope type - org scope",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
Id: org.GetPublicId(),
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
|
|
Item: &pb.Scope{
|
|
Type: "test",
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Unknown scope type - proj scope",
|
|
scopeId: proj.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
Id: proj.GetPublicId(),
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
|
|
Item: &pb.Scope{
|
|
Type: "test",
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Unknown primary auth method id",
|
|
scopeId: proj.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
Id: proj.GetPublicId(),
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
PrimaryAuthMethodId: &wrapperspb.StringValue{Value: "test_123"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Update org name and description with whitespace only",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name", "description"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: " "},
|
|
Description: &wrapperspb.StringValue{Value: " "},
|
|
Type: scope.Org.String(),
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Update org name and description with non-printable characters",
|
|
scopeId: scope.Global.String(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name", "description"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: " na\u200Bme "},
|
|
Description: &wrapperspb.StringValue{Value: " desc\u200Bription "},
|
|
Type: scope.Org.String(),
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Only non-existent paths in Mask",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{"nonexistent_field"}},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Unset Name",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Description: &wrapperspb.StringValue{Value: "ignored"},
|
|
},
|
|
},
|
|
res: &pbs.UpdateScopeResponse{
|
|
Item: &pb.Scope{
|
|
Id: proj.GetPublicId(),
|
|
ScopeId: org.GetPublicId(),
|
|
Scope: &pb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String(), Name: "defaultOrg", Description: "defaultOrg"},
|
|
Description: &wrapperspb.StringValue{Value: "defaultProj"},
|
|
CreatedTime: proj.GetCreateTime().GetTimestamp(),
|
|
Type: scope.Project.String(),
|
|
AuthorizedActions: testAuthorizedPrjActions,
|
|
AuthorizedCollectionActions: projectAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Unset Description",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"description"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Name: &wrappers.StringValue{Value: "ignored"},
|
|
},
|
|
},
|
|
res: &pbs.UpdateScopeResponse{
|
|
Item: &pb.Scope{
|
|
Id: proj.GetPublicId(),
|
|
ScopeId: org.GetPublicId(),
|
|
Scope: &pb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String(), Name: "defaultOrg", Description: "defaultOrg"},
|
|
Name: &wrappers.StringValue{Value: "defaultProj"},
|
|
CreatedTime: proj.GetCreateTime().GetTimestamp(),
|
|
Type: scope.Project.String(),
|
|
AuthorizedActions: testAuthorizedPrjActions,
|
|
AuthorizedCollectionActions: projectAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update Only Name",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "updated"},
|
|
Description: &wrapperspb.StringValue{Value: "ignored"},
|
|
},
|
|
},
|
|
res: &pbs.UpdateScopeResponse{
|
|
Item: &pb.Scope{
|
|
Id: proj.GetPublicId(),
|
|
ScopeId: org.GetPublicId(),
|
|
Scope: &pb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String(), Name: "defaultOrg", Description: "defaultOrg"},
|
|
Name: &wrapperspb.StringValue{Value: "updated"},
|
|
Description: &wrapperspb.StringValue{Value: "defaultProj"},
|
|
CreatedTime: proj.GetCreateTime().GetTimestamp(),
|
|
Type: scope.Project.String(),
|
|
AuthorizedActions: testAuthorizedPrjActions,
|
|
AuthorizedCollectionActions: projectAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update Only Description",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"description"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "ignored"},
|
|
Description: &wrapperspb.StringValue{Value: "notignored"},
|
|
},
|
|
},
|
|
res: &pbs.UpdateScopeResponse{
|
|
Item: &pb.Scope{
|
|
Id: proj.GetPublicId(),
|
|
ScopeId: org.GetPublicId(),
|
|
Scope: &pb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String(), Name: "defaultOrg", Description: "defaultOrg"},
|
|
Name: &wrapperspb.StringValue{Value: "defaultProj"},
|
|
Description: &wrapperspb.StringValue{Value: "notignored"},
|
|
CreatedTime: proj.GetCreateTime().GetTimestamp(),
|
|
Type: scope.Project.String(),
|
|
AuthorizedActions: testAuthorizedPrjActions,
|
|
AuthorizedCollectionActions: projectAuthorizedCollectionActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update a Non Existing Project",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
Id: "p_DoesntExist",
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"description"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Cant change Id",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
Id: proj.GetPublicId(),
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"id"},
|
|
},
|
|
Item: &pb.Scope{
|
|
Id: "p_somethinge",
|
|
Scope: &pb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String(), Name: "defaultOrg", Description: "defaultOrg"},
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "new desc"},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Cant specify Created Time",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"created_time"},
|
|
},
|
|
Item: &pb.Scope{
|
|
CreatedTime: timestamppb.Now(),
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Cant specify Updated Time",
|
|
scopeId: org.GetPublicId(),
|
|
req: &pbs.UpdateScopeRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"updated_time"},
|
|
},
|
|
Item: &pb.Scope{
|
|
UpdatedTime: timestamppb.Now(),
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ver := orgVersion
|
|
if tc.req.Id == proj.PublicId {
|
|
ver = projVersion
|
|
}
|
|
tc.req.Item.Version = ver
|
|
|
|
assert, require := assert.New(t), require.New(t)
|
|
var req *pbs.UpdateScopeRequest
|
|
switch {
|
|
case tc.scopeId == scope.Global.String() && tc.req.Item.GetType() == scope.Global.String():
|
|
tc.req.Item.Version = globalVersion
|
|
ver = globalVersion
|
|
req = proto.Clone(globalToMerge).(*pbs.UpdateScopeRequest)
|
|
if tc.err == nil {
|
|
defer resetGlobal()
|
|
}
|
|
case tc.scopeId == scope.Global.String():
|
|
req = proto.Clone(orgToMerge).(*pbs.UpdateScopeRequest)
|
|
if tc.err == nil {
|
|
defer resetOrg()
|
|
}
|
|
default:
|
|
ver = projVersion
|
|
tc.req.Item.Version = ver
|
|
req = proto.Clone(projToMerge).(*pbs.UpdateScopeRequest)
|
|
if tc.err == nil {
|
|
defer resetProject()
|
|
}
|
|
}
|
|
proto.Merge(req, tc.req)
|
|
|
|
got, gErr := tested.UpdateScope(auth.DisabledAuthTestContext(repoFn, tc.scopeId), req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "UpdateScope(%+v) got error\n%v, wanted\n%v", req, gErr, tc.err)
|
|
}
|
|
|
|
if got != nil {
|
|
assert.NotNilf(tc.res, "Expected UpdateScope response to be nil, but was %v", got)
|
|
gotUpdateTime := got.GetItem().GetUpdatedTime().AsTime()
|
|
// Verify it is a project updated after it was created
|
|
assert.True(gotUpdateTime.After(projCreated), "Updated project should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, projCreated)
|
|
|
|
// Clear all values which are hard to compare against.
|
|
got.Item.UpdatedTime, tc.res.Item.UpdatedTime = nil, nil
|
|
|
|
assert.Equal(ver+1, got.GetItem().GetVersion())
|
|
tc.res.Item.Version = ver + 1
|
|
}
|
|
assert.Empty(cmp.Diff(
|
|
got,
|
|
tc.res,
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
cmpopts.SortSlices(func(a, b protocmp.Message) bool {
|
|
return a.String() < b.String()
|
|
}),
|
|
), "UpdateScope(%q) got response\n%q, wanted\n%q", req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListKeys(t *testing.T) {
|
|
tc := controller.NewTestController(t, nil)
|
|
|
|
aToken := tc.Token()
|
|
uToken := tc.UnprivilegedToken()
|
|
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return tc.IamRepo(), nil
|
|
}
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return tc.ServersRepo(), nil
|
|
}
|
|
authTokenRepoFn := func() (*authtoken.Repository, error) {
|
|
return tc.AuthTokenRepo(), nil
|
|
}
|
|
|
|
privCtx := auth.NewVerifierContext(
|
|
context.Background(),
|
|
iamRepoFn,
|
|
authTokenRepoFn,
|
|
serversRepoFn,
|
|
tc.Kms(),
|
|
&authpb.RequestInfo{
|
|
PublicId: aToken.Id,
|
|
EncryptedToken: strings.Split(aToken.Token, "_")[2],
|
|
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
|
})
|
|
|
|
unprivCtx := auth.NewVerifierContext(
|
|
context.Background(),
|
|
iamRepoFn,
|
|
authTokenRepoFn,
|
|
serversRepoFn,
|
|
tc.Kms(),
|
|
&authpb.RequestInfo{
|
|
PublicId: uToken.Id,
|
|
EncryptedToken: strings.Split(uToken.Token, "_")[2],
|
|
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
|
})
|
|
|
|
org, proj := iam.TestScopes(t, tc.IamRepo())
|
|
|
|
org.Name = "defaultOrg"
|
|
org.Description = "defaultOrg"
|
|
org, _, err := tc.IamRepo().UpdateScope(context.Background(), org, 1, []string{"Name", "Description"})
|
|
require.NoError(t, err)
|
|
|
|
// Add new role for listing keys in org
|
|
listKeysRole := iam.TestRole(t, tc.DbConn(), org.PublicId)
|
|
_, err = tc.IamRepo().AddRoleGrants(context.Background(), listKeysRole.PublicId, 1, []string{"ids=*;type=*;actions=list-keys"})
|
|
require.NoError(t, err)
|
|
_, err = tc.IamRepo().AddPrincipalRoles(context.Background(), listKeysRole.PublicId, 2, []string{aToken.UserId})
|
|
require.NoError(t, err)
|
|
|
|
proj.Name = "defaultProj"
|
|
proj.Description = "defaultProj"
|
|
proj, _, err = tc.IamRepo().UpdateScope(context.Background(), proj, 1, []string{"Name", "Description"})
|
|
require.NoError(t, err)
|
|
|
|
// Add new role for listing keys in project
|
|
listKeysRole = iam.TestRole(t, tc.DbConn(), proj.PublicId)
|
|
_, err = tc.IamRepo().AddRoleGrants(context.Background(), listKeysRole.PublicId, 1, []string{"ids=*;type=*;actions=list-keys"})
|
|
require.NoError(t, err)
|
|
_, err = tc.IamRepo().AddPrincipalRoles(context.Background(), listKeysRole.PublicId, 2, []string{aToken.UserId})
|
|
require.NoError(t, err)
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.ListKeysRequest
|
|
res *pbs.ListKeysResponse
|
|
authCtx context.Context
|
|
err error
|
|
}{
|
|
{
|
|
name: "List keys in the global scope",
|
|
req: &pbs.ListKeysRequest{Id: "global"},
|
|
res: &pbs.ListKeysResponse{
|
|
Items: []*pb.Key{
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: "global",
|
|
Type: "global",
|
|
Name: "global",
|
|
Description: "Global Scope",
|
|
},
|
|
Purpose: "tokens",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: "global",
|
|
Type: "global",
|
|
Name: "global",
|
|
Description: "Global Scope",
|
|
},
|
|
Purpose: "oplog",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: "global",
|
|
Type: "global",
|
|
Name: "global",
|
|
Description: "Global Scope",
|
|
},
|
|
Purpose: "database",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: "global",
|
|
Type: "global",
|
|
Name: "global",
|
|
Description: "Global Scope",
|
|
},
|
|
Purpose: "audit",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: "global",
|
|
Type: "global",
|
|
Name: "global",
|
|
Description: "Global Scope",
|
|
},
|
|
Purpose: "oidc",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: "global",
|
|
Type: "global",
|
|
Name: "global",
|
|
Description: "Global Scope",
|
|
},
|
|
Purpose: "sessions",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: "global",
|
|
Type: "global",
|
|
Name: "global",
|
|
Description: "Global Scope",
|
|
},
|
|
Purpose: "rootKey",
|
|
Type: "kek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "List keys in an existing org",
|
|
req: &pbs.ListKeysRequest{Id: org.GetPublicId()},
|
|
res: &pbs.ListKeysResponse{
|
|
Items: []*pb.Key{
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: org.PublicId,
|
|
ParentScopeId: "global",
|
|
Type: "org",
|
|
Name: org.Name,
|
|
Description: org.Description,
|
|
},
|
|
Purpose: "tokens",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: org.PublicId,
|
|
ParentScopeId: "global",
|
|
Type: "org",
|
|
Name: org.Name,
|
|
Description: org.Description,
|
|
},
|
|
Purpose: "oplog",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: org.PublicId,
|
|
ParentScopeId: "global",
|
|
Type: "org",
|
|
Name: org.Name,
|
|
Description: org.Description,
|
|
},
|
|
Purpose: "database",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: org.PublicId,
|
|
ParentScopeId: "global",
|
|
Type: "org",
|
|
Name: org.Name,
|
|
Description: org.Description,
|
|
},
|
|
Purpose: "audit",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: org.PublicId,
|
|
ParentScopeId: "global",
|
|
Type: "org",
|
|
Name: org.Name,
|
|
Description: org.Description,
|
|
},
|
|
Purpose: "oidc",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: org.PublicId,
|
|
ParentScopeId: "global",
|
|
Type: "org",
|
|
Name: org.Name,
|
|
Description: org.Description,
|
|
},
|
|
Purpose: "sessions",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: org.PublicId,
|
|
ParentScopeId: "global",
|
|
Type: "org",
|
|
Name: org.Name,
|
|
Description: org.Description,
|
|
},
|
|
Purpose: "rootKey",
|
|
Type: "kek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "List keys in a non existing org",
|
|
req: &pbs.ListKeysRequest{Id: "o_DoesntExis"},
|
|
authCtx: privCtx,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "List keys in an existing project",
|
|
req: &pbs.ListKeysRequest{Id: proj.GetPublicId()},
|
|
res: &pbs.ListKeysResponse{
|
|
Items: []*pb.Key{
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: proj.PublicId,
|
|
ParentScopeId: org.PublicId,
|
|
Type: "project",
|
|
Name: proj.Name,
|
|
Description: proj.Description,
|
|
},
|
|
Purpose: "tokens",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: proj.PublicId,
|
|
ParentScopeId: org.PublicId,
|
|
Type: "project",
|
|
Name: proj.Name,
|
|
Description: proj.Description,
|
|
},
|
|
Purpose: "oplog",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: proj.PublicId,
|
|
ParentScopeId: org.PublicId,
|
|
Type: "project",
|
|
Name: proj.Name,
|
|
Description: proj.Description,
|
|
},
|
|
Purpose: "database",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: proj.PublicId,
|
|
ParentScopeId: org.PublicId,
|
|
Type: "project",
|
|
Name: proj.Name,
|
|
Description: proj.Description,
|
|
},
|
|
Purpose: "audit",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: proj.PublicId,
|
|
ParentScopeId: org.PublicId,
|
|
Type: "project",
|
|
Name: proj.Name,
|
|
Description: proj.Description,
|
|
},
|
|
Purpose: "oidc",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: proj.PublicId,
|
|
ParentScopeId: org.PublicId,
|
|
Type: "project",
|
|
Name: proj.Name,
|
|
Description: proj.Description,
|
|
},
|
|
Purpose: "sessions",
|
|
Type: "dek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: proj.PublicId,
|
|
ParentScopeId: org.PublicId,
|
|
Type: "project",
|
|
Name: proj.Name,
|
|
Description: proj.Description,
|
|
},
|
|
Purpose: "rootKey",
|
|
Type: "kek",
|
|
Versions: []*pb.KeyVersion{
|
|
{
|
|
Version: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "List keys in a non existing project",
|
|
req: &pbs.ListKeysRequest{Id: "p_DoesntExist"},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "Wrong id prefix",
|
|
req: &pbs.ListKeysRequest{Id: "j_1234567890"},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "space in id",
|
|
req: &pbs.ListKeysRequest{Id: "p_1 23456789"},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "unauthorized",
|
|
req: &pbs.ListKeysRequest{Id: "global"},
|
|
err: handlers.ApiErrorWithCode(codes.PermissionDenied),
|
|
authCtx: unprivCtx,
|
|
},
|
|
}
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
s, err := scopes.NewServiceFn(context.Background(), iamRepoFn, tc.Kms(), 1000)
|
|
require.NoError(err, "Couldn't create new project service.")
|
|
|
|
got, gErr := s.ListKeys(tt.authCtx, tt.req)
|
|
if tt.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tt.err), "ListKeys(%+v) got error\n%v, wanted\n%v", tt.req, gErr, tt.err)
|
|
} else {
|
|
require.NoError(gErr)
|
|
}
|
|
assert.Empty(
|
|
cmp.Diff(
|
|
tt.res,
|
|
got,
|
|
protocmp.Transform(),
|
|
// Sort by purpose for comparison since it is the only unique and predictable field
|
|
protocmp.SortRepeated(func(i, j *pb.Key) bool { return i.GetPurpose() < j.GetPurpose() }),
|
|
protocmp.IgnoreFields(&pb.Key{}, "id", "created_time"),
|
|
protocmp.IgnoreFields(&pb.KeyVersion{}, "id", "created_time"),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
),
|
|
"ListKeys(%q) got response\n%q, wanted\n%q", tt.req, got, tt.res,
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRotateKeys(t *testing.T) {
|
|
tc := controller.NewTestController(t, nil)
|
|
|
|
aToken := tc.Token()
|
|
uToken := tc.UnprivilegedToken()
|
|
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return tc.IamRepo(), nil
|
|
}
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return tc.ServersRepo(), nil
|
|
}
|
|
authTokenRepoFn := func() (*authtoken.Repository, error) {
|
|
return tc.AuthTokenRepo(), nil
|
|
}
|
|
|
|
privCtx := auth.NewVerifierContext(
|
|
context.Background(),
|
|
iamRepoFn,
|
|
authTokenRepoFn,
|
|
serversRepoFn,
|
|
tc.Kms(),
|
|
&authpb.RequestInfo{
|
|
PublicId: aToken.Id,
|
|
EncryptedToken: strings.Split(aToken.Token, "_")[2],
|
|
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
|
})
|
|
|
|
unprivCtx := auth.NewVerifierContext(
|
|
context.Background(),
|
|
iamRepoFn,
|
|
authTokenRepoFn,
|
|
serversRepoFn,
|
|
tc.Kms(),
|
|
&authpb.RequestInfo{
|
|
PublicId: uToken.Id,
|
|
EncryptedToken: strings.Split(uToken.Token, "_")[2],
|
|
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
|
})
|
|
|
|
org, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(aToken.UserId))
|
|
|
|
// Add new role for listing+rotating keys in org
|
|
rotateKeysRole := iam.TestRole(t, tc.DbConn(), org.PublicId)
|
|
iam.TestRoleGrant(t, tc.DbConn(), rotateKeysRole.PublicId, "ids=*;type=*;actions=rotate-keys,list-keys")
|
|
_ = iam.TestUserRole(t, tc.DbConn(), rotateKeysRole.PublicId, aToken.UserId)
|
|
|
|
// Add new role for listing+rotating keys in project
|
|
rotateKeysRole = iam.TestRole(t, tc.DbConn(), proj.PublicId)
|
|
iam.TestRoleGrant(t, tc.DbConn(), rotateKeysRole.PublicId, "ids=*;type=*;actions=rotate-keys,list-keys")
|
|
_ = iam.TestUserRole(t, tc.DbConn(), rotateKeysRole.PublicId, aToken.UserId)
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.RotateKeysRequest
|
|
res *pbs.RotateKeysResponse
|
|
authCtx context.Context
|
|
err error
|
|
}{
|
|
{
|
|
name: "unauthorized",
|
|
req: &pbs.RotateKeysRequest{ScopeId: "global", Rewrap: false},
|
|
err: handlers.ApiErrorWithCode(codes.PermissionDenied),
|
|
authCtx: unprivCtx,
|
|
},
|
|
{
|
|
name: "Rotate keys in a non existing org",
|
|
req: &pbs.RotateKeysRequest{ScopeId: "o_DoesntExis", Rewrap: false},
|
|
authCtx: privCtx,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "successfully Rotate keys in org",
|
|
req: &pbs.RotateKeysRequest{ScopeId: org.GetPublicId(), Rewrap: false},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "successfully Rotate and rewrap keys in org",
|
|
req: &pbs.RotateKeysRequest{ScopeId: org.GetPublicId(), Rewrap: true},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "successfully Rotate keys in project",
|
|
req: &pbs.RotateKeysRequest{ScopeId: proj.GetPublicId(), Rewrap: false},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "successfully Rotate and rewrap keys in project",
|
|
req: &pbs.RotateKeysRequest{ScopeId: proj.GetPublicId(), Rewrap: true},
|
|
authCtx: privCtx,
|
|
},
|
|
}
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
s, err := scopes.NewServiceFn(context.Background(), iamRepoFn, tc.Kms(), 1000)
|
|
require.NoError(err, "Couldn't create new project service.")
|
|
|
|
prevKeyVersions := map[uint32]int{}
|
|
var prevLatest uint32 = 0
|
|
|
|
// checking key versions before rotation
|
|
if tt.err == nil {
|
|
keys, gErr := s.ListKeys(privCtx, &pbs.ListKeysRequest{Id: tt.req.ScopeId})
|
|
require.NoError(gErr)
|
|
|
|
for _, key := range keys.Items {
|
|
for _, keyVersion := range key.Versions {
|
|
if keyVersion.Version > prevLatest {
|
|
prevLatest = keyVersion.Version
|
|
}
|
|
_, ok := prevKeyVersions[keyVersion.Version]
|
|
if !ok {
|
|
prevKeyVersions[keyVersion.Version] = 0
|
|
}
|
|
prevKeyVersions[keyVersion.Version]++
|
|
}
|
|
}
|
|
}
|
|
|
|
// RotateKeys returns nocontent response
|
|
_, kErr := s.RotateKeys(tt.authCtx, tt.req)
|
|
|
|
if tt.err != nil {
|
|
require.Error(kErr)
|
|
assert.True(errors.Is(kErr, tt.err), "RotateKeys(%+v) got error\n%v, wanted\n%v", tt.req, kErr, tt.err)
|
|
} else {
|
|
require.NoError(kErr)
|
|
keys, gErr := s.ListKeys(privCtx, &pbs.ListKeysRequest{Id: tt.req.ScopeId})
|
|
require.NoError(gErr)
|
|
|
|
keyVersions := map[uint32]int{}
|
|
var latest uint32 = 0
|
|
|
|
for _, key := range keys.Items {
|
|
for _, keyVersion := range key.Versions {
|
|
if keyVersion.Version > latest {
|
|
latest = keyVersion.Version
|
|
}
|
|
_, ok := keyVersions[keyVersion.Version]
|
|
if !ok {
|
|
keyVersions[keyVersion.Version] = 0
|
|
}
|
|
keyVersions[keyVersion.Version]++
|
|
}
|
|
}
|
|
|
|
// there should only be one new key version
|
|
assert.Equal(len(prevKeyVersions)+1, len(keyVersions))
|
|
// since we just rotated them, there should be the same number of version 1 and version 2
|
|
assert.Equal(prevKeyVersions[prevLatest], keyVersions[latest])
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListKeyVersionDestructionJobs(t *testing.T) {
|
|
tc := controller.NewTestController(t, nil)
|
|
|
|
aToken := tc.Token()
|
|
uToken := tc.UnprivilegedToken()
|
|
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return tc.IamRepo(), nil
|
|
}
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return tc.ServersRepo(), nil
|
|
}
|
|
authTokenRepoFn := func() (*authtoken.Repository, error) {
|
|
return tc.AuthTokenRepo(), nil
|
|
}
|
|
|
|
privCtx := auth.NewVerifierContext(
|
|
tc.Context(),
|
|
iamRepoFn,
|
|
authTokenRepoFn,
|
|
serversRepoFn,
|
|
tc.Kms(),
|
|
&authpb.RequestInfo{
|
|
PublicId: aToken.Id,
|
|
EncryptedToken: strings.Split(aToken.Token, "_")[2],
|
|
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
|
},
|
|
)
|
|
|
|
unprivCtx := auth.NewVerifierContext(
|
|
tc.Context(),
|
|
iamRepoFn,
|
|
authTokenRepoFn,
|
|
serversRepoFn,
|
|
tc.Kms(),
|
|
&authpb.RequestInfo{
|
|
PublicId: uToken.Id,
|
|
EncryptedToken: strings.Split(uToken.Token, "_")[2],
|
|
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
|
},
|
|
)
|
|
|
|
sqldb, err := tc.DbConn().SqlDB(tc.Context())
|
|
require.NoError(t, err)
|
|
|
|
org, proj := iam.TestScopes(t, tc.IamRepo())
|
|
|
|
org.Name = "defaultOrg"
|
|
org.Description = "defaultOrg"
|
|
org, _, err = tc.IamRepo().UpdateScope(tc.Context(), org, 1, []string{"Name", "Description"})
|
|
require.NoError(t, err)
|
|
|
|
// Add new role for listing key version destructions in org
|
|
listKeysRole := iam.TestRole(t, tc.DbConn(), org.PublicId)
|
|
_ = iam.TestRoleGrant(t, tc.DbConn(), listKeysRole.PublicId, "ids=*;type=*;actions=list-key-version-destruction-jobs")
|
|
_ = iam.TestUserRole(t, tc.DbConn(), listKeysRole.PublicId, aToken.UserId)
|
|
// Create a oidc auth method to create an encrypted value in this scope
|
|
databaseWrapper, err := tc.Kms().GetWrapper(tc.Context(), org.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
_ = oidc.TestAuthMethod(t, tc.DbConn(), databaseWrapper, org.PublicId, oidc.InactiveState, "noAccounts", "fido")
|
|
|
|
proj.Name = "defaultProj"
|
|
proj.Description = "defaultProj"
|
|
proj, _, err = tc.IamRepo().UpdateScope(tc.Context(), proj, 1, []string{"Name", "Description"})
|
|
require.NoError(t, err)
|
|
|
|
// Add new role for listing key version destructions in project
|
|
listKeysRole = iam.TestRole(t, tc.DbConn(), proj.PublicId)
|
|
_ = iam.TestRoleGrant(t, tc.DbConn(), listKeysRole.PublicId, "ids=*;type=*;actions=list-key-version-destruction-jobs")
|
|
_ = iam.TestUserRole(t, tc.DbConn(), listKeysRole.PublicId, aToken.UserId)
|
|
// Create a oidc auth method to create an encrypted value in this scope
|
|
databaseWrapper, err = tc.Kms().GetWrapper(tc.Context(), proj.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
_ = oidc.TestAuthMethod(t, tc.DbConn(), databaseWrapper, proj.PublicId, oidc.InactiveState, "noAccounts", "fido")
|
|
|
|
for _, scope := range []string{"global", org.PublicId, proj.PublicId} {
|
|
err = tc.Kms().RotateKeys(tc.Context(), scope)
|
|
require.NoError(t, err)
|
|
keys, err := tc.Kms().ListKeys(tc.Context(), scope)
|
|
require.NoError(t, err)
|
|
|
|
var kvToDestroy wrappingKms.KeyVersion
|
|
for _, key := range keys {
|
|
if key.Purpose == wrappingKms.KeyPurpose(kms.KeyPurposeDatabase.String()) {
|
|
kvToDestroy = key.Versions[0]
|
|
}
|
|
}
|
|
destroyed, err := tc.Kms().DestroyKeyVersion(tc.Context(), scope, kvToDestroy.Id)
|
|
require.NoError(t, err)
|
|
assert.False(t, destroyed)
|
|
t.Cleanup(func() {
|
|
_, err = sqldb.ExecContext(tc.Context(), "delete from kms_data_key_version_destruction_job where key_id=$1", kvToDestroy.Id)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.ListKeyVersionDestructionJobsRequest
|
|
res *pbs.ListKeyVersionDestructionJobsResponse
|
|
authCtx context.Context
|
|
err error
|
|
}{
|
|
{
|
|
name: "List key version destruction jobs in the global scope",
|
|
req: &pbs.ListKeyVersionDestructionJobsRequest{ScopeId: "global"},
|
|
res: &pbs.ListKeyVersionDestructionJobsResponse{
|
|
Items: []*pb.KeyVersionDestructionJob{
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: "global",
|
|
Type: "global",
|
|
Name: "global",
|
|
Description: "Global Scope",
|
|
},
|
|
Status: "pending",
|
|
CompletedCount: 0,
|
|
TotalCount: 7,
|
|
},
|
|
},
|
|
},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "List key version destruction jobs in an existing org",
|
|
req: &pbs.ListKeyVersionDestructionJobsRequest{ScopeId: org.GetPublicId()},
|
|
res: &pbs.ListKeyVersionDestructionJobsResponse{
|
|
Items: []*pb.KeyVersionDestructionJob{
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: org.GetPublicId(),
|
|
ParentScopeId: "global",
|
|
Type: "org",
|
|
Name: org.GetName(),
|
|
Description: org.GetDescription(),
|
|
},
|
|
Status: "pending",
|
|
CompletedCount: 0,
|
|
TotalCount: 1,
|
|
},
|
|
},
|
|
},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "List key version destruction jobs in a non existing org",
|
|
req: &pbs.ListKeyVersionDestructionJobsRequest{ScopeId: "o_DoesntExis"},
|
|
authCtx: privCtx,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "List key version destruction jobs in an existing project",
|
|
req: &pbs.ListKeyVersionDestructionJobsRequest{ScopeId: proj.GetPublicId()},
|
|
res: &pbs.ListKeyVersionDestructionJobsResponse{
|
|
Items: []*pb.KeyVersionDestructionJob{
|
|
{
|
|
Scope: &pb.ScopeInfo{
|
|
Id: proj.GetPublicId(),
|
|
ParentScopeId: org.GetPublicId(),
|
|
Type: "project",
|
|
Name: proj.GetName(),
|
|
Description: proj.GetDescription(),
|
|
},
|
|
Status: "pending",
|
|
CompletedCount: 0,
|
|
TotalCount: 1,
|
|
},
|
|
},
|
|
},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "List key version destruction jobs in a non existing project",
|
|
req: &pbs.ListKeyVersionDestructionJobsRequest{ScopeId: "p_DoesntExist"},
|
|
authCtx: privCtx,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Wrong id prefix",
|
|
req: &pbs.ListKeyVersionDestructionJobsRequest{ScopeId: "j_1234567890"},
|
|
authCtx: privCtx,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "space in id",
|
|
req: &pbs.ListKeyVersionDestructionJobsRequest{ScopeId: "p_1 23456789"},
|
|
authCtx: privCtx,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "unauthorized",
|
|
req: &pbs.ListKeyVersionDestructionJobsRequest{ScopeId: "global"},
|
|
authCtx: unprivCtx,
|
|
err: handlers.ApiErrorWithCode(codes.PermissionDenied),
|
|
},
|
|
}
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
s, err := scopes.NewServiceFn(context.Background(), iamRepoFn, tc.Kms(), 1000)
|
|
require.NoError(err, "Couldn't create new project service.")
|
|
|
|
got, gErr := s.ListKeyVersionDestructionJobs(tt.authCtx, tt.req)
|
|
if tt.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tt.err), "ListKeyVersionDestructionJobs(%+v) got error\n%v, wanted\n%v", tt.req, gErr, tt.err)
|
|
} else {
|
|
require.NoError(gErr)
|
|
}
|
|
assert.Empty(
|
|
cmp.Diff(
|
|
tt.res,
|
|
got,
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(i, j *pb.KeyVersionDestructionJob) bool { return i.GetTotalCount() < j.GetTotalCount() }),
|
|
protocmp.IgnoreFields(&pb.KeyVersionDestructionJob{}, "key_version_id", "created_time"),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
cmpopts.SortSlices(func(a, b protocmp.Message) bool {
|
|
return a.String() < b.String()
|
|
}),
|
|
),
|
|
"ListKeyVersionDestructionJobs(%q) got response\n%q, wanted\n%q", tt.req, got, tt.res,
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDestroyKeyVersion(t *testing.T) {
|
|
tc := controller.NewTestController(t, nil)
|
|
|
|
aToken := tc.Token()
|
|
uToken := tc.UnprivilegedToken()
|
|
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return tc.IamRepo(), nil
|
|
}
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return tc.ServersRepo(), nil
|
|
}
|
|
authTokenRepoFn := func() (*authtoken.Repository, error) {
|
|
return tc.AuthTokenRepo(), nil
|
|
}
|
|
|
|
privCtx := auth.NewVerifierContext(
|
|
tc.Context(),
|
|
iamRepoFn,
|
|
authTokenRepoFn,
|
|
serversRepoFn,
|
|
tc.Kms(),
|
|
&authpb.RequestInfo{
|
|
PublicId: aToken.Id,
|
|
EncryptedToken: strings.Split(aToken.Token, "_")[2],
|
|
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
|
},
|
|
)
|
|
|
|
unprivCtx := auth.NewVerifierContext(
|
|
tc.Context(),
|
|
iamRepoFn,
|
|
authTokenRepoFn,
|
|
serversRepoFn,
|
|
tc.Kms(),
|
|
&authpb.RequestInfo{
|
|
PublicId: uToken.Id,
|
|
EncryptedToken: strings.Split(uToken.Token, "_")[2],
|
|
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
|
},
|
|
)
|
|
|
|
org, proj := iam.TestScopes(t, tc.IamRepo())
|
|
|
|
org.Name = "defaultOrg"
|
|
org.Description = "defaultOrg"
|
|
org, _, err := tc.IamRepo().UpdateScope(tc.Context(), org, 1, []string{"Name", "Description"})
|
|
require.NoError(t, err)
|
|
|
|
// Add new role for destroying key versions in org
|
|
listKeysRole := iam.TestRole(t, tc.DbConn(), org.PublicId)
|
|
_ = iam.TestRoleGrant(t, tc.DbConn(), listKeysRole.PublicId, "ids=*;type=*;actions=destroy-key-version")
|
|
_ = iam.TestUserRole(t, tc.DbConn(), listKeysRole.PublicId, aToken.UserId)
|
|
// Create a oidc auth method to create an encrypted value in this scope
|
|
databaseWrapper, err := tc.Kms().GetWrapper(tc.Context(), org.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
_ = oidc.TestAuthMethod(t, tc.DbConn(), databaseWrapper, org.PublicId, oidc.InactiveState, "noAccounts", "fido")
|
|
|
|
proj.Name = "defaultProj"
|
|
proj.Description = "defaultProj"
|
|
proj, _, err = tc.IamRepo().UpdateScope(tc.Context(), proj, 1, []string{"Name", "Description"})
|
|
require.NoError(t, err)
|
|
|
|
// Add new role for destroying key versions in project
|
|
listKeysRole = iam.TestRole(t, tc.DbConn(), proj.PublicId)
|
|
_ = iam.TestRoleGrant(t, tc.DbConn(), listKeysRole.PublicId, "ids=*;type=*;actions=destroy-key-version")
|
|
_ = iam.TestUserRole(t, tc.DbConn(), listKeysRole.PublicId, aToken.UserId)
|
|
// Create a oidc auth method to create an encrypted value in this scope
|
|
databaseWrapper, err = tc.Kms().GetWrapper(tc.Context(), proj.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
_ = oidc.TestAuthMethod(t, tc.DbConn(), databaseWrapper, proj.PublicId, oidc.InactiveState, "noAccounts", "fido")
|
|
|
|
scopeToImmediatelyDestroyedKeyVersionId := map[string]string{}
|
|
scopeToPendingDestructionKeyVersionId := map[string]string{}
|
|
for _, scope := range []string{"global", org.PublicId, proj.PublicId} {
|
|
err = tc.Kms().RotateKeys(tc.Context(), scope)
|
|
require.NoError(t, err)
|
|
|
|
keys, err := tc.Kms().ListKeys(tc.Context(), scope)
|
|
require.NoError(t, err)
|
|
|
|
for _, key := range keys {
|
|
switch key.Purpose {
|
|
case wrappingKms.KeyPurpose(kms.KeyPurposeDatabase.String()):
|
|
scopeToPendingDestructionKeyVersionId[scope] = key.Versions[0].Id
|
|
case wrappingKms.KeyPurpose(kms.KeyPurposeRootKey.String()):
|
|
scopeToImmediatelyDestroyedKeyVersionId[scope] = key.Versions[0].Id
|
|
}
|
|
}
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.DestroyKeyVersionRequest
|
|
res *pbs.DestroyKeyVersionResponse
|
|
authCtx context.Context
|
|
err error
|
|
}{
|
|
{
|
|
name: "Errors when specifying a non existing key version Id",
|
|
req: &pbs.DestroyKeyVersionRequest{
|
|
ScopeId: "global",
|
|
KeyVersionId: "krkv_DoesntExist",
|
|
},
|
|
authCtx: privCtx,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Creates a key version destruction job in the global scope",
|
|
req: &pbs.DestroyKeyVersionRequest{
|
|
ScopeId: "global",
|
|
KeyVersionId: scopeToPendingDestructionKeyVersionId["global"],
|
|
},
|
|
res: &pbs.DestroyKeyVersionResponse{
|
|
State: "pending",
|
|
},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "Immediately destroys a root key version in the global scope",
|
|
req: &pbs.DestroyKeyVersionRequest{
|
|
ScopeId: "global",
|
|
KeyVersionId: scopeToImmediatelyDestroyedKeyVersionId["global"],
|
|
},
|
|
res: &pbs.DestroyKeyVersionResponse{
|
|
State: "completed",
|
|
},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "Creates a key version destruction job in an existing org",
|
|
req: &pbs.DestroyKeyVersionRequest{
|
|
ScopeId: org.GetPublicId(),
|
|
KeyVersionId: scopeToPendingDestructionKeyVersionId[org.GetPublicId()],
|
|
},
|
|
res: &pbs.DestroyKeyVersionResponse{
|
|
State: "pending",
|
|
},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "Immediately destroys a root key version in an existing org",
|
|
req: &pbs.DestroyKeyVersionRequest{
|
|
ScopeId: org.GetPublicId(),
|
|
KeyVersionId: scopeToImmediatelyDestroyedKeyVersionId[org.GetPublicId()],
|
|
},
|
|
res: &pbs.DestroyKeyVersionResponse{
|
|
State: "completed",
|
|
},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "Creates a key version destruction job in an existing project",
|
|
req: &pbs.DestroyKeyVersionRequest{
|
|
ScopeId: proj.GetPublicId(),
|
|
KeyVersionId: scopeToPendingDestructionKeyVersionId[proj.GetPublicId()],
|
|
},
|
|
res: &pbs.DestroyKeyVersionResponse{
|
|
State: "pending",
|
|
},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "Immediately destroys a root key version in an existing project",
|
|
req: &pbs.DestroyKeyVersionRequest{
|
|
ScopeId: proj.GetPublicId(),
|
|
KeyVersionId: scopeToImmediatelyDestroyedKeyVersionId[proj.GetPublicId()],
|
|
},
|
|
res: &pbs.DestroyKeyVersionResponse{
|
|
State: "completed",
|
|
},
|
|
authCtx: privCtx,
|
|
},
|
|
{
|
|
name: "Errors when specifying a key version that doesn't exist in the scope",
|
|
req: &pbs.DestroyKeyVersionRequest{
|
|
ScopeId: org.GetPublicId(),
|
|
KeyVersionId: scopeToPendingDestructionKeyVersionId[proj.GetPublicId()],
|
|
},
|
|
authCtx: privCtx,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Errors when specifying a non existing org",
|
|
req: &pbs.DestroyKeyVersionRequest{
|
|
ScopeId: "o_DoesntExist",
|
|
KeyVersionId: scopeToImmediatelyDestroyedKeyVersionId[org.GetPublicId()],
|
|
},
|
|
authCtx: privCtx,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Errors when specifying a non existing project",
|
|
req: &pbs.DestroyKeyVersionRequest{
|
|
ScopeId: "p_DoesntExist",
|
|
KeyVersionId: scopeToImmediatelyDestroyedKeyVersionId[proj.GetPublicId()],
|
|
},
|
|
authCtx: privCtx,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Wrong id prefix",
|
|
req: &pbs.DestroyKeyVersionRequest{
|
|
ScopeId: "j_1234567890",
|
|
KeyVersionId: scopeToImmediatelyDestroyedKeyVersionId["global"],
|
|
},
|
|
authCtx: privCtx,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "space in id",
|
|
req: &pbs.DestroyKeyVersionRequest{
|
|
ScopeId: "p_1 23456789",
|
|
KeyVersionId: scopeToImmediatelyDestroyedKeyVersionId["global"],
|
|
},
|
|
authCtx: privCtx,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "unauthorized",
|
|
req: &pbs.DestroyKeyVersionRequest{
|
|
ScopeId: "global",
|
|
KeyVersionId: scopeToImmediatelyDestroyedKeyVersionId["global"],
|
|
},
|
|
authCtx: unprivCtx,
|
|
err: handlers.ApiErrorWithCode(codes.PermissionDenied),
|
|
},
|
|
}
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
s, err := scopes.NewServiceFn(context.Background(), iamRepoFn, tc.Kms(), 1000)
|
|
require.NoError(err, "Couldn't create new project service.")
|
|
|
|
got, gErr := s.DestroyKeyVersion(tt.authCtx, tt.req)
|
|
if tt.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tt.err), "DestroyKeyVersion(%+v) got error\n%v, wanted\n%v", tt.req, gErr, tt.err)
|
|
} else {
|
|
require.NoError(gErr)
|
|
}
|
|
|
|
assert.Empty(cmp.Diff(
|
|
tt.res,
|
|
got,
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
cmpopts.SortSlices(func(a, b protocmp.Message) bool {
|
|
return a.String() < b.String()
|
|
}),
|
|
), "DestroyKeyVersion(%q) got response\n%q, wanted\n%q", tt.req, got, tt.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAttachStoragePolicy(t *testing.T) {
|
|
t.Run("unimplemented", func(t *testing.T) {
|
|
service := &scopes.Service{}
|
|
_, err := service.AttachStoragePolicy(context.Background(), &pbs.AttachStoragePolicyRequest{})
|
|
require.Error(t, err)
|
|
gotStatus, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, gotStatus.Code(), codes.Unimplemented)
|
|
assert.Equal(t, gotStatus.Message(), "Policies are an Enterprise-only feature")
|
|
})
|
|
}
|
|
|
|
func TestDetachStoragePolicy(t *testing.T) {
|
|
t.Run("unimplemented", func(t *testing.T) {
|
|
service := &scopes.Service{}
|
|
_, err := service.DetachStoragePolicy(context.Background(), &pbs.DetachStoragePolicyRequest{})
|
|
require.Error(t, err)
|
|
gotStatus, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, gotStatus.Code(), codes.Unimplemented)
|
|
assert.Equal(t, gotStatus.Message(), "Policies are an Enterprise-only feature")
|
|
})
|
|
}
|