* Add ListResolvableAliases to the user service There are a few non common ways this method uses our ACL and grant system. A user is able to list resolvable aliases for another user if they are granted permission to do so. That means we had to load the grants for the user being listed for and not the requester when determining if an alias is able to resolve to a destination for which the requested user has permission. Similarly, the grant hash used for the list pagination is required to be that of the user being listed and not the requester.
2080 lines
68 KiB
Go
2080 lines
68 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package users_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/hashicorp/boundary/globals"
|
|
talias "github.com/hashicorp/boundary/internal/alias/target"
|
|
"github.com/hashicorp/boundary/internal/auth/ldap"
|
|
"github.com/hashicorp/boundary/internal/auth/oidc"
|
|
"github.com/hashicorp/boundary/internal/auth/password"
|
|
"github.com/hashicorp/boundary/internal/authtoken"
|
|
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
|
|
"github.com/hashicorp/boundary/internal/daemon/controller/common"
|
|
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
|
|
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/users"
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
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/requests"
|
|
"github.com/hashicorp/boundary/internal/server"
|
|
"github.com/hashicorp/boundary/internal/target/tcp"
|
|
"github.com/hashicorp/boundary/internal/types/scope"
|
|
pbalias "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/aliases"
|
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
|
|
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/users"
|
|
"google.golang.org/genproto/protobuf/field_mask"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/testing/protocmp"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var testAuthorizedActions = []string{"no-op", "read", "update", "delete", "add-accounts", "set-accounts", "remove-accounts", "list-resolvable-aliases"}
|
|
|
|
func createDefaultUserAndRepos(t *testing.T, withAccts bool) (*iam.User, []string, common.IamRepoFactory, common.TargetAliasRepoFactory) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
wrap := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrap)
|
|
rw := db.New(conn)
|
|
repo := iam.TestRepo(t, conn, wrap)
|
|
repoFn := func() (*iam.Repository, error) {
|
|
return repo, nil
|
|
}
|
|
aliasRepoFn := func() (*talias.Repository, error) {
|
|
return talias.NewRepository(context.Background(), rw, rw, kmsCache)
|
|
}
|
|
o, _ := iam.TestScopes(t, repo)
|
|
u := iam.TestUser(t, repo, o.GetPublicId(), iam.WithDescription("default"), iam.WithName("default"))
|
|
|
|
switch withAccts {
|
|
case false:
|
|
return u, nil, repoFn, aliasRepoFn
|
|
default:
|
|
require := require.New(t)
|
|
databaseWrap, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(err)
|
|
primaryAm := oidc.TestAuthMethod(t, conn, databaseWrap, o.PublicId, oidc.ActivePublicState, "alice-rp", "fido",
|
|
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://alice-eve-smith.com")[0]),
|
|
oidc.WithApiUrl(oidc.TestConvertToUrls(t, "http://localhost:9200")[0]),
|
|
oidc.WithSigningAlgs(oidc.RS256),
|
|
)
|
|
iam.TestSetPrimaryAuthMethod(t, repo, o, primaryAm.PublicId)
|
|
|
|
oidcAcct := oidc.TestAccount(t, conn, primaryAm, "alice", oidc.WithFullName("Alice Eve Smith"), oidc.WithEmail("alice@smith.com"))
|
|
|
|
secondaryAm := password.TestAuthMethods(t, conn, o.PublicId, 1)
|
|
require.Len(secondaryAm, 1)
|
|
pwAcct := password.TestAccount(t, conn, secondaryAm[0].PublicId, "alice")
|
|
|
|
added, err := repo.AddUserAccounts(ctx, u.PublicId, u.Version, []string{oidcAcct.PublicId, pwAcct.PublicId})
|
|
require.NoError(err)
|
|
require.Len(added, 2)
|
|
|
|
// reload the user with their accounts
|
|
u, accts, err := repo.LookupUser(ctx, u.PublicId)
|
|
require.NoError(err)
|
|
return u, accts, repoFn, aliasRepoFn
|
|
}
|
|
}
|
|
|
|
func TestGet(t *testing.T) {
|
|
u, uAccts, repoFn, aliasRepo := createDefaultUserAndRepos(t, true)
|
|
|
|
toMerge := &pbs.GetUserRequest{
|
|
Id: u.GetPublicId(),
|
|
}
|
|
|
|
wantU := &pb.User{
|
|
Id: u.GetPublicId(),
|
|
ScopeId: u.GetScopeId(),
|
|
Scope: &scopes.ScopeInfo{Id: u.ScopeId, Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Name: &wrapperspb.StringValue{Value: u.GetName()},
|
|
Description: &wrapperspb.StringValue{Value: u.GetDescription()},
|
|
CreatedTime: u.CreateTime.GetTimestamp(),
|
|
UpdatedTime: u.UpdateTime.GetTimestamp(),
|
|
Version: u.Version,
|
|
AuthorizedActions: testAuthorizedActions,
|
|
LoginName: u.LoginName,
|
|
FullName: u.GetFullName(),
|
|
Email: u.GetEmail(),
|
|
PrimaryAccountId: u.GetPrimaryAccountId(),
|
|
AccountIds: uAccts,
|
|
Accounts: []*pb.Account{{Id: uAccts[0], ScopeId: u.ScopeId}, {Id: uAccts[1], ScopeId: u.ScopeId}},
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.GetUserRequest
|
|
res *pbs.GetUserResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "Get an Existing User",
|
|
req: &pbs.GetUserRequest{Id: u.GetPublicId()},
|
|
res: &pbs.GetUserResponse{Item: wantU},
|
|
},
|
|
{
|
|
name: "Get a non existent User",
|
|
req: &pbs.GetUserRequest{Id: globals.UserPrefix + "_DoesntExis"},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Wrong id prefix",
|
|
req: &pbs.GetUserRequest{Id: "j_1234567890"},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "space in id",
|
|
req: &pbs.GetUserRequest{Id: globals.UserPrefix + "_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.GetUserRequest)
|
|
proto.Merge(req, tc.req)
|
|
|
|
s, err := users.NewService(context.Background(), repoFn, aliasRepo, 1000)
|
|
require.NoError(err, "Couldn't create new user service.")
|
|
|
|
got, gErr := s.GetUser(auth.DisabledAuthTestContext(repoFn, u.GetScopeId()), req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "GetUser(%+v) got error %v, wanted %v", req, gErr, tc.err)
|
|
}
|
|
assert.Empty(cmp.Diff(
|
|
got,
|
|
tc.res,
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
), "GetUser(%q) got response %q, wanted %q", req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestList(t *testing.T) {
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrap := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrap)
|
|
iamRepo := iam.TestRepo(t, conn, wrap)
|
|
repoFn := func() (*iam.Repository, error) {
|
|
return iamRepo, nil
|
|
}
|
|
aliasRepoFn := func() (*talias.Repository, error) {
|
|
return talias.NewRepository(context.Background(), rw, rw, kmsCache)
|
|
}
|
|
|
|
oNoUsers, _ := iam.TestScopes(t, iamRepo)
|
|
oWithUsers, _ := iam.TestScopes(t, iamRepo)
|
|
|
|
databaseWrap, err := kmsCache.GetWrapper(context.Background(), oWithUsers.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
primaryAm := oidc.TestAuthMethod(t, conn, databaseWrap, oWithUsers.PublicId, oidc.ActivePublicState, "alice-rp", "fido",
|
|
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://alice-eve-smith.com")[0]),
|
|
oidc.WithApiUrl(oidc.TestConvertToUrls(t, "http://localhost:9200")[0]),
|
|
oidc.WithSigningAlgs(oidc.RS256),
|
|
)
|
|
iam.TestSetPrimaryAuthMethod(t, iamRepo, oWithUsers, primaryAm.PublicId)
|
|
|
|
secondaryAm := password.TestAuthMethods(t, conn, oWithUsers.PublicId, 1)
|
|
require.Len(t, secondaryAm, 1)
|
|
|
|
s, err := users.NewService(context.Background(), repoFn, aliasRepoFn, 1000)
|
|
require.NoError(t, err)
|
|
|
|
var wantUsers []*pb.User
|
|
|
|
// Populate expected values for recursive test
|
|
var totalUsers []*pb.User
|
|
{
|
|
disabledAuthCtx := auth.DisabledAuthTestContext(repoFn, "global")
|
|
anon, err := s.GetUser(disabledAuthCtx, &pbs.GetUserRequest{Id: globals.AnonymousUserId})
|
|
require.NoError(t, err)
|
|
totalUsers = append(totalUsers, anon.GetItem())
|
|
authUser, err := s.GetUser(disabledAuthCtx, &pbs.GetUserRequest{Id: globals.AnyAuthenticatedUserId})
|
|
require.NoError(t, err)
|
|
totalUsers = append(totalUsers, authUser.GetItem())
|
|
recovery, err := s.GetUser(disabledAuthCtx, &pbs.GetUserRequest{Id: globals.RecoveryUserId})
|
|
require.NoError(t, err)
|
|
totalUsers = append(totalUsers, recovery.GetItem())
|
|
}
|
|
|
|
// Add new users
|
|
for i := 0; i < 10; i++ {
|
|
newU, err := iam.NewUser(ctx, oWithUsers.GetPublicId())
|
|
require.NoError(t, err)
|
|
u, err := iamRepo.CreateUser(context.Background(), newU)
|
|
require.NoError(t, err)
|
|
oidcAcct := oidc.TestAccount(t, conn, primaryAm, fmt.Sprintf("alice+%d", i), oidc.WithFullName("Alice Eve Smith"), oidc.WithEmail("alice@smith.com"))
|
|
pwAcct := password.TestAccount(t, conn, secondaryAm[0].PublicId, fmt.Sprintf("alice+%d", i))
|
|
|
|
added, err := iamRepo.AddUserAccounts(ctx, u.PublicId, u.Version, []string{oidcAcct.PublicId, pwAcct.PublicId})
|
|
require.NoError(t, err)
|
|
require.Len(t, added, 2)
|
|
|
|
u, _, err = iamRepo.LookupUser(ctx, u.PublicId)
|
|
require.NoError(t, err)
|
|
wantUsers = append(wantUsers, &pb.User{
|
|
Id: u.GetPublicId(),
|
|
ScopeId: u.GetScopeId(),
|
|
Scope: &scopes.ScopeInfo{Id: u.GetScopeId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
CreatedTime: u.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: u.GetUpdateTime().GetTimestamp(),
|
|
Version: 2,
|
|
AuthorizedActions: testAuthorizedActions,
|
|
LoginName: oidcAcct.GetSubject(),
|
|
FullName: oidcAcct.GetFullName(),
|
|
Email: oidcAcct.GetEmail(),
|
|
PrimaryAccountId: oidcAcct.GetPublicId(),
|
|
})
|
|
}
|
|
// Populate these users into the total
|
|
totalUsers = append(totalUsers, wantUsers...)
|
|
slices.Reverse(totalUsers)
|
|
slices.Reverse(wantUsers)
|
|
|
|
// Run analyze to update postgres estimates
|
|
sqlDb, err := conn.SqlDB(ctx)
|
|
require.NoError(t, err)
|
|
_, err = sqlDb.ExecContext(ctx, "analyze")
|
|
require.NoError(t, err)
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.ListUsersRequest
|
|
res *pbs.ListUsersResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "List Many Users",
|
|
req: &pbs.ListUsersRequest{ScopeId: oWithUsers.GetPublicId()},
|
|
res: &pbs.ListUsersResponse{
|
|
Items: wantUsers,
|
|
EstItemCount: uint32(len(wantUsers)),
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "List No Users",
|
|
req: &pbs.ListUsersRequest{ScopeId: oNoUsers.GetPublicId()},
|
|
res: &pbs.ListUsersResponse{
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "List Recursively in Org",
|
|
req: &pbs.ListUsersRequest{ScopeId: oWithUsers.GetPublicId(), Recursive: true},
|
|
res: &pbs.ListUsersResponse{
|
|
Items: wantUsers,
|
|
EstItemCount: uint32(len(wantUsers)),
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "List Recursively in Global",
|
|
req: &pbs.ListUsersRequest{ScopeId: "global", Recursive: true},
|
|
res: &pbs.ListUsersResponse{
|
|
Items: totalUsers,
|
|
EstItemCount: uint32(len(totalUsers)),
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "Filter Many Users",
|
|
req: &pbs.ListUsersRequest{ScopeId: "global", Recursive: true, Filter: fmt.Sprintf(`"/item/scope/id"==%q`, oWithUsers.GetPublicId())},
|
|
res: &pbs.ListUsersResponse{
|
|
Items: wantUsers,
|
|
EstItemCount: uint32(len(wantUsers)),
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "Filter To No Users",
|
|
req: &pbs.ListUsersRequest{ScopeId: oWithUsers.GetPublicId(), Filter: `"/item/id"=="doesntmatch"`},
|
|
res: &pbs.ListUsersResponse{
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "Filter Bad Format",
|
|
req: &pbs.ListUsersRequest{ScopeId: oWithUsers.GetPublicId(), Filter: `"//id/"=="bad"`},
|
|
err: handlers.InvalidArgumentErrorf("bad format", nil),
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
require.NoError(err, "Couldn't create new user service.")
|
|
|
|
// Test with non-anon user
|
|
got, gErr := s.ListUsers(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "ListUsers(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
return
|
|
}
|
|
assert.Empty(cmp.Diff(
|
|
got,
|
|
tc.res,
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
|
), "ListUsers(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
|
// Test with anon user
|
|
|
|
got, gErr = s.ListUsers(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId(), auth.WithUserId(globals.AnonymousUserId)), tc.req)
|
|
require.NoError(gErr)
|
|
assert.Len(got.Items, len(tc.res.Items))
|
|
for _, item := range got.GetItems() {
|
|
require.Empty(item.Version)
|
|
require.Nil(item.CreatedTime)
|
|
require.Nil(item.UpdatedTime)
|
|
require.Nil(item.Accounts)
|
|
require.Nil(item.AccountIds)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func userToProto(u *iam.User, si *scopes.ScopeInfo, authorizedActions []string) *pb.User {
|
|
pu := &pb.User{
|
|
Id: u.GetPublicId(),
|
|
ScopeId: u.GetScopeId(),
|
|
Scope: si,
|
|
CreatedTime: u.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: u.GetUpdateTime().GetTimestamp(),
|
|
Version: u.GetVersion(),
|
|
AuthorizedActions: authorizedActions,
|
|
}
|
|
if u.GetName() != "" {
|
|
pu.Name = wrapperspb.String(u.GetName())
|
|
}
|
|
if u.GetDescription() != "" {
|
|
pu.Description = wrapperspb.String(u.GetDescription())
|
|
}
|
|
return pu
|
|
}
|
|
|
|
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)
|
|
|
|
iamRepo := iam.TestRepo(t, conn, wrapper)
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iamRepo, nil
|
|
}
|
|
aliasRepoFn := func() (*talias.Repository, error) {
|
|
return talias.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
tokenRepoFn := func() (*authtoken.Repository, error) {
|
|
return authtoken.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return server.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
tokenRepo, err := tokenRepoFn()
|
|
require.NoError(t, err)
|
|
|
|
// Run analyze to update postgres meta tables
|
|
_, err = sqlDB.ExecContext(ctx, "analyze")
|
|
require.NoError(t, err)
|
|
|
|
oNoUsers, _ := iam.TestScopes(t, iamRepo)
|
|
oWithUsers, p := iam.TestScopes(t, iamRepo)
|
|
|
|
var allUsers []*pb.User
|
|
// Get the 3 system users (u_recovery, u_anon, u_auth)
|
|
us, _, err := iamRepo.ListUsers(ctx, []string{"global"})
|
|
require.NoError(t, err)
|
|
require.Len(t, us, 3)
|
|
// They (should) be returned in reverse order by create time, so we reverse
|
|
slices.Reverse(us)
|
|
for _, u := range us {
|
|
allUsers = append(allUsers, userToProto(u, &scopes.ScopeInfo{
|
|
Id: u.ScopeId,
|
|
Name: scope.Global.String(),
|
|
Description: "Global Scope",
|
|
Type: scope.Global.String(),
|
|
}, testAuthorizedActions))
|
|
}
|
|
|
|
authMethod := password.TestAuthMethods(t, conn, "global", 1)[0]
|
|
acct := password.TestAccount(t, conn, authMethod.GetPublicId(), "test_user")
|
|
u := iam.TestUser(t, iamRepo, "global", iam.WithAccountIds(acct.PublicId))
|
|
allUsers = append(allUsers, userToProto(u, &scopes.ScopeInfo{
|
|
Id: u.ScopeId,
|
|
Name: scope.Global.String(),
|
|
Description: "Global Scope",
|
|
Type: scope.Global.String(),
|
|
}, testAuthorizedActions))
|
|
|
|
// add roles to be able to see all users
|
|
allowedRole := iam.TestRole(t, conn, "global")
|
|
iam.TestRoleGrant(t, conn, allowedRole.GetPublicId(), "ids=*;type=*;actions=*")
|
|
iam.TestUserRole(t, conn, allowedRole.GetPublicId(), u.GetPublicId())
|
|
for _, scope := range []*iam.Scope{oWithUsers, oNoUsers} {
|
|
allowedRole := iam.TestRole(t, conn, scope.GetPublicId())
|
|
iam.TestRoleGrant(t, conn, allowedRole.GetPublicId(), "ids=*;type=*;actions=*")
|
|
iam.TestUserRole(t, conn, allowedRole.GetPublicId(), u.GetPublicId())
|
|
}
|
|
|
|
at, err := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
|
require.NoError(t, err)
|
|
|
|
// Test without anon user
|
|
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)
|
|
|
|
var safeToDeleteUser string
|
|
orgScopeInfo := &scopes.ScopeInfo{Id: oWithUsers.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}
|
|
for i := 0; i < 10; i++ {
|
|
ou := iam.TestUser(t, iamRepo, oWithUsers.GetPublicId())
|
|
allUsers = append(allUsers, userToProto(ou, orgScopeInfo, testAuthorizedActions))
|
|
safeToDeleteUser = ou.GetPublicId()
|
|
}
|
|
slices.Reverse(allUsers)
|
|
|
|
a, err := users.NewService(ctx, iamRepoFn, aliasRepoFn, 1000)
|
|
require.NoError(t, err, "Couldn't create new user service.")
|
|
|
|
// Run analyze to update postgres estimates
|
|
_, err = sqlDB.ExecContext(context.Background(), "analyze")
|
|
require.NoError(t, err)
|
|
|
|
itemCount := uint32(len(allUsers))
|
|
testPageSize := int((itemCount - 2) / 2)
|
|
|
|
// Start paginating, recursively
|
|
req := &pbs.ListUsersRequest{
|
|
ScopeId: "global",
|
|
Recursive: true,
|
|
Filter: "",
|
|
ListToken: "",
|
|
PageSize: uint32(testPageSize),
|
|
}
|
|
got, err := a.ListUsers(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), testPageSize)
|
|
// Compare without comparing the list token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListUsersResponse{
|
|
Items: allUsers[0:testPageSize],
|
|
ResponseType: "delta",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
// In addition to the added users, there are the users added
|
|
// by the test setup when specifying the permissions of the
|
|
// requester
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Request second page
|
|
req.ListToken = got.ListToken
|
|
got, err = a.ListUsers(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), testPageSize)
|
|
// Compare without comparing the list token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListUsersResponse{
|
|
Items: allUsers[testPageSize : testPageSize*2],
|
|
ResponseType: "delta",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Request rest of results
|
|
req.ListToken = got.ListToken
|
|
req.PageSize = 10
|
|
got, err = a.ListUsers(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 2)
|
|
// Compare without comparing the list token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListUsersResponse{
|
|
Items: allUsers[testPageSize*2:],
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Update 2 users and see them in the refresh
|
|
r1 := allUsers[len(allUsers)-1]
|
|
r1.Description = wrapperspb.String("updated1")
|
|
resp1, err := a.UpdateUser(ctx, &pbs.UpdateUserRequest{
|
|
Id: r1.GetId(),
|
|
Item: &pb.User{Description: r1.GetDescription(), Version: r1.GetVersion()},
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{"description"}},
|
|
})
|
|
require.NoError(t, err)
|
|
r1.UpdatedTime = resp1.GetItem().GetUpdatedTime()
|
|
r1.Version = resp1.GetItem().GetVersion()
|
|
allUsers = append([]*pb.User{r1}, allUsers[:len(allUsers)-1]...)
|
|
|
|
r2 := allUsers[len(allUsers)-1]
|
|
r2.Description = wrapperspb.String("updated2")
|
|
resp2, err := a.UpdateUser(ctx, &pbs.UpdateUserRequest{
|
|
Id: r2.GetId(),
|
|
Item: &pb.User{Description: r2.GetDescription(), Version: r2.GetVersion()},
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{"description"}},
|
|
})
|
|
require.NoError(t, err)
|
|
r2.UpdatedTime = resp2.GetItem().GetUpdatedTime()
|
|
r2.Version = resp2.GetItem().GetVersion()
|
|
allUsers = append([]*pb.User{r2}, allUsers[:len(allUsers)-1]...)
|
|
|
|
// Run analyze to update postgres estimates
|
|
_, err = sqlDB.ExecContext(ctx, "analyze")
|
|
require.NoError(t, err)
|
|
|
|
// Request updated results
|
|
req.ListToken = got.ListToken
|
|
req.PageSize = 1
|
|
got, err = a.ListUsers(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
// Compare without comparing the list token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListUsersResponse{
|
|
Items: []*pb.User{allUsers[0]},
|
|
ResponseType: "delta",
|
|
SortBy: "updated_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Get next page
|
|
req.ListToken = got.ListToken
|
|
got, err = a.ListUsers(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
// Compare without comparing the list token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListUsersResponse{
|
|
Items: []*pb.User{allUsers[1]},
|
|
ResponseType: "complete",
|
|
SortBy: "updated_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "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`, allUsers[len(allUsers)-2].Id, allUsers[len(allUsers)-1].Id)
|
|
got, err = a.ListUsers(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListUsersResponse{
|
|
Items: []*pb.User{allUsers[len(allUsers)-2]},
|
|
ResponseType: "delta",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
// Should be empty again
|
|
RemovedIds: nil,
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
|
),
|
|
)
|
|
req.ListToken = got.ListToken
|
|
// Get the second page
|
|
got, err = a.ListUsers(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListUsersResponse{
|
|
Items: []*pb.User{allUsers[len(allUsers)-1]},
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
_, err = iamRepo.DeleteUser(ctx, safeToDeleteUser)
|
|
require.NoError(t, err)
|
|
req.ListToken = got.ListToken
|
|
got, err = a.ListUsers(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListUsersResponse{
|
|
Items: nil,
|
|
ResponseType: "complete",
|
|
SortBy: "updated_time",
|
|
SortDir: "desc",
|
|
RemovedIds: []string{safeToDeleteUser},
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Create unauthenticated user
|
|
unauthAt := authtoken.TestAuthToken(t, conn, kms, oWithUsers.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 = a.ListUsers(ctx, &pbs.ListUsersRequest{
|
|
ScopeId: "global",
|
|
Recursive: true,
|
|
})
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, handlers.ForbiddenError(), err)
|
|
}
|
|
|
|
func TestListResolvableAliasesPagination(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)
|
|
|
|
iamRepo := iam.TestRepo(t, conn, wrapper)
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iamRepo, nil
|
|
}
|
|
aliasRepoFn := func() (*talias.Repository, error) {
|
|
return talias.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
tokenRepoFn := func() (*authtoken.Repository, error) {
|
|
return authtoken.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return server.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
tokenRepo, err := tokenRepoFn()
|
|
require.NoError(t, err)
|
|
|
|
// Run analyze to update postgres meta tables
|
|
_, err = sqlDB.ExecContext(ctx, "analyze")
|
|
require.NoError(t, err)
|
|
|
|
_, p := iam.TestScopes(t, iamRepo)
|
|
tar := tcp.TestTarget(ctx, t, conn, p.GetPublicId(), "resolvable")
|
|
|
|
_, unresolvableP := iam.TestScopes(t, iamRepo)
|
|
unresolvedTar := tcp.TestTarget(ctx, t, conn, unresolvableP.GetPublicId(), "unresolvable")
|
|
// Create an alias that shouldn't be included in the paginated list results.
|
|
talias.TestAlias(t, rw, "unresolved.alias", talias.WithDestinationId(unresolvedTar.GetPublicId()))
|
|
|
|
authMethod := password.TestAuthMethods(t, conn, "global", 1)[0]
|
|
acct := password.TestAccount(t, conn, authMethod.GetPublicId(), "test_user")
|
|
u := iam.TestUser(t, iamRepo, "global", iam.WithAccountIds(acct.PublicId))
|
|
|
|
// add roles for requester to be able to perform all actions on everyone
|
|
allowedRole := iam.TestRole(t, conn, "global", iam.WithGrantScopeIds([]string{globals.GrantScopeThis, globals.GrantScopeDescendants}))
|
|
iam.TestRoleGrant(t, conn, allowedRole.GetPublicId(), "ids=*;type=*;actions=*")
|
|
iam.TestUserRole(t, conn, allowedRole.GetPublicId(), u.GetPublicId())
|
|
|
|
at, err := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
|
require.NoError(t, err)
|
|
|
|
// add roles for user whose resolvable aliases are being listed they can
|
|
// only see the aliases which resolve to targets in project p.
|
|
resolvingUsersAt := authtoken.TestAuthToken(t, conn, kms, scope.Global.String())
|
|
roleForResolving := iam.TestRole(t, conn, "global", iam.WithGrantScopeIds([]string{p.GetPublicId(), globals.GrantScopeThis}))
|
|
iam.TestRoleGrant(t, conn, roleForResolving.GetPublicId(), "ids=*;type=target;actions=authorize-session")
|
|
iam.TestRoleGrant(t, conn, roleForResolving.GetPublicId(), "ids={{.User.Id}};type=user;actions=list-resolvable-aliases")
|
|
iam.TestUserRole(t, conn, roleForResolving.GetPublicId(), resolvingUsersAt.GetIamUserId())
|
|
|
|
var allAliases []*talias.Alias
|
|
var allAliasPbs []*pbalias.Alias
|
|
var safeToRemoveAlias *talias.Alias
|
|
for i := 0; i < 10; i++ {
|
|
na := talias.TestAlias(t, rw, fmt.Sprintf("aliase%d.test", i), talias.WithDestinationId(tar.GetPublicId()))
|
|
allAliases = append(allAliases, na)
|
|
allAliasPbs = append(allAliasPbs, &pbalias.Alias{
|
|
Id: na.GetPublicId(),
|
|
Value: na.GetValue(),
|
|
DestinationId: wrapperspb.String(na.GetDestinationId()),
|
|
CreatedTime: na.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: na.GetUpdateTime().GetTimestamp(),
|
|
Type: "target",
|
|
})
|
|
|
|
safeToRemoveAlias = na
|
|
}
|
|
slices.Reverse(allAliases)
|
|
slices.Reverse(allAliasPbs)
|
|
|
|
a, err := users.NewService(ctx, iamRepoFn, aliasRepoFn, 1000)
|
|
require.NoError(t, err, "Couldn't create new user service.")
|
|
|
|
// Run analyze to update postgres estimates
|
|
_, err = sqlDB.ExecContext(context.Background(), "analyze")
|
|
require.NoError(t, err)
|
|
|
|
// +1 because we have one alias that points to an unresolvable target
|
|
itemCount := uint32(len(allAliasPbs)) + 1
|
|
testPageSize := int((itemCount - 2) / 2)
|
|
|
|
// See that the resolvingUser can query themselves
|
|
requestInfo := authpb.RequestInfo{
|
|
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
|
PublicId: resolvingUsersAt.GetPublicId(),
|
|
Token: resolvingUsersAt.GetToken(),
|
|
}
|
|
requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
|
ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
|
|
got, err := a.ListResolvableAliases(ctx, &pbs.ListResolvableAliasesRequest{
|
|
Id: resolvingUsersAt.GetIamUserId(),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListResolvableAliasesResponse{
|
|
Items: allAliasPbs,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// This user cannot list resolvable for other aliases
|
|
_, err = a.ListResolvableAliases(ctx, &pbs.ListResolvableAliasesRequest{
|
|
Id: at.GetIamUserId(),
|
|
})
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, handlers.ForbiddenError(), err)
|
|
|
|
// Now let the admin user list resolvable aliases
|
|
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.ListResolvableAliasesRequest{
|
|
Id: resolvingUsersAt.GetIamUserId(),
|
|
ListToken: "",
|
|
PageSize: uint32(testPageSize),
|
|
}
|
|
got, err = a.ListResolvableAliases(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), testPageSize)
|
|
// Compare without comparing the list token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListResolvableAliasesResponse{
|
|
Items: allAliasPbs[0:testPageSize],
|
|
ResponseType: "delta",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
// In addition to the added users, there are the users added
|
|
// by the test setup when specifying the permissions of the
|
|
// requester
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Request second page
|
|
req.ListToken = got.ListToken
|
|
got, err = a.ListResolvableAliases(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), testPageSize)
|
|
// Compare without comparing the list token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListResolvableAliasesResponse{
|
|
Items: allAliasPbs[testPageSize : testPageSize*2],
|
|
ResponseType: "delta",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Request rest of results
|
|
req.ListToken = got.ListToken
|
|
req.PageSize = 10
|
|
got, err = a.ListResolvableAliases(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 2)
|
|
// Compare without comparing the list token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListResolvableAliasesResponse{
|
|
Items: allAliasPbs[testPageSize*2:],
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Update 2 aliases and see them in the refresh
|
|
aliasRepo, err := aliasRepoFn()
|
|
require.NoError(t, err)
|
|
|
|
for i := 0; i < 2; i++ {
|
|
r := allAliases[len(allAliasPbs)-1]
|
|
rPb := allAliasPbs[len(allAliasPbs)-1]
|
|
r.Description = fmt.Sprintf("updated%d", i)
|
|
|
|
updated, _, err := aliasRepo.UpdateAlias(ctx, r, r.GetVersion(), []string{"description"})
|
|
require.NoError(t, err)
|
|
|
|
r.Version = updated.GetVersion()
|
|
r.UpdateTime = updated.GetUpdateTime()
|
|
rPb.UpdatedTime = updated.GetUpdateTime().GetTimestamp()
|
|
// ListResolvableAliases does not return the description, or the version
|
|
// so we do not update those values here in the protobuf aliases
|
|
allAliases = append([]*talias.Alias{r}, allAliases[:len(allAliases)-1]...)
|
|
allAliasPbs = append([]*pbalias.Alias{rPb}, allAliasPbs[:len(allAliasPbs)-1]...)
|
|
}
|
|
|
|
// Run analyze to update postgres estimates
|
|
_, err = sqlDB.ExecContext(ctx, "analyze")
|
|
require.NoError(t, err)
|
|
|
|
// Request updated results
|
|
req.ListToken = got.ListToken
|
|
req.PageSize = 1
|
|
got, err = a.ListResolvableAliases(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
// Compare without comparing the list token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListResolvableAliasesResponse{
|
|
Items: []*pbalias.Alias{allAliasPbs[0]},
|
|
ResponseType: "delta",
|
|
SortBy: "updated_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Get next page
|
|
req.ListToken = got.ListToken
|
|
got, err = a.ListResolvableAliases(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
// Compare without comparing the list token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListResolvableAliasesResponse{
|
|
Items: []*pbalias.Alias{allAliasPbs[1]},
|
|
ResponseType: "complete",
|
|
SortBy: "updated_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
_, err = aliasRepo.DeleteAlias(ctx, safeToRemoveAlias.GetPublicId())
|
|
require.NoError(t, err)
|
|
req.ListToken = got.ListToken
|
|
got, err = a.ListResolvableAliases(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListResolvableAliasesResponse{
|
|
Items: nil,
|
|
ResponseType: "complete",
|
|
SortBy: "updated_time",
|
|
SortDir: "desc",
|
|
RemovedIds: []string{safeToRemoveAlias.GetPublicId()},
|
|
EstItemCount: itemCount,
|
|
},
|
|
protocmp.Transform(),
|
|
protocmp.SortRepeated(func(a, b string) bool {
|
|
return strings.Compare(a, b) < 0
|
|
}),
|
|
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Create unauthenticated user
|
|
unauthAt := authtoken.TestAuthToken(t, conn, kms, scope.Global.String())
|
|
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 = a.ListResolvableAliases(ctx, &pbs.ListResolvableAliasesRequest{
|
|
Id: resolvingUsersAt.GetIamUserId(),
|
|
})
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, handlers.ForbiddenError(), err)
|
|
}
|
|
|
|
func TestDelete(t *testing.T) {
|
|
u, _, repoFn, aliasRepoFn := createDefaultUserAndRepos(t, false)
|
|
|
|
s, err := users.NewService(context.Background(), repoFn, aliasRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new user service.")
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.DeleteUserRequest
|
|
res *pbs.DeleteUserResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "Delete an Existing User",
|
|
req: &pbs.DeleteUserRequest{
|
|
Id: u.GetPublicId(),
|
|
},
|
|
},
|
|
{
|
|
name: "Delete bad user id",
|
|
req: &pbs.DeleteUserRequest{
|
|
Id: globals.UserPrefix + "_doesntexis",
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Bad User Id formatting",
|
|
req: &pbs.DeleteUserRequest{
|
|
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.DeleteUser(auth.DisabledAuthTestContext(repoFn, u.GetScopeId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "DeleteUser(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
}
|
|
assert.EqualValuesf(tc.res, got, "DeleteUser(%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)
|
|
u, _, repoFn, aliasRepoFn := createDefaultUserAndRepos(t, false)
|
|
|
|
s, err := users.NewService(context.Background(), repoFn, aliasRepoFn, 1000)
|
|
require.NoError(err, "Error when getting new user service")
|
|
req := &pbs.DeleteUserRequest{
|
|
Id: u.GetPublicId(),
|
|
}
|
|
ctx := auth.DisabledAuthTestContext(repoFn, u.GetScopeId())
|
|
_, gErr := s.DeleteUser(ctx, req)
|
|
assert.NoError(gErr, "First attempt")
|
|
_, gErr = s.DeleteUser(ctx, req)
|
|
assert.Error(gErr, "Second attempt")
|
|
assert.True(errors.Is(gErr, handlers.ApiErrorWithCode(codes.NotFound)), "Expected permission denied for the second delete.")
|
|
}
|
|
|
|
func TestCreate(t *testing.T) {
|
|
defaultUser, _, repoFn, aliasRepoFn := createDefaultUserAndRepos(t, false)
|
|
defaultCreated := defaultUser.GetCreateTime().GetTimestamp().AsTime()
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.CreateUserRequest
|
|
res *pbs.CreateUserResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "Create a valid User",
|
|
req: &pbs.CreateUserRequest{Item: &pb.User{
|
|
ScopeId: defaultUser.GetScopeId(),
|
|
Name: &wrapperspb.StringValue{Value: "name"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
}},
|
|
res: &pbs.CreateUserResponse{
|
|
Uri: fmt.Sprintf("users/%s_", globals.UserPrefix),
|
|
Item: &pb.User{
|
|
ScopeId: defaultUser.GetScopeId(),
|
|
Scope: &scopes.ScopeInfo{Id: defaultUser.GetScopeId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Name: &wrapperspb.StringValue{Value: "name"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Version: 1,
|
|
AuthorizedActions: testAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Create a valid Global User",
|
|
req: &pbs.CreateUserRequest{Item: &pb.User{
|
|
ScopeId: scope.Global.String(),
|
|
Name: &wrapperspb.StringValue{Value: "name"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
}},
|
|
res: &pbs.CreateUserResponse{
|
|
Uri: fmt.Sprintf("users/%s_", globals.UserPrefix),
|
|
Item: &pb.User{
|
|
ScopeId: scope.Global.String(),
|
|
Scope: &scopes.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,
|
|
AuthorizedActions: testAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Can't specify Id",
|
|
req: &pbs.CreateUserRequest{Item: &pb.User{
|
|
ScopeId: defaultUser.GetScopeId(),
|
|
Id: globals.UserPrefix + "_notallowed",
|
|
}},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Can't specify Created Time",
|
|
req: &pbs.CreateUserRequest{Item: &pb.User{
|
|
ScopeId: defaultUser.GetScopeId(),
|
|
CreatedTime: timestamppb.Now(),
|
|
}},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Can't specify Update Time",
|
|
req: &pbs.CreateUserRequest{Item: &pb.User{
|
|
ScopeId: defaultUser.GetScopeId(),
|
|
UpdatedTime: timestamppb.Now(),
|
|
}},
|
|
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)
|
|
s, err := users.NewService(context.Background(), repoFn, aliasRepoFn, 1000)
|
|
require.NoError(err, "Error when getting new user service.")
|
|
|
|
got, gErr := s.CreateUser(auth.DisabledAuthTestContext(repoFn, tc.req.GetItem().GetScopeId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "CreateUser(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
}
|
|
if got != nil {
|
|
assert.Contains(got.GetUri(), tc.res.Uri)
|
|
assert.True(strings.HasPrefix(got.GetItem().GetId(), globals.UserPrefix+"_"))
|
|
gotCreateTime := got.GetItem().GetCreatedTime().AsTime()
|
|
gotUpdateTime := got.GetItem().GetUpdatedTime().AsTime()
|
|
// Verify it is a user created after the test setup's default user
|
|
assert.True(gotCreateTime.After(defaultCreated), "New user should have been created after default user. Was created %v, which is after %v", gotCreateTime, defaultCreated)
|
|
assert.True(gotUpdateTime.After(defaultCreated), "New user should have been updated after default user. Was updated %v, which is after %v", gotUpdateTime, defaultCreated)
|
|
|
|
// Clear all values which are hard to compare against.
|
|
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(
|
|
got,
|
|
tc.res,
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
), "CreateUser(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdate(t *testing.T) {
|
|
u, _, repoFn, aliasRepoFn := createDefaultUserAndRepos(t, false)
|
|
tested, err := users.NewService(context.Background(), repoFn, aliasRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new user service.")
|
|
|
|
created := u.GetCreateTime().GetTimestamp().AsTime()
|
|
toMerge := &pbs.UpdateUserRequest{
|
|
Id: u.GetPublicId(),
|
|
}
|
|
|
|
var version uint32 = 1
|
|
|
|
resetUser := func() {
|
|
repo, err := repoFn()
|
|
require.NoError(t, err, "Couldn't get a new repo")
|
|
version++ // From the test case that resulted in calling this
|
|
u, _, _, err = repo.UpdateUser(context.Background(), u, version, []string{"Name", "Description"})
|
|
require.NoError(t, err, "Failed to reset the user")
|
|
version++
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.UpdateUserRequest
|
|
res *pbs.UpdateUserResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "Update an Existing User",
|
|
req: &pbs.UpdateUserRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name", "description"},
|
|
},
|
|
Item: &pb.User{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
},
|
|
},
|
|
res: &pbs.UpdateUserResponse{
|
|
Item: &pb.User{
|
|
Id: u.GetPublicId(),
|
|
ScopeId: u.GetScopeId(),
|
|
Scope: &scopes.ScopeInfo{Id: u.GetScopeId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
CreatedTime: u.GetCreateTime().GetTimestamp(),
|
|
AuthorizedActions: testAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Multiple Paths in single string",
|
|
req: &pbs.UpdateUserRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name,description"},
|
|
},
|
|
Item: &pb.User{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
},
|
|
},
|
|
res: &pbs.UpdateUserResponse{
|
|
Item: &pb.User{
|
|
Id: u.GetPublicId(),
|
|
ScopeId: u.GetScopeId(),
|
|
Scope: &scopes.ScopeInfo{Id: u.GetScopeId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
CreatedTime: u.GetCreateTime().GetTimestamp(),
|
|
AuthorizedActions: testAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "No Update Mask",
|
|
req: &pbs.UpdateUserRequest{
|
|
Item: &pb.User{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "No Paths in Mask",
|
|
req: &pbs.UpdateUserRequest{
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
|
|
Item: &pb.User{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Only non-existent paths in Mask",
|
|
req: &pbs.UpdateUserRequest{
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{"nonexistent_field"}},
|
|
Item: &pb.User{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Unset Name",
|
|
req: &pbs.UpdateUserRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name"},
|
|
},
|
|
Item: &pb.User{
|
|
Description: &wrapperspb.StringValue{Value: "ignored"},
|
|
},
|
|
},
|
|
res: &pbs.UpdateUserResponse{
|
|
Item: &pb.User{
|
|
Id: u.GetPublicId(),
|
|
ScopeId: u.GetScopeId(),
|
|
Scope: &scopes.ScopeInfo{Id: u.GetScopeId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Description: &wrapperspb.StringValue{Value: "default"},
|
|
CreatedTime: u.GetCreateTime().GetTimestamp(),
|
|
AuthorizedActions: testAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update Only Name",
|
|
req: &pbs.UpdateUserRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name"},
|
|
},
|
|
Item: &pb.User{
|
|
Name: &wrapperspb.StringValue{Value: "updated"},
|
|
Description: &wrapperspb.StringValue{Value: "ignored"},
|
|
},
|
|
},
|
|
res: &pbs.UpdateUserResponse{
|
|
Item: &pb.User{
|
|
Id: u.GetPublicId(),
|
|
ScopeId: u.GetScopeId(),
|
|
Scope: &scopes.ScopeInfo{Id: u.GetScopeId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Name: &wrapperspb.StringValue{Value: "updated"},
|
|
Description: &wrapperspb.StringValue{Value: "default"},
|
|
CreatedTime: u.GetCreateTime().GetTimestamp(),
|
|
AuthorizedActions: testAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update Only Description",
|
|
req: &pbs.UpdateUserRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"description"},
|
|
},
|
|
Item: &pb.User{
|
|
Name: &wrapperspb.StringValue{Value: "ignored"},
|
|
Description: &wrapperspb.StringValue{Value: "notignored"},
|
|
},
|
|
},
|
|
res: &pbs.UpdateUserResponse{
|
|
Item: &pb.User{
|
|
Id: u.GetPublicId(),
|
|
ScopeId: u.GetScopeId(),
|
|
Scope: &scopes.ScopeInfo{Id: u.GetScopeId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Name: &wrapperspb.StringValue{Value: "default"},
|
|
Description: &wrapperspb.StringValue{Value: "notignored"},
|
|
CreatedTime: u.GetCreateTime().GetTimestamp(),
|
|
AuthorizedActions: testAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update a Non Existing User",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: globals.UserPrefix + "_DoesntExis",
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"description"},
|
|
},
|
|
Item: &pb.User{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Cant change Id",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: u.GetPublicId(),
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"id"},
|
|
},
|
|
Item: &pb.User{
|
|
Id: globals.UserPrefix + "_somethinge",
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "new desc"},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Cant specify Created Time",
|
|
req: &pbs.UpdateUserRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"created_time"},
|
|
},
|
|
Item: &pb.User{
|
|
CreatedTime: timestamppb.Now(),
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Cant specify Updated Time",
|
|
req: &pbs.UpdateUserRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"updated_time"},
|
|
},
|
|
Item: &pb.User{
|
|
UpdatedTime: timestamppb.Now(),
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tc.req.Item.Version = version
|
|
|
|
assert, require := assert.New(t), require.New(t)
|
|
req := proto.Clone(toMerge).(*pbs.UpdateUserRequest)
|
|
proto.Merge(req, tc.req)
|
|
|
|
// Test with bad version (too high, too low)
|
|
req.Item.Version = version + 2
|
|
_, gErr := tested.UpdateUser(auth.DisabledAuthTestContext(repoFn, u.GetScopeId()), req)
|
|
require.Error(gErr)
|
|
req.Item.Version = version - 1
|
|
_, gErr = tested.UpdateUser(auth.DisabledAuthTestContext(repoFn, u.GetScopeId()), req)
|
|
require.Error(gErr)
|
|
req.Item.Version = version
|
|
|
|
got, gErr := tested.UpdateUser(auth.DisabledAuthTestContext(repoFn, u.GetScopeId()), req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
require.True(errors.Is(gErr, tc.err), "UpdateUser(%+v) got error %v, wanted %v", req, gErr, tc.err)
|
|
} else {
|
|
defer resetUser()
|
|
}
|
|
|
|
if got != nil {
|
|
assert.NotNilf(tc.res, "Expected UpdateUser response to be nil, but was %v", got)
|
|
gotUpdateTime := got.GetItem().GetUpdatedTime().AsTime()
|
|
// Verify it is a user updated after it was created
|
|
assert.True(gotUpdateTime.After(created), "Updated user should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, created)
|
|
|
|
// Clear all values which are hard to compare against.
|
|
got.Item.UpdatedTime, tc.res.Item.UpdatedTime = nil, nil
|
|
assert.Equal(version+1, got.GetItem().GetVersion())
|
|
tc.res.Item.Version = version + 1
|
|
}
|
|
assert.Empty(cmp.Diff(
|
|
tc.res,
|
|
got,
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
), "UpdateUser(%q) got response %q, wanted %q", req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAddAccount(t *testing.T) {
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
wrap := db.TestWrapper(t)
|
|
ctx := context.Background()
|
|
rw := db.New(conn)
|
|
kmsCache := kms.TestKms(t, conn, wrap)
|
|
iamRepo := iam.TestRepo(t, conn, wrap)
|
|
repoFn := func() (*iam.Repository, error) {
|
|
return iamRepo, nil
|
|
}
|
|
aliasRepoFn := func() (*talias.Repository, error) {
|
|
return talias.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
s, err := users.NewService(ctx, repoFn, aliasRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new user service.")
|
|
|
|
o, _ := iam.TestScopes(t, iamRepo)
|
|
acctCnt := 3
|
|
accts := make([]*password.Account, 0, acctCnt)
|
|
for i := 0; i < acctCnt; i++ {
|
|
amId := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0].GetPublicId()
|
|
newAcct := password.TestAccount(t, conn, amId, "name1")
|
|
accts = append(accts, newAcct)
|
|
}
|
|
|
|
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), o.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
oidcAm := oidc.TestAuthMethod(
|
|
t, conn, databaseWrapper, o.PublicId, oidc.ActivePrivateState,
|
|
"alice-rp", "fido",
|
|
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://www.alice.com")[0]),
|
|
oidc.WithSigningAlgs(oidc.RS256),
|
|
oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
|
|
)
|
|
oidcAcct := oidc.TestAccount(t, conn, oidcAm, "test-subject")
|
|
|
|
ldapAm := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://ldap1"})
|
|
ldapAcct := ldap.TestAccount(t, conn, ldapAm, "test-acct",
|
|
ldap.WithMemberOfGroups(ctx, "admin"),
|
|
ldap.WithFullName(ctx, "test-name"),
|
|
ldap.WithEmail(ctx, "test-email"),
|
|
ldap.WithDn(ctx, "test-dn"),
|
|
)
|
|
|
|
addCases := []struct {
|
|
name string
|
|
setup func(*iam.User)
|
|
addAccounts []string
|
|
resultAccounts []string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Add account on empty user",
|
|
setup: func(u *iam.User) {},
|
|
addAccounts: []string{accts[1].GetPublicId()},
|
|
resultAccounts: []string{accts[1].GetPublicId()},
|
|
},
|
|
{
|
|
name: "Add oidc account on empty user",
|
|
setup: func(u *iam.User) {},
|
|
addAccounts: []string{oidcAcct.GetPublicId()},
|
|
resultAccounts: []string{oidcAcct.GetPublicId()},
|
|
},
|
|
{
|
|
name: "Add ldap account on empty user",
|
|
setup: func(u *iam.User) {},
|
|
addAccounts: []string{ldapAcct.GetPublicId()},
|
|
resultAccounts: []string{ldapAcct.GetPublicId()},
|
|
},
|
|
{
|
|
name: "Add account on populated user",
|
|
setup: func(u *iam.User) {
|
|
_, err := iamRepo.SetUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
|
[]string{accts[0].GetPublicId()})
|
|
require.NoError(t, err)
|
|
u.Version = u.Version + 1
|
|
},
|
|
addAccounts: []string{accts[1].GetPublicId()},
|
|
resultAccounts: []string{accts[0].GetPublicId(), accts[1].GetPublicId()},
|
|
},
|
|
{
|
|
name: "Add duplicate account on populated user",
|
|
setup: func(u *iam.User) {
|
|
_, err := iamRepo.SetUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
|
[]string{accts[0].GetPublicId()})
|
|
require.NoError(t, err)
|
|
u.Version = u.Version + 1
|
|
},
|
|
addAccounts: []string{accts[1].GetPublicId(), accts[1].GetPublicId()},
|
|
resultAccounts: []string{accts[0].GetPublicId(), accts[1].GetPublicId()},
|
|
},
|
|
{
|
|
name: "Add empty on populated user",
|
|
setup: func(u *iam.User) {
|
|
_, err := iamRepo.SetUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
|
[]string{accts[0].GetPublicId(), accts[1].GetPublicId()})
|
|
require.NoError(t, err)
|
|
u.Version = u.Version + 1
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range addCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
usr := iam.TestUser(t, iamRepo, o.GetPublicId())
|
|
defer func() {
|
|
_, err := iamRepo.DeleteUser(context.Background(), usr.GetPublicId())
|
|
require.NoError(t, err)
|
|
}()
|
|
tc.setup(usr)
|
|
req := &pbs.AddUserAccountsRequest{
|
|
Id: usr.GetPublicId(),
|
|
Version: usr.GetVersion(),
|
|
AccountIds: tc.addAccounts,
|
|
}
|
|
|
|
got, err := s.AddUserAccounts(auth.DisabledAuthTestContext(repoFn, o.GetPublicId()), req)
|
|
if tc.wantErr {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err, "Got error: %v", err)
|
|
assert.ElementsMatch(t, got.GetItem().GetAccountIds(), tc.resultAccounts)
|
|
})
|
|
}
|
|
|
|
usr := iam.TestUser(t, iamRepo, o.GetPublicId())
|
|
|
|
failCases := []struct {
|
|
name string
|
|
req *pbs.AddUserAccountsRequest
|
|
err error
|
|
}{
|
|
{
|
|
name: "Bad user Id",
|
|
req: &pbs.AddUserAccountsRequest{
|
|
Id: "bad id",
|
|
Version: usr.GetVersion(),
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Bad account Id",
|
|
req: &pbs.AddUserAccountsRequest{
|
|
Id: usr.GetPublicId(),
|
|
Version: usr.GetVersion(),
|
|
AccountIds: []string{"invalid"},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
}
|
|
for _, tc := range failCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
_, gErr := s.AddUserAccounts(auth.DisabledAuthTestContext(repoFn, usr.GetScopeId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "AddUserAccounts(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetAccount(t *testing.T) {
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
wrap := db.TestWrapper(t)
|
|
ctx := context.Background()
|
|
rw := db.New(conn)
|
|
kmsCache := kms.TestKms(t, conn, wrap)
|
|
iamRepo := iam.TestRepo(t, conn, wrap)
|
|
repoFn := func() (*iam.Repository, error) {
|
|
return iamRepo, nil
|
|
}
|
|
aliasRepoFn := func() (*talias.Repository, error) {
|
|
return talias.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
s, err := users.NewService(ctx, repoFn, aliasRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new user service.")
|
|
|
|
o, _ := iam.TestScopes(t, iamRepo)
|
|
acctCnt := 3
|
|
accts := make([]*password.Account, 0, acctCnt)
|
|
for i := 0; i < acctCnt; i++ {
|
|
amId := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0].GetPublicId()
|
|
newAcct := password.TestAccount(t, conn, amId, "name1")
|
|
accts = append(accts, newAcct)
|
|
}
|
|
|
|
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), o.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
oidcAm := oidc.TestAuthMethod(
|
|
t, conn, databaseWrapper, o.PublicId, oidc.ActivePrivateState,
|
|
"alice-rp", "fido",
|
|
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://www.alice.com")[0]),
|
|
oidc.WithSigningAlgs(oidc.RS256),
|
|
oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
|
|
)
|
|
oidcAcct := oidc.TestAccount(t, conn, oidcAm, "test-subject")
|
|
|
|
ldapAm := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://ldap1"})
|
|
ldapAcct := ldap.TestAccount(t, conn, ldapAm, "test-acct",
|
|
ldap.WithMemberOfGroups(ctx, "admin"),
|
|
ldap.WithFullName(ctx, "test-name"),
|
|
ldap.WithEmail(ctx, "test-email"),
|
|
ldap.WithDn(ctx, "test-dn"),
|
|
)
|
|
|
|
setCases := []struct {
|
|
name string
|
|
setup func(*iam.User)
|
|
setAccounts []string
|
|
resultAccounts []string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Set account on empty user",
|
|
setup: func(u *iam.User) {},
|
|
setAccounts: []string{accts[1].GetPublicId()},
|
|
resultAccounts: []string{accts[1].GetPublicId()},
|
|
},
|
|
{
|
|
name: "Set oidc account on empty user",
|
|
setup: func(u *iam.User) {},
|
|
setAccounts: []string{oidcAcct.GetPublicId()},
|
|
resultAccounts: []string{oidcAcct.GetPublicId()},
|
|
},
|
|
{
|
|
name: "Set ldap account on empty user",
|
|
setup: func(u *iam.User) {},
|
|
setAccounts: []string{ldapAcct.GetPublicId()},
|
|
resultAccounts: []string{ldapAcct.GetPublicId()},
|
|
},
|
|
{
|
|
name: "Set account on populated user",
|
|
setup: func(u *iam.User) {
|
|
_, err := iamRepo.AddUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
|
[]string{accts[0].GetPublicId()})
|
|
require.NoError(t, err)
|
|
u.Version = u.Version + 1
|
|
},
|
|
setAccounts: []string{accts[1].GetPublicId()},
|
|
resultAccounts: []string{accts[1].GetPublicId()},
|
|
},
|
|
{
|
|
name: "Set duplicate account on populated user",
|
|
setup: func(u *iam.User) {
|
|
_, err := iamRepo.AddUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
|
[]string{accts[0].GetPublicId()})
|
|
require.NoError(t, err)
|
|
u.Version = u.Version + 1
|
|
},
|
|
setAccounts: []string{accts[1].GetPublicId(), accts[1].GetPublicId()},
|
|
resultAccounts: []string{accts[1].GetPublicId()},
|
|
},
|
|
{
|
|
name: "Set empty on populated user",
|
|
setup: func(u *iam.User) {
|
|
_, err := iamRepo.AddUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
|
[]string{accts[0].GetPublicId(), accts[1].GetPublicId()})
|
|
require.NoError(t, err)
|
|
u.Version = u.Version + 1
|
|
},
|
|
setAccounts: []string{},
|
|
resultAccounts: nil,
|
|
},
|
|
}
|
|
|
|
for _, tc := range setCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
usr := iam.TestUser(t, iamRepo, o.GetPublicId())
|
|
defer func() {
|
|
_, err := iamRepo.DeleteUser(context.Background(), usr.GetPublicId())
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
tc.setup(usr)
|
|
req := &pbs.SetUserAccountsRequest{
|
|
Id: usr.GetPublicId(),
|
|
Version: usr.GetVersion(),
|
|
AccountIds: tc.setAccounts,
|
|
}
|
|
|
|
got, err := s.SetUserAccounts(auth.DisabledAuthTestContext(repoFn, o.GetPublicId()), req)
|
|
if tc.wantErr {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err, "Got error: %v", err)
|
|
assert.ElementsMatch(t, got.GetItem().GetAccountIds(), tc.resultAccounts)
|
|
})
|
|
}
|
|
|
|
usr := iam.TestUser(t, iamRepo, o.GetPublicId())
|
|
|
|
failCases := []struct {
|
|
name string
|
|
req *pbs.SetUserAccountsRequest
|
|
err error
|
|
}{
|
|
{
|
|
name: "Bad User Id",
|
|
req: &pbs.SetUserAccountsRequest{
|
|
Id: "bad id",
|
|
Version: usr.GetVersion(),
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Bad account Id",
|
|
req: &pbs.SetUserAccountsRequest{
|
|
Id: usr.GetPublicId(),
|
|
Version: usr.GetVersion(),
|
|
AccountIds: []string{"invalid"},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
}
|
|
for _, tc := range failCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
_, gErr := s.SetUserAccounts(auth.DisabledAuthTestContext(repoFn, usr.GetScopeId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "SetUserAccounts(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRemoveAccount(t *testing.T) {
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
wrap := db.TestWrapper(t)
|
|
ctx := context.Background()
|
|
rw := db.New(conn)
|
|
kmsCache := kms.TestKms(t, conn, wrap)
|
|
iamRepo := iam.TestRepo(t, conn, wrap)
|
|
repoFn := func() (*iam.Repository, error) {
|
|
return iamRepo, nil
|
|
}
|
|
aliasRepoFn := func() (*talias.Repository, error) {
|
|
return talias.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
s, err := users.NewService(ctx, repoFn, aliasRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new user service.")
|
|
|
|
o, _ := iam.TestScopes(t, iamRepo)
|
|
acctCnt := 3
|
|
accts := make([]*password.Account, 0, acctCnt)
|
|
for i := 0; i < acctCnt; i++ {
|
|
amId := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0].GetPublicId()
|
|
newAcct := password.TestAccount(t, conn, amId, "name1")
|
|
accts = append(accts, newAcct)
|
|
}
|
|
|
|
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), o.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
oidcAm := oidc.TestAuthMethod(
|
|
t, conn, databaseWrapper, o.PublicId, oidc.ActivePrivateState,
|
|
"alice-rp", "fido",
|
|
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://www.alice.com")[0]),
|
|
oidc.WithSigningAlgs(oidc.RS256),
|
|
oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
|
|
)
|
|
oidcAcct := oidc.TestAccount(t, conn, oidcAm, "test-subject")
|
|
|
|
ldapAm := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://ldap1"})
|
|
ldapAcct := ldap.TestAccount(t, conn, ldapAm, "test-acct",
|
|
ldap.WithMemberOfGroups(ctx, "admin"),
|
|
ldap.WithFullName(ctx, "test-name"),
|
|
ldap.WithEmail(ctx, "test-email"),
|
|
ldap.WithDn(ctx, "test-dn"),
|
|
)
|
|
|
|
addCases := []struct {
|
|
name string
|
|
setup func(*iam.User)
|
|
removeAccounts []string
|
|
resultAccounts []string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Remove account on empty user",
|
|
setup: func(*iam.User) {},
|
|
removeAccounts: []string{accts[1].GetPublicId()},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "Remove 1 of 2 accounts from user",
|
|
setup: func(u *iam.User) {
|
|
_, err := iamRepo.SetUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
|
[]string{accts[0].GetPublicId(), accts[1].GetPublicId()})
|
|
require.NoError(t, err)
|
|
u.Version = u.Version + 1
|
|
},
|
|
removeAccounts: []string{accts[1].GetPublicId()},
|
|
resultAccounts: []string{accts[0].GetPublicId()},
|
|
},
|
|
{
|
|
name: "Remove 1 oidc account of 2 accounts from user",
|
|
setup: func(u *iam.User) {
|
|
_, err := iamRepo.SetUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
|
[]string{accts[0].GetPublicId(), oidcAcct.GetPublicId()})
|
|
require.NoError(t, err)
|
|
u.Version = u.Version + 1
|
|
},
|
|
removeAccounts: []string{oidcAcct.GetPublicId()},
|
|
resultAccounts: []string{accts[0].GetPublicId()},
|
|
},
|
|
{
|
|
name: "Remove 1 duplicate accounts of 2 accounts from user",
|
|
setup: func(u *iam.User) {
|
|
_, err := iamRepo.SetUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
|
[]string{accts[0].GetPublicId(), accts[1].GetPublicId()})
|
|
require.NoError(t, err)
|
|
u.Version = u.Version + 1
|
|
},
|
|
removeAccounts: []string{accts[1].GetPublicId(), accts[1].GetPublicId()},
|
|
resultAccounts: []string{accts[0].GetPublicId()},
|
|
},
|
|
{
|
|
name: "Remove 1 ldap account of 3 accounts from user",
|
|
setup: func(u *iam.User) {
|
|
_, err := iamRepo.SetUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
|
[]string{accts[0].GetPublicId(), oidcAcct.GetPublicId(), ldapAcct.GetPublicId()})
|
|
require.NoError(t, err)
|
|
u.Version = u.Version + 1
|
|
},
|
|
removeAccounts: []string{ldapAcct.GetPublicId()},
|
|
resultAccounts: []string{accts[0].GetPublicId(), oidcAcct.GetPublicId()},
|
|
},
|
|
{
|
|
name: "Remove all accounts from user",
|
|
setup: func(u *iam.User) {
|
|
_, err := iamRepo.SetUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
|
[]string{accts[0].GetPublicId(), accts[1].GetPublicId()})
|
|
require.NoError(t, err)
|
|
u.Version = u.Version + 1
|
|
},
|
|
removeAccounts: []string{accts[0].GetPublicId(), accts[1].GetPublicId()},
|
|
resultAccounts: []string{},
|
|
},
|
|
{
|
|
name: "Remove empty on populated user",
|
|
setup: func(u *iam.User) {
|
|
_, err := iamRepo.SetUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
|
[]string{accts[0].GetPublicId()})
|
|
require.NoError(t, err)
|
|
u.Version = u.Version + 1
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range addCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
usr := iam.TestUser(t, iamRepo, o.GetPublicId())
|
|
defer func() {
|
|
_, err := iamRepo.DeleteUser(context.Background(), usr.GetPublicId())
|
|
require.NoError(t, err)
|
|
}()
|
|
tc.setup(usr)
|
|
req := &pbs.RemoveUserAccountsRequest{
|
|
Id: usr.GetPublicId(),
|
|
Version: usr.GetVersion(),
|
|
AccountIds: tc.removeAccounts,
|
|
}
|
|
|
|
got, err := s.RemoveUserAccounts(auth.DisabledAuthTestContext(repoFn, o.GetPublicId()), req)
|
|
if tc.wantErr {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err, "Got error: %v", err)
|
|
|
|
assert.ElementsMatch(t, got.GetItem().GetAccountIds(), tc.resultAccounts)
|
|
})
|
|
}
|
|
|
|
usr := iam.TestUser(t, iamRepo, o.GetPublicId())
|
|
|
|
failCases := []struct {
|
|
name string
|
|
req *pbs.RemoveUserAccountsRequest
|
|
err error
|
|
}{
|
|
{
|
|
name: "Bad User Id",
|
|
req: &pbs.RemoveUserAccountsRequest{
|
|
Id: "bad id",
|
|
Version: usr.GetVersion(),
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Bad account Id",
|
|
req: &pbs.RemoveUserAccountsRequest{
|
|
Id: usr.GetPublicId(),
|
|
Version: usr.GetVersion(),
|
|
AccountIds: []string{"invalid"},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
}
|
|
for _, tc := range failCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
_, gErr := s.RemoveUserAccounts(auth.DisabledAuthTestContext(repoFn, usr.GetScopeId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "AddUserAccounts(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
}
|
|
})
|
|
}
|
|
}
|