4507 lines
142 KiB
Go
4507 lines
142 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package accounts_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"
|
|
"github.com/hashicorp/boundary/internal/auth/ldap"
|
|
ldapstore "github.com/hashicorp/boundary/internal/auth/ldap/store"
|
|
"github.com/hashicorp/boundary/internal/auth/oidc"
|
|
oidcstore "github.com/hashicorp/boundary/internal/auth/oidc/store"
|
|
"github.com/hashicorp/boundary/internal/auth/password"
|
|
pwstore "github.com/hashicorp/boundary/internal/auth/password/store"
|
|
"github.com/hashicorp/boundary/internal/authtoken"
|
|
requestauth "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/accounts"
|
|
"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/types/action"
|
|
"github.com/hashicorp/boundary/internal/types/scope"
|
|
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/accounts"
|
|
scopepb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
|
|
"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/protobuf/testing/protocmp"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
)
|
|
|
|
var (
|
|
pwAuthorizedActions = []string{
|
|
action.NoOp.String(),
|
|
action.Read.String(),
|
|
action.Update.String(),
|
|
action.Delete.String(),
|
|
action.SetPassword.String(),
|
|
action.ChangePassword.String(),
|
|
}
|
|
oidcAuthorizedActions = []string{
|
|
action.NoOp.String(),
|
|
action.Read.String(),
|
|
action.Update.String(),
|
|
action.Delete.String(),
|
|
}
|
|
ldapAuthorizedActions = []string{
|
|
action.NoOp.String(),
|
|
action.Read.String(),
|
|
action.Update.String(),
|
|
action.Delete.String(),
|
|
}
|
|
)
|
|
|
|
func TestNewService(t *testing.T) {
|
|
ctx := context.TODO()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrap := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrap)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
pwRepo common.PasswordAuthRepoFactory
|
|
oidcRepo common.OidcAuthRepoFactory
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "nil-all",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "nil-pw-repo",
|
|
oidcRepo: oidcRepoFn,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "nil-oidc-repo",
|
|
pwRepo: pwRepoFn,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "success",
|
|
pwRepo: pwRepoFn,
|
|
oidcRepo: oidcRepoFn,
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := accounts.NewService(ctx, tc.pwRepo, tc.oidcRepo, ldapRepoFn, 1000)
|
|
if tc.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGet(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)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
// Use a small limit to test that membership lookup is explicitly unlimited
|
|
return oidc.NewRepository(ctx, rw, rw, kmsCache, oidc.WithLimit(1))
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
// Use a small limit to test that membership lookup is explicitly unlimited
|
|
return ldap.NewRepository(ctx, rw, rw, kmsCache, ldap.WithLimit(ctx, 1))
|
|
}
|
|
|
|
s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(t, err, "Couldn't create new auth token service.")
|
|
|
|
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
|
|
am := password.TestAuthMethods(t, conn, org.GetPublicId(), 1)[0]
|
|
pwA := password.TestAccount(t, conn, am.GetPublicId(), "name1")
|
|
|
|
pwWireAccount := pb.Account{
|
|
Id: pwA.GetPublicId(),
|
|
AuthMethodId: pwA.GetAuthMethodId(),
|
|
CreatedTime: pwA.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: pwA.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: password.Subtype.String(),
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{LoginName: pwA.GetLoginName()},
|
|
},
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
}
|
|
|
|
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
oidcAm := oidc.TestAuthMethod(
|
|
t, conn, databaseWrapper, org.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]),
|
|
)
|
|
oidcA := oidc.TestAccount(t, conn, oidcAm, "test-subject")
|
|
// Create some managed groups that will always match, so we can test that it is
|
|
// returned in results
|
|
mg := oidc.TestManagedGroup(t, conn, oidcAm, `"/token/sub" matches ".*"`)
|
|
mg2 := oidc.TestManagedGroup(t, conn, oidcAm, `"/token/sub" matches ".*"`)
|
|
oidcWireAccount := pb.Account{
|
|
Id: oidcA.GetPublicId(),
|
|
AuthMethodId: oidcA.GetAuthMethodId(),
|
|
CreatedTime: oidcA.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: oidcA.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Issuer: oidcAm.GetIssuer(),
|
|
Subject: "test-subject",
|
|
},
|
|
},
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
ManagedGroupIds: []string{mg.GetPublicId(), mg2.GetPublicId()},
|
|
}
|
|
|
|
ldapAm := ldap.TestAuthMethod(t, conn, databaseWrapper, org.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"),
|
|
)
|
|
ldapMg := ldap.TestManagedGroup(t, conn, ldapAm, []string{"admin"})
|
|
ldapMg2 := ldap.TestManagedGroup(t, conn, ldapAm, []string{"admin"})
|
|
ldapWireAccount := pb.Account{
|
|
Id: ldapAcct.GetPublicId(),
|
|
AuthMethodId: ldapAm.GetPublicId(),
|
|
CreatedTime: ldapAcct.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: ldapAcct.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: ldapAcct.GetLoginName(),
|
|
FullName: ldapAcct.GetFullName(),
|
|
Email: ldapAcct.GetEmail(),
|
|
Dn: ldapAcct.GetDn(),
|
|
MemberOfGroups: []string{"admin"},
|
|
},
|
|
},
|
|
Type: ldap.Subtype.String(),
|
|
AuthorizedActions: ldapAuthorizedActions,
|
|
ManagedGroupIds: []string{ldapMg.GetPublicId(), ldapMg2.GetPublicId()},
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.GetAccountRequest
|
|
res *pbs.GetAccountResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "Get an ldap account",
|
|
req: &pbs.GetAccountRequest{Id: ldapWireAccount.GetId()},
|
|
res: &pbs.GetAccountResponse{Item: &ldapWireAccount},
|
|
},
|
|
{
|
|
name: "Get a password account",
|
|
req: &pbs.GetAccountRequest{Id: pwWireAccount.GetId()},
|
|
res: &pbs.GetAccountResponse{Item: &pwWireAccount},
|
|
},
|
|
{
|
|
name: "Get an oidc account",
|
|
req: &pbs.GetAccountRequest{Id: oidcWireAccount.GetId()},
|
|
res: &pbs.GetAccountResponse{Item: &oidcWireAccount},
|
|
},
|
|
{
|
|
name: "Get a non existing old password account",
|
|
req: &pbs.GetAccountRequest{Id: globals.PasswordAccountPreviousPrefix + "_DoesntExis"},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Get a non existing new password account",
|
|
req: &pbs.GetAccountRequest{Id: globals.PasswordAccountPrefix + "_DoesntExis"},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Get a non existing oidc account",
|
|
req: &pbs.GetAccountRequest{Id: globals.OidcAccountPrefix + "_DoesntExis"},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Get a non existing ldap account",
|
|
req: &pbs.GetAccountRequest{Id: globals.LdapAccountPrefix + "_DoesntExis"},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Wrong id prefix",
|
|
req: &pbs.GetAccountRequest{Id: "j_1234567890"},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "space in id",
|
|
req: &pbs.GetAccountRequest{Id: globals.AuthTokenPrefix + "_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)
|
|
|
|
if globals.ResourceInfoFromPrefix(tc.req.Id).Subtype == oidc.Subtype {
|
|
// Set up managed groups before getting. First get the current
|
|
// managed groups to make sure we have the right version.
|
|
oidcRepo, err := oidcRepoFn()
|
|
require.NoError(err)
|
|
currMg, err := oidcRepo.LookupManagedGroup(ctx, mg.GetPublicId())
|
|
require.NoError(err)
|
|
currMg2, err := oidcRepo.LookupManagedGroup(ctx, mg2.GetPublicId())
|
|
require.NoError(err)
|
|
_, _, err = oidcRepo.SetManagedGroupMemberships(ctx, oidcAm, oidcA, []*oidc.ManagedGroup{currMg, currMg2})
|
|
require.NoError(err)
|
|
}
|
|
|
|
got, gErr := s.GetAccount(requestauth.DisabledAuthTestContext(iamRepoFn, org.GetPublicId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "GetAccount(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
return
|
|
}
|
|
require.NoError(gErr)
|
|
assert.Empty(cmp.Diff(
|
|
got,
|
|
tc.res,
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
), "GetAccount(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListPassword(t *testing.T) {
|
|
ctx := context.TODO()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
sqlDb, err := conn.SqlDB(ctx)
|
|
require.NoError(t, err)
|
|
wrap := db.TestWrapper(t)
|
|
kms := kms.TestKms(t, conn, wrap)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
ams := password.TestAuthMethods(t, conn, o.GetPublicId(), 3)
|
|
amNoAccounts, amSomeAccounts, amOtherAccounts := ams[0], ams[1], ams[2]
|
|
|
|
var wantSomeAccounts []*pb.Account
|
|
for _, aa := range password.TestMultipleAccounts(t, conn, amSomeAccounts.GetPublicId(), 3) {
|
|
wantSomeAccounts = append(wantSomeAccounts, &pb.Account{
|
|
Id: aa.GetPublicId(),
|
|
AuthMethodId: aa.GetAuthMethodId(),
|
|
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: "password",
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{LoginName: aa.GetLoginName()},
|
|
},
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
})
|
|
}
|
|
|
|
slices.Reverse(wantSomeAccounts)
|
|
|
|
var wantOtherAccounts []*pb.Account
|
|
for _, aa := range password.TestMultipleAccounts(t, conn, amOtherAccounts.GetPublicId(), 3) {
|
|
wantOtherAccounts = append(wantOtherAccounts, &pb.Account{
|
|
Id: aa.GetPublicId(),
|
|
AuthMethodId: aa.GetAuthMethodId(),
|
|
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: "password",
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{LoginName: aa.GetLoginName()},
|
|
},
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
})
|
|
}
|
|
|
|
slices.Reverse(wantOtherAccounts)
|
|
|
|
_, err = sqlDb.ExecContext(ctx, "analyze")
|
|
require.NoError(t, err)
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.ListAccountsRequest
|
|
res *pbs.ListAccountsResponse
|
|
err error
|
|
skipAnon bool
|
|
}{
|
|
{
|
|
name: "List Some Accounts",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: amSomeAccounts.GetPublicId()},
|
|
res: &pbs.ListAccountsResponse{
|
|
Items: wantSomeAccounts,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
EstItemCount: uint32(len(wantSomeAccounts)),
|
|
},
|
|
},
|
|
{
|
|
name: "List Other Accounts",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: amOtherAccounts.GetPublicId()},
|
|
res: &pbs.ListAccountsResponse{
|
|
Items: wantOtherAccounts,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
EstItemCount: uint32(len(wantOtherAccounts)),
|
|
},
|
|
},
|
|
{
|
|
name: "List No Accounts",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: amNoAccounts.GetPublicId()},
|
|
res: &pbs.ListAccountsResponse{
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
EstItemCount: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "Unfound Auth Method",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: globals.PasswordAuthMethodPrefix + "_DoesntExis"},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Filter Some Accounts",
|
|
req: &pbs.ListAccountsRequest{
|
|
AuthMethodId: amSomeAccounts.GetPublicId(),
|
|
Filter: fmt.Sprintf(`"/item/attributes/login_name"==%q`, wantSomeAccounts[1].GetPasswordAccountAttributes().LoginName),
|
|
},
|
|
res: &pbs.ListAccountsResponse{
|
|
Items: wantSomeAccounts[1:2],
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
EstItemCount: 1,
|
|
},
|
|
skipAnon: true,
|
|
},
|
|
{
|
|
name: "Filter All Accounts",
|
|
req: &pbs.ListAccountsRequest{
|
|
AuthMethodId: amSomeAccounts.GetPublicId(),
|
|
Filter: `"/item/id"=="noaccountmatchesthis"`,
|
|
},
|
|
res: &pbs.ListAccountsResponse{
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "Filter Bad Format",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: amSomeAccounts.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)
|
|
s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(err, "Couldn't create new user service.")
|
|
|
|
// Test non-anon first
|
|
got, gErr := s.ListAccounts(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "ListAccounts() with auth method %q got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
return
|
|
} else {
|
|
require.NoError(gErr)
|
|
}
|
|
assert.Empty(cmp.Diff(
|
|
got,
|
|
tc.res,
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
))
|
|
|
|
// Now test with anon
|
|
if tc.skipAnon {
|
|
return
|
|
}
|
|
got, gErr = s.ListAccounts(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId(), requestauth.WithUserId(globals.AnonymousUserId)), tc.req)
|
|
require.NoError(gErr)
|
|
assert.Len(got.Items, len(tc.res.Items))
|
|
for _, g := range got.GetItems() {
|
|
assert.Nil(g.Attrs)
|
|
assert.Nil(g.CreatedTime)
|
|
assert.Nil(g.UpdatedTime)
|
|
assert.Empty(g.Version)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListOidc(t *testing.T) {
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
sqlDb, err := conn.SqlDB(ctx)
|
|
require.NoError(t, err)
|
|
wrap := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrap)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
amNoAccounts := oidc.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, oidc.ActivePrivateState, "noAccounts", "fido",
|
|
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://www.noaccounts.com")[0]), oidc.WithSigningAlgs(oidc.RS256), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]))
|
|
amSomeAccounts := oidc.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, oidc.ActivePrivateState, "someAccounts", "fido",
|
|
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://www.someaccounts.com")[0]), oidc.WithSigningAlgs(oidc.RS256), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]))
|
|
amOtherAccounts := oidc.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, oidc.ActivePrivateState, "otherAccounts", "fido",
|
|
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://www.otheraccounts.com")[0]), oidc.WithSigningAlgs(oidc.RS256), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]))
|
|
|
|
var wantSomeAccounts []*pb.Account
|
|
for i := 0; i < 3; i++ {
|
|
subId := fmt.Sprintf("test-subject%d", i)
|
|
aa := oidc.TestAccount(t, conn, amSomeAccounts, subId)
|
|
wantSomeAccounts = append(wantSomeAccounts, &pb.Account{
|
|
Id: aa.GetPublicId(),
|
|
AuthMethodId: aa.GetAuthMethodId(),
|
|
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Issuer: amSomeAccounts.GetIssuer(),
|
|
Subject: subId,
|
|
},
|
|
},
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
})
|
|
}
|
|
|
|
slices.Reverse(wantSomeAccounts)
|
|
|
|
var wantOtherAccounts []*pb.Account
|
|
for i := 0; i < 3; i++ {
|
|
subId := fmt.Sprintf("test-subject%d", i)
|
|
aa := oidc.TestAccount(t, conn, amOtherAccounts, subId)
|
|
wantOtherAccounts = append(wantOtherAccounts, &pb.Account{
|
|
Id: aa.GetPublicId(),
|
|
AuthMethodId: aa.GetAuthMethodId(),
|
|
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Issuer: amOtherAccounts.GetIssuer(),
|
|
Subject: subId,
|
|
},
|
|
},
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
})
|
|
}
|
|
|
|
slices.Reverse(wantOtherAccounts)
|
|
|
|
_, err = sqlDb.ExecContext(ctx, "analyze")
|
|
require.NoError(t, err)
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.ListAccountsRequest
|
|
res *pbs.ListAccountsResponse
|
|
err error
|
|
skipAnon bool
|
|
}{
|
|
{
|
|
name: "List Some Accounts",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: amSomeAccounts.GetPublicId()},
|
|
res: &pbs.ListAccountsResponse{
|
|
Items: wantSomeAccounts,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
EstItemCount: uint32(len(wantSomeAccounts)),
|
|
},
|
|
},
|
|
{
|
|
name: "List Other Accounts",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: amOtherAccounts.GetPublicId()},
|
|
res: &pbs.ListAccountsResponse{
|
|
Items: wantOtherAccounts,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
EstItemCount: uint32(len(wantOtherAccounts)),
|
|
},
|
|
},
|
|
{
|
|
name: "List No Accounts",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: amNoAccounts.GetPublicId()},
|
|
res: &pbs.ListAccountsResponse{
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "Unfound Auth Method",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: globals.OidcAuthMethodPrefix + "_DoesntExis"},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Filter Some Accounts",
|
|
req: &pbs.ListAccountsRequest{
|
|
AuthMethodId: amSomeAccounts.GetPublicId(),
|
|
Filter: fmt.Sprintf(`"/item/attributes/subject"==%q`, wantSomeAccounts[1].GetOidcAccountAttributes().Subject),
|
|
},
|
|
res: &pbs.ListAccountsResponse{
|
|
Items: wantSomeAccounts[1:2],
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
EstItemCount: 1,
|
|
},
|
|
skipAnon: true,
|
|
},
|
|
{
|
|
name: "Filter All Accounts",
|
|
req: &pbs.ListAccountsRequest{
|
|
AuthMethodId: amSomeAccounts.GetPublicId(),
|
|
Filter: `"/item/id"=="noaccountmatchesthis"`,
|
|
},
|
|
res: &pbs.ListAccountsResponse{
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "Filter Bad Format",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: amSomeAccounts.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)
|
|
s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(err, "Couldn't create new user service.")
|
|
|
|
got, gErr := s.ListAccounts(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "ListAccounts() with auth method %q got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
return
|
|
} else {
|
|
require.NoError(gErr)
|
|
}
|
|
assert.Empty(cmp.Diff(
|
|
got,
|
|
tc.res,
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
))
|
|
|
|
// Now test with anon
|
|
if tc.skipAnon {
|
|
return
|
|
}
|
|
got, gErr = s.ListAccounts(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId(), requestauth.WithUserId(globals.AnonymousUserId)), tc.req)
|
|
require.NoError(gErr)
|
|
assert.Len(got.Items, len(tc.res.Items))
|
|
for _, g := range got.GetItems() {
|
|
assert.Nil(g.Attrs)
|
|
assert.Nil(g.CreatedTime)
|
|
assert.Nil(g.UpdatedTime)
|
|
assert.Empty(g.Version)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListLdap(t *testing.T) {
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
sqlDb, err := conn.SqlDB(ctx)
|
|
require.NoError(t, err)
|
|
wrap := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrap)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
amNoAccounts := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://no-accounts"})
|
|
amSomeAccounts := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://some-accounts"})
|
|
amOtherAccounts := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://other-accounts"})
|
|
|
|
var wantSomeAccounts []*pb.Account
|
|
for i := 0; i < 3; i++ {
|
|
loginName := fmt.Sprintf("test-login-name%d", i)
|
|
aa := ldap.TestAccount(t, conn, amSomeAccounts, loginName)
|
|
wantSomeAccounts = append(wantSomeAccounts, &pb.Account{
|
|
Id: aa.GetPublicId(),
|
|
AuthMethodId: aa.GetAuthMethodId(),
|
|
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: loginName,
|
|
},
|
|
},
|
|
AuthorizedActions: ldapAuthorizedActions,
|
|
})
|
|
}
|
|
|
|
slices.Reverse(wantSomeAccounts)
|
|
|
|
var wantOtherAccounts []*pb.Account
|
|
for i := 0; i < 3; i++ {
|
|
loginName := fmt.Sprintf("test-login-name%d", i)
|
|
aa := ldap.TestAccount(t, conn, amOtherAccounts, loginName)
|
|
wantOtherAccounts = append(wantOtherAccounts, &pb.Account{
|
|
Id: aa.GetPublicId(),
|
|
AuthMethodId: aa.GetAuthMethodId(),
|
|
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: loginName,
|
|
},
|
|
},
|
|
AuthorizedActions: ldapAuthorizedActions,
|
|
})
|
|
}
|
|
|
|
slices.Reverse(wantOtherAccounts)
|
|
|
|
_, err = sqlDb.ExecContext(ctx, "analyze")
|
|
require.NoError(t, err)
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.ListAccountsRequest
|
|
res *pbs.ListAccountsResponse
|
|
err error
|
|
skipAnon bool
|
|
}{
|
|
{
|
|
name: "List Some Accounts",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: amSomeAccounts.GetPublicId()},
|
|
res: &pbs.ListAccountsResponse{
|
|
Items: wantSomeAccounts,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
EstItemCount: uint32(len(wantSomeAccounts)),
|
|
},
|
|
},
|
|
{
|
|
name: "List Other Accounts",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: amOtherAccounts.GetPublicId()},
|
|
res: &pbs.ListAccountsResponse{
|
|
Items: wantOtherAccounts,
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
EstItemCount: uint32(len(wantOtherAccounts)),
|
|
},
|
|
},
|
|
{
|
|
name: "List No Accounts",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: amNoAccounts.GetPublicId()},
|
|
res: &pbs.ListAccountsResponse{
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "Unfound Auth Method",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: globals.OidcAuthMethodPrefix + "_DoesntExis"},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Filter Some Accounts",
|
|
req: &pbs.ListAccountsRequest{
|
|
AuthMethodId: amSomeAccounts.GetPublicId(),
|
|
Filter: fmt.Sprintf(`"/item/attributes/login_name"==%q`, wantSomeAccounts[1].GetLdapAccountAttributes().LoginName),
|
|
},
|
|
res: &pbs.ListAccountsResponse{
|
|
Items: wantSomeAccounts[1:2],
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
EstItemCount: 1,
|
|
},
|
|
skipAnon: true,
|
|
},
|
|
{
|
|
name: "Filter All Accounts",
|
|
req: &pbs.ListAccountsRequest{
|
|
AuthMethodId: amSomeAccounts.GetPublicId(),
|
|
Filter: `"/item/id"=="noaccountmatchesthis"`,
|
|
},
|
|
res: &pbs.ListAccountsResponse{
|
|
ResponseType: "complete",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
},
|
|
},
|
|
{
|
|
name: "Filter Bad Format",
|
|
req: &pbs.ListAccountsRequest{AuthMethodId: amSomeAccounts.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)
|
|
s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(err, "Couldn't create new user service.")
|
|
|
|
got, gErr := s.ListAccounts(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "ListAccounts() with auth method %q got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
return
|
|
} else {
|
|
require.NoError(gErr)
|
|
}
|
|
assert.Empty(cmp.Diff(
|
|
got,
|
|
tc.res,
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
))
|
|
|
|
// Now test with anon
|
|
if tc.skipAnon {
|
|
return
|
|
}
|
|
got, gErr = s.ListAccounts(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId(), requestauth.WithUserId(globals.AnonymousUserId)), tc.req)
|
|
require.NoError(gErr)
|
|
assert.Len(got.Items, len(tc.res.Items))
|
|
for _, g := range got.GetItems() {
|
|
assert.Nil(g.Attrs)
|
|
assert.Nil(g.CreatedTime)
|
|
assert.Nil(g.UpdatedTime)
|
|
assert.Empty(g.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)
|
|
rw := db.New(conn)
|
|
wrap := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrap)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
tokenRepoFn := func() (*authtoken.Repository, error) {
|
|
return authtoken.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
serversRepoFn := func() (*server.Repository, error) {
|
|
return server.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
|
|
s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(t, err)
|
|
|
|
iamRepo := iam.TestRepo(t, conn, wrap)
|
|
tokenRepo, _ := tokenRepoFn()
|
|
pwRepo, _ := pwRepoFn()
|
|
oidcRepo, _ := oidcRepoFn()
|
|
ldapRepo, _ := ldapRepoFn()
|
|
o, pwt := iam.TestScopes(t, iamRepo)
|
|
|
|
t.Run("password", func(t *testing.T) {
|
|
authMethod := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
|
|
|
|
var accounts []*pb.Account
|
|
for _, aa := range password.TestMultipleAccounts(t, conn, authMethod.GetPublicId(), 9) {
|
|
accounts = append(accounts, &pb.Account{
|
|
Id: aa.GetPublicId(),
|
|
AuthMethodId: aa.GetAuthMethodId(),
|
|
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: "password",
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{LoginName: aa.GetLoginName()},
|
|
},
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
})
|
|
}
|
|
|
|
acct := password.TestAccount(t, conn, authMethod.GetPublicId(), "test-login-last")
|
|
u := iam.TestUser(t, iamRepo, o.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, o.GetPublicId())
|
|
iam.TestRoleGrant(t, conn, privOrgRole.GetPublicId(), "ids=*;type=*;actions=*")
|
|
iam.TestUserRole(t, conn, privOrgRole.GetPublicId(), u.GetPublicId())
|
|
|
|
accounts = append(accounts, &pb.Account{
|
|
Id: acct.GetPublicId(),
|
|
AuthMethodId: acct.GetAuthMethodId(),
|
|
CreatedTime: acct.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: acct.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: "password",
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{LoginName: acct.GetLoginName()},
|
|
},
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
})
|
|
|
|
// Since we sort by created_time descending, we reverse the slice
|
|
slices.Reverse(accounts)
|
|
|
|
at, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
|
|
|
requestInfo := authpb.RequestInfo{
|
|
TokenFormat: uint32(requestauth.AuthTokenTypeBearer),
|
|
PublicId: at.GetPublicId(),
|
|
Token: at.GetToken(),
|
|
}
|
|
requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
|
ctx = requestauth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kmsCache, &requestInfo)
|
|
|
|
req := &pbs.ListAccountsRequest{
|
|
AuthMethodId: authMethod.GetPublicId(),
|
|
Filter: "",
|
|
ListToken: "",
|
|
PageSize: 2,
|
|
}
|
|
|
|
// Run analyze in the DB to update the estimate tables
|
|
_, err = sqlDB.ExecContext(ctx, "analyze")
|
|
require.NoError(t, err)
|
|
|
|
got, err := s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 2)
|
|
|
|
// all comparisons will be done without list token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: accounts[0:2],
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// second page
|
|
req.ListToken = got.ListToken
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 2)
|
|
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: accounts[2:4],
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// remainder of results
|
|
req.ListToken = got.ListToken
|
|
req.PageSize = 6
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 6)
|
|
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: accounts[4:],
|
|
ResponseType: "complete",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// create another acct
|
|
aa := password.TestAccount(t, conn, authMethod.GetPublicId(), "test-login-new-last")
|
|
newAccount := &pb.Account{
|
|
Id: aa.GetPublicId(),
|
|
AuthMethodId: aa.GetAuthMethodId(),
|
|
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: "password",
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{LoginName: aa.GetLoginName()},
|
|
},
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
}
|
|
// Append new account to the front of the slice, since it's the most recently updated
|
|
accounts = append([]*pb.Account{newAccount}, accounts...)
|
|
|
|
// delete different acct
|
|
_, err = pwRepo.DeleteAccount(ctx, o.GetPublicId(), accounts[len(accounts)-1].Id)
|
|
require.NoError(t, err)
|
|
deletedAccount := accounts[len(accounts)-1]
|
|
accounts = accounts[:len(accounts)-1]
|
|
|
|
// update another acct
|
|
accounts[1].Name = wrapperspb.String("new-name")
|
|
accounts[1].Version = 2
|
|
a := &password.Account{
|
|
Account: &pwstore.Account{
|
|
PublicId: accounts[1].Id,
|
|
AuthMethodId: accounts[1].AuthMethodId,
|
|
Name: accounts[1].Name.GetValue(),
|
|
},
|
|
}
|
|
ua, _, err := pwRepo.UpdateAccount(ctx, o.GetPublicId(), a, 1, []string{"name"})
|
|
require.NoError(t, err)
|
|
accounts[1].UpdatedTime = ua.GetUpdateTime().GetTimestamp()
|
|
accounts[1].Version = ua.GetVersion()
|
|
// Add to the front since it's most recently updated
|
|
accounts = append(
|
|
[]*pb.Account{accounts[1]},
|
|
append(
|
|
[]*pb.Account{accounts[0]},
|
|
accounts[2:]...,
|
|
)...,
|
|
)
|
|
|
|
// 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 = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: []*pb.Account{accounts[0]},
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "updated_time",
|
|
SortDir: "desc",
|
|
RemovedIds: []string{deletedAccount.Id},
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Get the next page
|
|
req.ListToken = got.ListToken
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: []*pb.Account{accounts[1]},
|
|
ResponseType: "complete",
|
|
ListToken: "",
|
|
SortBy: "updated_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Request new page with filter requiring looping
|
|
// to fill the page.
|
|
req.ListToken = ""
|
|
req.Filter = fmt.Sprintf(`"/item/id"==%q or "/item/id"==%q`, accounts[len(accounts)-2].Id, accounts[len(accounts)-1].Id)
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: []*pb.Account{accounts[len(accounts)-2]},
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
// Should be empty again
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
req.ListToken = got.ListToken
|
|
// Get the second page
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: []*pb.Account{accounts[len(accounts)-1]},
|
|
ResponseType: "complete",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Create unauthenticated user
|
|
unauthAt := authtoken.TestAuthToken(t, conn, kmsCache, o.GetPublicId())
|
|
unauthR := iam.TestRole(t, conn, pwt.GetPublicId())
|
|
_ = iam.TestUserRole(t, conn, unauthR.GetPublicId(), unauthAt.GetIamUserId())
|
|
|
|
// Make a request with the unauthenticated user,
|
|
// ensure the response is 403 forbidden.
|
|
requestInfo = authpb.RequestInfo{
|
|
TokenFormat: uint32(requestauth.AuthTokenTypeBearer),
|
|
PublicId: unauthAt.GetPublicId(),
|
|
Token: unauthAt.GetToken(),
|
|
}
|
|
requestContext = context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
|
ctx = requestauth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kmsCache, &requestInfo)
|
|
|
|
_, err = s.ListAccounts(ctx, &pbs.ListAccountsRequest{
|
|
AuthMethodId: authMethod.GetPublicId(),
|
|
})
|
|
require.Error(t, err)
|
|
assert.Equal(t, handlers.ForbiddenError(), err)
|
|
})
|
|
|
|
t.Run("oidc", func(t *testing.T) {
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
authMethod := oidc.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, oidc.ActivePrivateState, "someAccounts", "fido",
|
|
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://www.someaccounts.com")[0]), oidc.WithSigningAlgs(oidc.RS256), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]))
|
|
|
|
var accounts []*pb.Account
|
|
for i := 0; i < 9; i++ {
|
|
subId := fmt.Sprintf("test-subject%d", i)
|
|
aa := oidc.TestAccount(t, conn, authMethod, subId)
|
|
accounts = append(accounts, &pb.Account{
|
|
Id: aa.GetPublicId(),
|
|
AuthMethodId: aa.GetAuthMethodId(),
|
|
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Issuer: authMethod.GetIssuer(),
|
|
Subject: subId,
|
|
},
|
|
},
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
})
|
|
}
|
|
|
|
acct := oidc.TestAccount(t, conn, authMethod, "test-login-last")
|
|
u := iam.TestUser(t, iamRepo, o.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, o.GetPublicId())
|
|
iam.TestRoleGrant(t, conn, privOrgRole.GetPublicId(), "ids=*;type=*;actions=*")
|
|
iam.TestUserRole(t, conn, privOrgRole.GetPublicId(), u.GetPublicId())
|
|
|
|
accounts = append(accounts, &pb.Account{
|
|
Id: acct.GetPublicId(),
|
|
AuthMethodId: acct.GetAuthMethodId(),
|
|
CreatedTime: acct.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: acct.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Issuer: authMethod.GetIssuer(),
|
|
Subject: "test-login-last",
|
|
},
|
|
},
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
})
|
|
|
|
// Since we sort by created_time descending, we reverse the slice
|
|
slices.Reverse(accounts)
|
|
|
|
at, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
|
|
|
requestInfo := authpb.RequestInfo{
|
|
TokenFormat: uint32(requestauth.AuthTokenTypeBearer),
|
|
PublicId: at.GetPublicId(),
|
|
Token: at.GetToken(),
|
|
}
|
|
requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
|
ctx = requestauth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kmsCache, &requestInfo)
|
|
|
|
req := &pbs.ListAccountsRequest{
|
|
AuthMethodId: authMethod.GetPublicId(),
|
|
Filter: "",
|
|
ListToken: "",
|
|
PageSize: 2,
|
|
}
|
|
|
|
// Run analyze in the DB to update the estimate tables
|
|
_, err = sqlDB.ExecContext(ctx, "analyze")
|
|
require.NoError(t, err)
|
|
|
|
got, err := s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 2)
|
|
|
|
// all comparisons will be done without list token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: accounts[0:2],
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// second page
|
|
req.ListToken = got.ListToken
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 2)
|
|
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: accounts[2:4],
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// remainder of results
|
|
req.ListToken = got.ListToken
|
|
req.PageSize = 6
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 6)
|
|
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: accounts[4:],
|
|
ResponseType: "complete",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// create another acct
|
|
aa := oidc.TestAccount(t, conn, authMethod, "test-login-new-last")
|
|
newAccount := &pb.Account{
|
|
Id: aa.GetPublicId(),
|
|
AuthMethodId: aa.GetAuthMethodId(),
|
|
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Issuer: authMethod.GetIssuer(),
|
|
Subject: "test-login-new-last",
|
|
},
|
|
},
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
}
|
|
// Add to front since it's the latest updated
|
|
accounts = append([]*pb.Account{newAccount}, accounts...)
|
|
|
|
// delete different acct
|
|
_, err = oidcRepo.DeleteAccount(ctx, o.GetPublicId(), accounts[len(accounts)-1].Id)
|
|
require.NoError(t, err)
|
|
deletedAccount := accounts[len(accounts)-1]
|
|
accounts = accounts[:len(accounts)-1]
|
|
|
|
// update another acct
|
|
accounts[1].Name = wrapperspb.String("new-name")
|
|
accounts[1].Version = 2
|
|
a := &oidc.Account{
|
|
Account: &oidcstore.Account{
|
|
PublicId: accounts[1].Id,
|
|
AuthMethodId: accounts[1].AuthMethodId,
|
|
Name: accounts[1].Name.GetValue(),
|
|
},
|
|
}
|
|
ua, _, err := oidcRepo.UpdateAccount(ctx, o.GetPublicId(), a, 1, []string{"name"})
|
|
require.NoError(t, err)
|
|
accounts[1].UpdatedTime = ua.GetUpdateTime().GetTimestamp()
|
|
accounts[1].Version = ua.GetVersion()
|
|
// Add to the front since it's most recently updated
|
|
accounts = append(
|
|
[]*pb.Account{accounts[1]},
|
|
append(
|
|
[]*pb.Account{accounts[0]},
|
|
accounts[2:]...,
|
|
)...,
|
|
)
|
|
|
|
// 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 = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: []*pb.Account{accounts[0]},
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "updated_time",
|
|
SortDir: "desc",
|
|
RemovedIds: []string{deletedAccount.Id},
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Get next page
|
|
req.ListToken = got.ListToken
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: []*pb.Account{accounts[1]},
|
|
ResponseType: "complete",
|
|
ListToken: "",
|
|
SortBy: "updated_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "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`, accounts[len(accounts)-2].Id, accounts[len(accounts)-1].Id)
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: []*pb.Account{accounts[len(accounts)-2]},
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
// Should be empty again
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
req.ListToken = got.ListToken
|
|
// Get the second page
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: []*pb.Account{accounts[len(accounts)-1]},
|
|
ResponseType: "complete",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Create unauthenticated user
|
|
unauthAt := authtoken.TestAuthToken(t, conn, kmsCache, o.GetPublicId())
|
|
unauthR := iam.TestRole(t, conn, pwt.GetPublicId())
|
|
_ = iam.TestUserRole(t, conn, unauthR.GetPublicId(), unauthAt.GetIamUserId())
|
|
|
|
// Make a request with the unauthenticated user,
|
|
// ensure the response is 403 forbidden.
|
|
requestInfo = authpb.RequestInfo{
|
|
TokenFormat: uint32(requestauth.AuthTokenTypeBearer),
|
|
PublicId: unauthAt.GetPublicId(),
|
|
Token: unauthAt.GetToken(),
|
|
}
|
|
requestContext = context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
|
ctx = requestauth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kmsCache, &requestInfo)
|
|
|
|
_, err = s.ListAccounts(ctx, &pbs.ListAccountsRequest{
|
|
AuthMethodId: authMethod.GetPublicId(),
|
|
})
|
|
require.Error(t, err)
|
|
assert.Equal(t, handlers.ForbiddenError(), err)
|
|
})
|
|
|
|
t.Run("ldap", func(t *testing.T) {
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
authMethod := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://some-accounts"})
|
|
|
|
var accounts []*pb.Account
|
|
for i := 0; i < 9; i++ {
|
|
loginName := fmt.Sprintf("test-login-name%d", i)
|
|
aa := ldap.TestAccount(t, conn, authMethod, loginName)
|
|
accounts = append(accounts, &pb.Account{
|
|
Id: aa.GetPublicId(),
|
|
AuthMethodId: aa.GetAuthMethodId(),
|
|
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: loginName,
|
|
},
|
|
},
|
|
AuthorizedActions: ldapAuthorizedActions,
|
|
})
|
|
}
|
|
|
|
acct := ldap.TestAccount(t, conn, authMethod, "test-login-last")
|
|
u := iam.TestUser(t, iamRepo, o.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, o.GetPublicId())
|
|
iam.TestRoleGrant(t, conn, privOrgRole.GetPublicId(), "ids=*;type=*;actions=*")
|
|
iam.TestUserRole(t, conn, privOrgRole.GetPublicId(), u.GetPublicId())
|
|
|
|
accounts = append(accounts, &pb.Account{
|
|
Id: acct.GetPublicId(),
|
|
AuthMethodId: acct.GetAuthMethodId(),
|
|
CreatedTime: acct.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: acct.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "test-login-last",
|
|
},
|
|
},
|
|
AuthorizedActions: ldapAuthorizedActions,
|
|
})
|
|
|
|
// Since we sort by created_time descending, we reverse the slice
|
|
slices.Reverse(accounts)
|
|
|
|
at, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
|
|
|
requestInfo := authpb.RequestInfo{
|
|
TokenFormat: uint32(requestauth.AuthTokenTypeBearer),
|
|
PublicId: at.GetPublicId(),
|
|
Token: at.GetToken(),
|
|
}
|
|
requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
|
ctx = requestauth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kmsCache, &requestInfo)
|
|
|
|
req := &pbs.ListAccountsRequest{
|
|
AuthMethodId: authMethod.GetPublicId(),
|
|
Filter: "",
|
|
ListToken: "",
|
|
PageSize: 2,
|
|
}
|
|
|
|
// Run analyze in the DB to update the estimate tables
|
|
_, err = sqlDB.ExecContext(ctx, "analyze")
|
|
require.NoError(t, err)
|
|
|
|
got, err := s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 2)
|
|
|
|
// all comparisons will be done without list token
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: accounts[0:2],
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// second page
|
|
req.ListToken = got.ListToken
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 2)
|
|
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: accounts[2:4],
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// remainder of results
|
|
req.ListToken = got.ListToken
|
|
req.PageSize = 6
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 6)
|
|
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: accounts[4:],
|
|
ResponseType: "complete",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// create another acct
|
|
aa := ldap.TestAccount(t, conn, authMethod, "test-login-new-last")
|
|
newAccount := &pb.Account{
|
|
Id: aa.GetPublicId(),
|
|
AuthMethodId: aa.GetAuthMethodId(),
|
|
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
|
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "test-login-new-last",
|
|
},
|
|
},
|
|
AuthorizedActions: ldapAuthorizedActions,
|
|
}
|
|
// Add to front since it's the latest updated
|
|
accounts = append([]*pb.Account{newAccount}, accounts...)
|
|
|
|
// delete different acct
|
|
_, err = ldapRepo.DeleteAccount(ctx, accounts[len(accounts)-1].Id)
|
|
require.NoError(t, err)
|
|
deletedAccount := accounts[len(accounts)-1]
|
|
accounts = accounts[:len(accounts)-1]
|
|
|
|
// update another acct
|
|
accounts[1].Name = wrapperspb.String("new-name")
|
|
accounts[1].Version = 2
|
|
a := &ldap.Account{
|
|
Account: &ldapstore.Account{
|
|
PublicId: accounts[1].Id,
|
|
AuthMethodId: accounts[1].AuthMethodId,
|
|
Name: accounts[1].Name.GetValue(),
|
|
ScopeId: accounts[1].Scope.Id,
|
|
},
|
|
}
|
|
ua, _, err := ldapRepo.UpdateAccount(ctx, o.GetPublicId(), a, 1, []string{"name"})
|
|
require.NoError(t, err)
|
|
accounts[1].UpdatedTime = ua.GetUpdateTime().GetTimestamp()
|
|
accounts[1].Version = ua.GetVersion()
|
|
// Add to the front since it's most recently updated
|
|
accounts = append(
|
|
[]*pb.Account{accounts[1]},
|
|
append(
|
|
[]*pb.Account{accounts[0]},
|
|
accounts[2:]...,
|
|
)...,
|
|
)
|
|
|
|
// 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 = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: []*pb.Account{accounts[0]},
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "updated_time",
|
|
SortDir: "desc",
|
|
RemovedIds: []string{deletedAccount.Id},
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Get next page
|
|
req.ListToken = got.ListToken
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: []*pb.Account{accounts[1]},
|
|
ResponseType: "complete",
|
|
ListToken: "",
|
|
SortBy: "updated_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "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`, accounts[len(accounts)-2].Id, accounts[len(accounts)-1].Id)
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: []*pb.Account{accounts[len(accounts)-2]},
|
|
ResponseType: "delta",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
// Should be empty again
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
req.ListToken = got.ListToken
|
|
// Get the second page
|
|
got, err = s.ListAccounts(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.GetItems(), 1)
|
|
assert.Empty(t,
|
|
cmp.Diff(
|
|
got,
|
|
&pbs.ListAccountsResponse{
|
|
Items: []*pb.Account{accounts[len(accounts)-1]},
|
|
ResponseType: "complete",
|
|
ListToken: "",
|
|
SortBy: "created_time",
|
|
SortDir: "desc",
|
|
RemovedIds: nil,
|
|
EstItemCount: 10,
|
|
},
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
protocmp.Transform(),
|
|
protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"),
|
|
),
|
|
)
|
|
|
|
// Create unauthenticated user
|
|
unauthAt := authtoken.TestAuthToken(t, conn, kmsCache, o.GetPublicId())
|
|
unauthR := iam.TestRole(t, conn, pwt.GetPublicId())
|
|
_ = iam.TestUserRole(t, conn, unauthR.GetPublicId(), unauthAt.GetIamUserId())
|
|
|
|
// Make a request with the unauthenticated user,
|
|
// ensure the response is 403 forbidden.
|
|
requestInfo = authpb.RequestInfo{
|
|
TokenFormat: uint32(requestauth.AuthTokenTypeBearer),
|
|
PublicId: unauthAt.GetPublicId(),
|
|
Token: unauthAt.GetToken(),
|
|
}
|
|
requestContext = context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
|
ctx = requestauth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kmsCache, &requestInfo)
|
|
|
|
_, err = s.ListAccounts(ctx, &pbs.ListAccountsRequest{
|
|
AuthMethodId: authMethod.GetPublicId(),
|
|
})
|
|
require.Error(t, err)
|
|
assert.Equal(t, handlers.ForbiddenError(), err)
|
|
})
|
|
}
|
|
|
|
func TestDelete(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)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
am1 := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
|
|
ac := password.TestAccount(t, conn, am1.GetPublicId(), "name1")
|
|
|
|
databaseWrapper, err := kmsCache.GetWrapper(ctx, 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]),
|
|
)
|
|
oidcA := 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-account")
|
|
|
|
s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new user service.")
|
|
|
|
cases := []struct {
|
|
name string
|
|
scope string
|
|
req *pbs.DeleteAccountRequest
|
|
res *pbs.DeleteAccountResponse
|
|
err error
|
|
errContains string
|
|
}{
|
|
{
|
|
name: "Delete an existing pw account",
|
|
req: &pbs.DeleteAccountRequest{
|
|
Id: ac.GetPublicId(),
|
|
},
|
|
},
|
|
{
|
|
name: "Delete an existing oidc account",
|
|
req: &pbs.DeleteAccountRequest{
|
|
Id: oidcA.GetPublicId(),
|
|
},
|
|
},
|
|
{
|
|
name: "Delete an existing ldap account",
|
|
req: &pbs.DeleteAccountRequest{
|
|
Id: ldapAcct.GetPublicId(),
|
|
},
|
|
},
|
|
{
|
|
name: "Delete bad old pw account id",
|
|
req: &pbs.DeleteAccountRequest{
|
|
Id: globals.PasswordAccountPreviousPrefix + "_doesntexis",
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
errContains: "Resource not found.",
|
|
},
|
|
{
|
|
name: "Delete bad new pw account id",
|
|
req: &pbs.DeleteAccountRequest{
|
|
Id: globals.PasswordAccountPrefix + "_doesntexis",
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
errContains: "Resource not found.",
|
|
},
|
|
{
|
|
name: "Delete bad oidc account id",
|
|
req: &pbs.DeleteAccountRequest{
|
|
Id: globals.OidcAccountPrefix + "_doesntexis",
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
errContains: "Resource not found.",
|
|
},
|
|
{
|
|
name: "Delete bad ldap account id",
|
|
req: &pbs.DeleteAccountRequest{
|
|
Id: globals.LdapAccountPrefix + "_doesntexis",
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
errContains: "Resource not found.",
|
|
},
|
|
{
|
|
name: "Bad account id formatting",
|
|
req: &pbs.DeleteAccountRequest{
|
|
Id: "bad_format",
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "Incorrectly formatted identifier.",
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
got, gErr := s.DeleteAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "DeleteAccount(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
require.NotEmpty(tc.errContains)
|
|
assert.Contains(gErr.Error(), tc.errContains)
|
|
}
|
|
assert.EqualValuesf(tc.res, got, "DeleteAccount(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDelete_twice(t *testing.T) {
|
|
ctx := context.TODO()
|
|
assert, require := assert.New(t), require.New(t)
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
wrap := db.TestWrapper(t)
|
|
rw := db.New(conn)
|
|
kms := kms.TestKms(t, conn, wrap)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
|
|
ac := password.TestAccount(t, conn, am.GetPublicId(), "name1")
|
|
|
|
s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(err, "Error when getting new user service")
|
|
req := &pbs.DeleteAccountRequest{
|
|
Id: ac.GetPublicId(),
|
|
}
|
|
_, gErr := s.DeleteAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), req)
|
|
assert.NoError(gErr, "First attempt")
|
|
_, gErr = s.DeleteAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), req)
|
|
assert.Error(gErr, "Second attempt")
|
|
assert.True(errors.Is(gErr, handlers.ApiErrorWithCode(codes.NotFound)), "Expected not found for the second delete.")
|
|
}
|
|
|
|
func TestCreatePassword(t *testing.T) {
|
|
ctx := context.TODO()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrap := db.TestWrapper(t)
|
|
kms := kms.TestKms(t, conn, wrap)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
|
|
s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new account service.")
|
|
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
|
|
defaultAccount := password.TestAccount(t, conn, am.GetPublicId(), "name1")
|
|
defaultCreated := defaultAccount.GetCreateTime().GetTimestamp()
|
|
require.NoError(t, err, "Error converting proto to timestamp.")
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.CreateAccountRequest
|
|
res *pbs.CreateAccountResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "Create a valid Account",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
|
Name: &wrapperspb.StringValue{Value: "name"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: "password",
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "validaccount",
|
|
Password: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: &pbs.CreateAccountResponse{
|
|
Uri: fmt.Sprintf("accounts/%s_", globals.PasswordAccountPrefix),
|
|
Item: &pb.Account{
|
|
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
|
Name: &wrapperspb.StringValue{Value: "name"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: "password",
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "validaccount",
|
|
Password: nil,
|
|
},
|
|
},
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Create a valid Account without type defined",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "notypedefined",
|
|
Password: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: &pbs.CreateAccountResponse{
|
|
Uri: fmt.Sprintf("accounts/%s_", globals.PasswordAccountPrefix),
|
|
Item: &pb.Account{
|
|
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: "password",
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "notypedefined",
|
|
Password: nil,
|
|
},
|
|
},
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Create a valid Account with password defined",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
|
Name: &wrapperspb.StringValue{Value: "name_with_password"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "haspassword",
|
|
Password: &wrapperspb.StringValue{Value: "somepassword"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: &pbs.CreateAccountResponse{
|
|
Uri: fmt.Sprintf("accounts/%s_", globals.PasswordAccountPrefix),
|
|
Item: &pb.Account{
|
|
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
|
Name: &wrapperspb.StringValue{Value: "name_with_password"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: "password",
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "haspassword",
|
|
Password: nil,
|
|
},
|
|
},
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Cant specify mismatching type",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
|
Type: "wrong",
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "nopwprovided",
|
|
Password: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Can't specify Id",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
|
Id: globals.PasswordAccountPrefix + "_notallowed",
|
|
Type: "password",
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "cantprovideid",
|
|
Password: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Can't specify Created Time",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
|
CreatedTime: timestamppb.Now(),
|
|
Type: "password",
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "nocreatedtime",
|
|
Password: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Can't specify Update Time",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
|
UpdatedTime: timestamppb.Now(),
|
|
Type: "password",
|
|
Attrs: &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "noupdatetime",
|
|
Password: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Must specify login name for password type",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
|
Type: "password",
|
|
},
|
|
},
|
|
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)
|
|
got, gErr := s.CreateAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "CreateAccount(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
} else {
|
|
require.NoError(gErr)
|
|
}
|
|
if got != nil {
|
|
assert.Contains(got.GetUri(), tc.res.Uri)
|
|
assert.True(strings.HasPrefix(got.GetItem().GetId(), globals.PasswordAccountPrefix+"_"))
|
|
gotCreateTime := got.GetItem().GetCreatedTime()
|
|
require.NoError(err, "Error converting proto to timestamp.")
|
|
gotUpdateTime := got.GetItem().GetUpdatedTime()
|
|
require.NoError(err, "Error converting proto to timestamp.")
|
|
// Verify it is a user created after the test setup's default user
|
|
assert.True(gotCreateTime.AsTime().After(defaultCreated.AsTime()), "New account should have been created after default user. Was created %v, which is after %v", gotCreateTime, defaultCreated)
|
|
assert.True(gotUpdateTime.AsTime().After(defaultCreated.AsTime()), "New account 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
|
|
}),
|
|
), "CreateAccount(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateOidc(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)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
|
|
s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new account service.")
|
|
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
am := 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]),
|
|
)
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.CreateAccountRequest
|
|
res *pbs.CreateAccountResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "Create a valid Account",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "name"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Subject: "valid-account",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: &pbs.CreateAccountResponse{
|
|
Uri: fmt.Sprintf("accounts/%s_", globals.OidcAccountPrefix),
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "name"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Subject: "valid-account",
|
|
Issuer: am.GetIssuer(),
|
|
},
|
|
},
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Create a valid Account without type defined",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Subject: "no type defined",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: &pbs.CreateAccountResponse{
|
|
Uri: fmt.Sprintf("accounts/%s_", globals.OidcAccountPrefix),
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Subject: "no type defined",
|
|
Issuer: am.GetIssuer(),
|
|
},
|
|
},
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Create Account With Overwritten Issuer",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "overwritten issuer"},
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Subject: "overwritten-issuer",
|
|
Issuer: "https://overwrite.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: &pbs.CreateAccountResponse{
|
|
Uri: fmt.Sprintf("accounts/%s_", globals.OidcAccountPrefix),
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "overwritten issuer"},
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Subject: "overwritten-issuer",
|
|
Issuer: "https://overwrite.com",
|
|
},
|
|
},
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Cant specify mismatching type",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Type: password.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Subject: "cant-specify-mismatching-type",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Can't specify Id",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Id: globals.OidcAccountPrefix + "_notallowed",
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Subject: "cant-specify-id",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Can't specify Created Time",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
CreatedTime: timestamppb.Now(),
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Subject: "cant-specify-created-time",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Can't specify Update Time",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
UpdatedTime: timestamppb.Now(),
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Subject: "cant-specify-update-time",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Must specify subject",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Type: oidc.Subtype.String(),
|
|
},
|
|
},
|
|
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)
|
|
got, gErr := s.CreateAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "CreateAccount(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
return
|
|
}
|
|
require.NoError(gErr)
|
|
if got != nil {
|
|
assert.Contains(got.GetUri(), tc.res.Uri)
|
|
assert.True(strings.HasPrefix(got.GetItem().GetId(), globals.OidcAccountPrefix+"_"))
|
|
// 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
|
|
}),
|
|
), "CreateAccount(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateLdap(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)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
|
|
s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new account service.")
|
|
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
am := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://ldap1"})
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.CreateAccountRequest
|
|
res *pbs.CreateAccountResponse
|
|
err error
|
|
errContains string
|
|
}{
|
|
{
|
|
name: "Create a valid Account",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "name"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "valid-account",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: &pbs.CreateAccountResponse{
|
|
Uri: fmt.Sprintf("accounts/%s_", globals.LdapAccountPrefix),
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "name"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "valid-account",
|
|
},
|
|
},
|
|
AuthorizedActions: ldapAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Create a valid Account without type defined",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "no type defined",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: &pbs.CreateAccountResponse{
|
|
Uri: fmt.Sprintf("accounts/%s_", globals.LdapAccountPrefix),
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
|
Version: 1,
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "no type defined",
|
|
},
|
|
},
|
|
AuthorizedActions: ldapAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Cant specify mismatching type",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Type: password.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "cant-specify-mismatching-type",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "Doesn't match the parent resource's type",
|
|
},
|
|
{
|
|
name: "Can't specify Id",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Id: globals.LdapAccountPrefix + "_notallowed",
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "cant-specify-mismatching-type",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "name: \"id\", desc: \"This is a read only field.\"",
|
|
},
|
|
{
|
|
name: "Can't specify Created Time",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
CreatedTime: timestamppb.Now(),
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "cant-specify-mismatching-type",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "name: \"created_time\", desc: \"This is a read only field.\"",
|
|
},
|
|
{
|
|
name: "Can't specify Update Time",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
UpdatedTime: timestamppb.Now(),
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "cant-specify-mismatching-type",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "name: \"updated_time\", desc: \"This is a read only field.\"",
|
|
},
|
|
{
|
|
name: "Must specify login name",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "name: \"attributes.login_name\", desc: \"This is a required field for this type.",
|
|
},
|
|
{
|
|
name: "Can't specify full name",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "cant-specify-full-name",
|
|
FullName: "something",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "name: \"attributes.full_name\", desc: \"This is a read only field.\"",
|
|
},
|
|
{
|
|
name: "Can't specify email",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "cant-specify-email",
|
|
Email: "something",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "name: \"attributes.email\", desc: \"This is a read only field.\"",
|
|
},
|
|
{
|
|
name: "Can't specify dn",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "cant-specify-dn",
|
|
Dn: "something",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "name: \"attributes.dn\", desc: \"This is a read only field.\"",
|
|
},
|
|
{
|
|
name: "Can't specify member of groups",
|
|
req: &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "cant-specify-member-of",
|
|
MemberOfGroups: []string{"something"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "name: \"attributes.member_of_groups\", desc: \"This is a read only field.\"",
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
got, gErr := s.CreateAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "CreateAccount(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
require.NotEmpty(tc.errContains)
|
|
assert.Contains(gErr.Error(), tc.errContains)
|
|
return
|
|
}
|
|
require.NoError(gErr)
|
|
if got != nil {
|
|
assert.Contains(got.GetUri(), tc.res.Uri)
|
|
assert.True(strings.HasPrefix(got.GetItem().GetId(), globals.LdapAccountPrefix+"_"))
|
|
// 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
|
|
}),
|
|
), "CreateAccount(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdatePassword(t *testing.T) {
|
|
ctx := context.TODO()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrap := db.TestWrapper(t)
|
|
kms := kms.TestKms(t, conn, wrap)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
|
|
tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new accounts service.")
|
|
|
|
defaultScopeInfo := &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: o.GetType(), ParentScopeId: scope.Global.String()}
|
|
defaultAttributes := &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "default",
|
|
},
|
|
}
|
|
modifiedAttributes := &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "modified",
|
|
},
|
|
}
|
|
|
|
freshAccount := func(t *testing.T) (*pb.Account, func()) {
|
|
t.Helper()
|
|
acc, err := tested.CreateAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()),
|
|
&pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: wrapperspb.String("default"),
|
|
Description: wrapperspb.String("default"),
|
|
Type: "password",
|
|
Attrs: defaultAttributes,
|
|
},
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
clean := func() {
|
|
_, err := tested.DeleteAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()),
|
|
&pbs.DeleteAccountRequest{Id: acc.GetItem().GetId()})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
return acc.GetItem(), clean
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.UpdateAccountRequest
|
|
res *pbs.UpdateAccountResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "Update an Existing AuthMethod",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.NameField, globals.DescriptionField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: "password",
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: "password",
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Multiple Paths in single string",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name,description"},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: "password",
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: "password",
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "No Update Mask",
|
|
req: &pbs.UpdateAccountRequest{
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Cant change type",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.NameField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: ""},
|
|
Type: "oidc",
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "No Paths in Mask",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Only non-existent paths in Mask",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{"nonexistent_field"}},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Unset Name",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.NameField},
|
|
},
|
|
Item: &pb.Account{
|
|
Description: &wrapperspb.StringValue{Value: "ignored"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Description: &wrapperspb.StringValue{Value: "default"},
|
|
Type: "password",
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update Only Name",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.NameField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "updated"},
|
|
Description: &wrapperspb.StringValue{Value: "ignored"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "updated"},
|
|
Description: &wrapperspb.StringValue{Value: "default"},
|
|
Type: "password",
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update Only Description",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.DescriptionField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "ignored"},
|
|
Description: &wrapperspb.StringValue{Value: "notignored"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "default"},
|
|
Description: &wrapperspb.StringValue{Value: "notignored"},
|
|
Type: "password",
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update Only LoginName",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"attributes.login_name"},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "ignored"},
|
|
Description: &wrapperspb.StringValue{Value: "ignored"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "default"},
|
|
Description: &wrapperspb.StringValue{Value: "default"},
|
|
Type: "password",
|
|
Attrs: modifiedAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: pwAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update a Non Existing Old ID Account",
|
|
req: &pbs.UpdateAccountRequest{
|
|
Id: globals.PasswordAccountPreviousPrefix + "_DoesntExis",
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.DescriptionField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Update a Non Existing New ID Account",
|
|
req: &pbs.UpdateAccountRequest{
|
|
Id: globals.PasswordAccountPrefix + "_DoesntExis",
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.DescriptionField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Cant change Id",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"id"},
|
|
},
|
|
Item: &pb.Account{
|
|
Id: globals.PasswordAccountPrefix + "_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.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"created_time"},
|
|
},
|
|
Item: &pb.Account{
|
|
CreatedTime: timestamppb.Now(),
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Cant specify Updated Time",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"updated_time"},
|
|
},
|
|
Item: &pb.Account{
|
|
UpdatedTime: timestamppb.Now(),
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Cant specify Type",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"type"},
|
|
},
|
|
Item: &pb.Account{
|
|
Type: "oidc",
|
|
},
|
|
},
|
|
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)
|
|
acc, cleanup := freshAccount(t)
|
|
defer cleanup()
|
|
|
|
tc.req.Item.Version = 1
|
|
|
|
if tc.req.GetId() == "" {
|
|
tc.req.Id = acc.GetId()
|
|
}
|
|
|
|
if tc.res != nil && tc.res.Item != nil {
|
|
tc.res.Item.Id = acc.GetId()
|
|
tc.res.Item.CreatedTime = acc.GetCreatedTime()
|
|
}
|
|
|
|
got, gErr := tested.UpdateAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "UpdateAccount(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
} else {
|
|
require.NoError(gErr)
|
|
}
|
|
|
|
if tc.res == nil {
|
|
require.Nil(got)
|
|
}
|
|
|
|
if got != nil {
|
|
assert.NotNilf(tc.res, "Expected UpdateAccount response to be nil, but was %v", got)
|
|
gotUpdateTime := got.GetItem().GetUpdatedTime()
|
|
require.NoError(err, "Error converting proto to timestamp")
|
|
|
|
created := acc.GetCreatedTime()
|
|
require.NoError(err, "Error converting proto to timestamp")
|
|
|
|
// Verify it is a auth_method updated after it was created
|
|
assert.True(gotUpdateTime.AsTime().After(created.AsTime()), "Updated account 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.EqualValues(2, got.Item.Version)
|
|
tc.res.Item.Version = 2
|
|
}
|
|
assert.Empty(cmp.Diff(
|
|
got,
|
|
tc.res,
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
), "UpdateAccount(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateOidc(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)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
|
|
databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
am := 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]))
|
|
|
|
tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new auth_method service.")
|
|
|
|
defaultScopeInfo := &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: o.GetType(), ParentScopeId: scope.Global.String()}
|
|
defaultAttributes := &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Issuer: "https://www.alice.com",
|
|
Subject: "test-subject",
|
|
},
|
|
}
|
|
modifiedAttributes := &pb.Account_OidcAccountAttributes{
|
|
OidcAccountAttributes: &pb.OidcAccountAttributes{
|
|
Issuer: "https://www.changed.com",
|
|
Subject: "changed",
|
|
},
|
|
}
|
|
|
|
freshAccount := func(t *testing.T) (*oidc.Account, func()) {
|
|
t.Helper()
|
|
acc := oidc.TestAccount(t, conn, am, "test-subject", oidc.WithName("default"), oidc.WithDescription("default"))
|
|
|
|
clean := func() {
|
|
_, err := tested.DeleteAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()),
|
|
&pbs.DeleteAccountRequest{Id: acc.GetPublicId()})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
return acc, clean
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.UpdateAccountRequest
|
|
res *pbs.UpdateAccountResponse
|
|
err error
|
|
}{
|
|
{
|
|
name: "Update an Existing AuthMethod",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.NameField, globals.DescriptionField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: oidc.Subtype.String(),
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Multiple Paths in single string",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name,description"},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: oidc.Subtype.String(),
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "No Update Mask",
|
|
req: &pbs.UpdateAccountRequest{
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Cant change type",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.NameField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: ""},
|
|
Type: "oidc",
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "No Paths in Mask",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Only non-existent paths in Mask",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{"nonexistent_field"}},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Unset Name",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.NameField},
|
|
},
|
|
Item: &pb.Account{
|
|
Description: &wrapperspb.StringValue{Value: "ignored"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Description: &wrapperspb.StringValue{Value: "default"},
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update Only Name",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.NameField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "updated"},
|
|
Description: &wrapperspb.StringValue{Value: "ignored"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "updated"},
|
|
Description: &wrapperspb.StringValue{Value: "default"},
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update Only Description",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.DescriptionField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "ignored"},
|
|
Description: &wrapperspb.StringValue{Value: "notignored"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "default"},
|
|
Description: &wrapperspb.StringValue{Value: "notignored"},
|
|
Type: oidc.Subtype.String(),
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: oidcAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update LoginName",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"attributes.login_name"},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "ignored"},
|
|
Description: &wrapperspb.StringValue{Value: "ignored"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Update a Non Existing Account",
|
|
req: &pbs.UpdateAccountRequest{
|
|
Id: globals.PasswordAccountPrefix + "_DoesntExis",
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.DescriptionField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
},
|
|
{
|
|
name: "Cant change Id",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"id"},
|
|
},
|
|
Item: &pb.Account{
|
|
Id: globals.PasswordAccountPrefix + "_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.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"created_time"},
|
|
},
|
|
Item: &pb.Account{
|
|
CreatedTime: timestamppb.Now(),
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Cant specify Updated Time",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"updated_time"},
|
|
},
|
|
Item: &pb.Account{
|
|
UpdatedTime: timestamppb.Now(),
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Cant specify Type",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"type"},
|
|
},
|
|
Item: &pb.Account{
|
|
Type: "oidc",
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Update Issuer",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"attributes.issuer"},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "ignored"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
},
|
|
{
|
|
name: "Update Subject",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"attributes.subject"},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "ignored"},
|
|
Attrs: modifiedAttributes,
|
|
},
|
|
},
|
|
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)
|
|
acc, cleanup := freshAccount(t)
|
|
defer cleanup()
|
|
|
|
tc.req.Item.Version = 1
|
|
|
|
if tc.req.GetId() == "" {
|
|
tc.req.Id = acc.GetPublicId()
|
|
}
|
|
|
|
if tc.res != nil && tc.res.Item != nil {
|
|
tc.res.Item.Id = acc.GetPublicId()
|
|
tc.res.Item.CreatedTime = acc.GetCreateTime().GetTimestamp()
|
|
}
|
|
|
|
got, gErr := tested.UpdateAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "UpdateAccount(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
}
|
|
|
|
if tc.res == nil {
|
|
require.Nil(got)
|
|
}
|
|
|
|
if got != nil {
|
|
assert.NotNilf(tc.res, "Expected UpdateAccount response to be nil, but was %v", got)
|
|
gotUpdateTime := got.GetItem().GetUpdatedTime()
|
|
require.NoError(err, "Error converting proto to timestamp")
|
|
|
|
created := acc.GetCreateTime().GetTimestamp()
|
|
require.NoError(err, "Error converting proto to timestamp")
|
|
|
|
// Verify it is a auth_method updated after it was created
|
|
assert.True(gotUpdateTime.AsTime().After(created.AsTime()), "Updated account 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.EqualValues(2, got.Item.Version)
|
|
tc.res.Item.Version = 2
|
|
}
|
|
assert.Empty(cmp.Diff(
|
|
got,
|
|
tc.res,
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
), "UpdateAccount(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateLdap(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)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
|
|
databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
am := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://ldap"})
|
|
|
|
tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new auth_method service.")
|
|
|
|
defaultScopeInfo := &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: o.GetType(), ParentScopeId: scope.Global.String()}
|
|
defaultAttributes := &pb.Account_LdapAccountAttributes{
|
|
LdapAccountAttributes: &pb.LdapAccountAttributes{
|
|
LoginName: "test-login",
|
|
},
|
|
}
|
|
|
|
freshAccount := func(t *testing.T) (*ldap.Account, func()) {
|
|
t.Helper()
|
|
acc := ldap.TestAccount(t, conn, am, "test-login", ldap.WithName(ctx, "default"), ldap.WithDescription(ctx, "default"))
|
|
|
|
clean := func() {
|
|
_, err := tested.DeleteAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()),
|
|
&pbs.DeleteAccountRequest{Id: acc.GetPublicId()})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
return acc, clean
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *pbs.UpdateAccountRequest
|
|
res *pbs.UpdateAccountResponse
|
|
err error
|
|
errContains string
|
|
}{
|
|
{
|
|
name: "update-existing",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.NameField, globals.DescriptionField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: ldap.Subtype.String(),
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: ldapAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Multiple Paths in single string",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"name,description"},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: ldap.Subtype.String(),
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: ldapAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "No Update Mask",
|
|
req: &pbs.UpdateAccountRequest{
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "UpdateMask not provided but is required to update this resource",
|
|
},
|
|
{
|
|
name: "Cant change type",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.NameField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: ""},
|
|
Type: "oidc",
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "Cannot modify the resource type",
|
|
},
|
|
{
|
|
name: "No Paths in Mask",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "missing mask",
|
|
},
|
|
{
|
|
name: "Only non-existent paths in Mask",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{Paths: []string{"nonexistent_field"}},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "updated name"},
|
|
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "No valid fields included in the update mask",
|
|
},
|
|
{
|
|
name: "Unset Name",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.NameField},
|
|
},
|
|
Item: &pb.Account{
|
|
Description: &wrapperspb.StringValue{Value: "ignored"},
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Description: &wrapperspb.StringValue{Value: "default"},
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: ldapAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update Only Name",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.NameField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "updated"},
|
|
Description: &wrapperspb.StringValue{Value: "ignored"},
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "updated"},
|
|
Description: &wrapperspb.StringValue{Value: "default"},
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: ldapAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update Only Description",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.DescriptionField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "ignored"},
|
|
Description: &wrapperspb.StringValue{Value: "notignored"},
|
|
},
|
|
},
|
|
res: &pbs.UpdateAccountResponse{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Name: &wrapperspb.StringValue{Value: "default"},
|
|
Description: &wrapperspb.StringValue{Value: "notignored"},
|
|
Type: ldap.Subtype.String(),
|
|
Attrs: defaultAttributes,
|
|
Scope: defaultScopeInfo,
|
|
AuthorizedActions: ldapAuthorizedActions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Update LoginName",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"attributes.login_name"},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "ignored"},
|
|
Description: &wrapperspb.StringValue{Value: "ignored"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "Field cannot be updated.",
|
|
},
|
|
{
|
|
name: "Update a Non Existing Account",
|
|
req: &pbs.UpdateAccountRequest{
|
|
Id: globals.LdapAccountPrefix + "_DoesntExis",
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{globals.DescriptionField},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "desc"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.NotFound),
|
|
errContains: "Resource not found.",
|
|
},
|
|
{
|
|
name: "Cant change Id",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"id"},
|
|
},
|
|
Item: &pb.Account{
|
|
Id: globals.LdapAccountPrefix + "_somethinge",
|
|
Name: &wrapperspb.StringValue{Value: "new"},
|
|
Description: &wrapperspb.StringValue{Value: "new desc"},
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "name: \"id\", desc: \"This is a read only field and cannot be specified in an update request.",
|
|
},
|
|
{
|
|
name: "Cant specify Created Time",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"created_time"},
|
|
},
|
|
Item: &pb.Account{
|
|
CreatedTime: timestamppb.Now(),
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "name: \"created_time\", desc: \"This is a read only field and cannot be specified in an update request.",
|
|
},
|
|
{
|
|
name: "Cant specify Updated Time",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"updated_time"},
|
|
},
|
|
Item: &pb.Account{
|
|
UpdatedTime: timestamppb.Now(),
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "name: \"updated_time\", desc: \"This is a read only field and cannot be specified in an update request.",
|
|
},
|
|
{
|
|
name: "Cant specify Type",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"type"},
|
|
},
|
|
Item: &pb.Account{
|
|
Type: "oidc",
|
|
},
|
|
},
|
|
res: nil,
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "name: \"type\", desc: \"Cannot modify the resource type.",
|
|
},
|
|
{
|
|
name: "Update Login name",
|
|
req: &pbs.UpdateAccountRequest{
|
|
UpdateMask: &field_mask.FieldMask{
|
|
Paths: []string{"attributes.login_name"},
|
|
},
|
|
Item: &pb.Account{
|
|
Name: &wrapperspb.StringValue{Value: "ignored"},
|
|
},
|
|
},
|
|
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
|
errContains: "name: \"attributes.login_name\", desc: \"Field cannot be updated.",
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
acc, cleanup := freshAccount(t)
|
|
defer cleanup()
|
|
|
|
tc.req.Item.Version = 1
|
|
|
|
if tc.req.GetId() == "" {
|
|
tc.req.Id = acc.GetPublicId()
|
|
}
|
|
|
|
if tc.res != nil && tc.res.Item != nil {
|
|
tc.res.Item.Id = acc.GetPublicId()
|
|
tc.res.Item.CreatedTime = acc.GetCreateTime().GetTimestamp()
|
|
}
|
|
|
|
got, gErr := tested.UpdateAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
|
if tc.err != nil {
|
|
require.Error(gErr)
|
|
assert.True(errors.Is(gErr, tc.err), "UpdateAccount(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
|
require.NotEmpty(tc.errContains)
|
|
assert.Contains(gErr.Error(), tc.errContains)
|
|
return
|
|
}
|
|
|
|
require.NoError(gErr)
|
|
|
|
if tc.res == nil {
|
|
require.Nil(got)
|
|
}
|
|
|
|
if got != nil {
|
|
assert.NotNilf(tc.res, "Expected UpdateAccount response to be nil, but was %v", got)
|
|
gotUpdateTime := got.GetItem().GetUpdatedTime()
|
|
require.NoError(err, "Error converting proto to timestamp")
|
|
|
|
created := acc.GetCreateTime().GetTimestamp()
|
|
require.NoError(err, "Error converting proto to timestamp")
|
|
|
|
// Verify it is a auth_method updated after it was created
|
|
assert.True(gotUpdateTime.AsTime().After(created.AsTime()), "Updated account 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.EqualValues(2, got.Item.Version)
|
|
tc.res.Item.Version = 2
|
|
}
|
|
assert.Empty(cmp.Diff(
|
|
got,
|
|
tc.res,
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
), "UpdateAccount(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetPassword(t *testing.T) {
|
|
ctx := context.TODO()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrap := db.TestWrapper(t)
|
|
kms := kms.TestKms(t, conn, wrap)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new auth_method service.")
|
|
|
|
createAccount := func(t *testing.T, pw string) *pb.Account {
|
|
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
|
|
pwAttrs := &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "testusername",
|
|
},
|
|
}
|
|
if pw != "" {
|
|
pwAttrs.PasswordAccountAttributes.Password = wrapperspb.String(pw)
|
|
}
|
|
createResp, err := tested.CreateAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Type: "password",
|
|
Attrs: pwAttrs,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, createResp)
|
|
require.NotNil(t, createResp.GetItem())
|
|
return createResp.GetItem()
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
oldPw string
|
|
newPw string
|
|
}{
|
|
{
|
|
name: "has old set new",
|
|
oldPw: "originalpassword",
|
|
newPw: "a different password",
|
|
},
|
|
{
|
|
name: "has old unset new",
|
|
oldPw: "originalpassword",
|
|
newPw: "",
|
|
},
|
|
{
|
|
name: "no old password set new",
|
|
oldPw: "",
|
|
newPw: "a different password",
|
|
},
|
|
}
|
|
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
acct := createAccount(t, tt.oldPw)
|
|
|
|
setResp, err := tested.SetPassword(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), &pbs.SetPasswordRequest{
|
|
Id: acct.GetId(),
|
|
Version: acct.GetVersion(),
|
|
Password: tt.newPw,
|
|
})
|
|
require.NoError(err)
|
|
assert.Equal(acct.GetVersion()+1, setResp.GetItem().GetVersion())
|
|
// clear uncomparable fields
|
|
acct.Version, setResp.GetItem().Version = 0, 0
|
|
acct.UpdatedTime, setResp.GetItem().UpdatedTime = nil, nil
|
|
|
|
assert.Empty(cmp.Diff(
|
|
acct,
|
|
setResp.GetItem(),
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
))
|
|
})
|
|
}
|
|
|
|
defaultAcct := createAccount(t, "")
|
|
badRequestCases := []struct {
|
|
name string
|
|
accountId string
|
|
version uint32
|
|
password string
|
|
}{
|
|
{
|
|
name: "empty account id",
|
|
accountId: "",
|
|
version: defaultAcct.GetVersion(),
|
|
password: "somepassword",
|
|
},
|
|
{
|
|
name: "unset version",
|
|
accountId: defaultAcct.GetId(),
|
|
version: 0,
|
|
password: "somepassword",
|
|
},
|
|
{
|
|
name: "notfound old account id",
|
|
accountId: globals.PasswordAccountPreviousPrefix + "_DoesntExis",
|
|
version: defaultAcct.GetVersion(),
|
|
password: "anewpassword",
|
|
},
|
|
{
|
|
name: "notfound new account id",
|
|
accountId: globals.PasswordAccountPrefix + "_DoesntExis",
|
|
version: defaultAcct.GetVersion(),
|
|
password: "anewpassword",
|
|
},
|
|
{
|
|
name: "password too short",
|
|
accountId: defaultAcct.GetId(),
|
|
version: defaultAcct.GetVersion(),
|
|
password: "123",
|
|
},
|
|
}
|
|
|
|
for _, tt := range badRequestCases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
setResp, err := tested.SetPassword(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), &pbs.SetPasswordRequest{
|
|
Id: tt.accountId,
|
|
Version: tt.version,
|
|
Password: tt.password,
|
|
})
|
|
assert.Error(err)
|
|
assert.Nil(setResp)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestChangePassword(t *testing.T) {
|
|
ctx := context.TODO()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrap := db.TestWrapper(t)
|
|
kms := kms.TestKms(t, conn, wrap)
|
|
pwRepoFn := func() (*password.Repository, error) {
|
|
return password.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
oidcRepoFn := func() (*oidc.Repository, error) {
|
|
return oidc.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
ldapRepoFn := func() (*ldap.Repository, error) {
|
|
return ldap.NewRepository(ctx, rw, rw, kms)
|
|
}
|
|
|
|
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000)
|
|
require.NoError(t, err, "Error when getting new auth_method service.")
|
|
|
|
createAccount := func(t *testing.T, pw string) *pb.Account {
|
|
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
|
|
pwAttrs := &pb.Account_PasswordAccountAttributes{
|
|
PasswordAccountAttributes: &pb.PasswordAccountAttributes{
|
|
LoginName: "testusername",
|
|
},
|
|
}
|
|
if pw != "" {
|
|
pwAttrs.PasswordAccountAttributes.Password = wrapperspb.String(pw)
|
|
}
|
|
createResp, err := tested.CreateAccount(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), &pbs.CreateAccountRequest{
|
|
Item: &pb.Account{
|
|
AuthMethodId: am.GetPublicId(),
|
|
Type: "password",
|
|
Attrs: pwAttrs,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, createResp)
|
|
require.NotNil(t, createResp.GetItem())
|
|
return createResp.GetItem()
|
|
}
|
|
|
|
t.Run("valid update", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
acct := createAccount(t, "originalpassword")
|
|
|
|
changeResp, err := tested.ChangePassword(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), &pbs.ChangePasswordRequest{
|
|
Id: acct.GetId(),
|
|
Version: acct.GetVersion(),
|
|
CurrentPassword: "originalpassword",
|
|
NewPassword: "a different password",
|
|
})
|
|
require.NoError(err)
|
|
assert.Equal(acct.GetVersion()+1, changeResp.GetItem().GetVersion())
|
|
// clear uncomparable fields
|
|
acct.Version, changeResp.GetItem().Version = 0, 0
|
|
acct.UpdatedTime, changeResp.GetItem().UpdatedTime = nil, nil
|
|
|
|
assert.Empty(cmp.Diff(
|
|
acct,
|
|
changeResp.GetItem(),
|
|
protocmp.Transform(),
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
))
|
|
})
|
|
|
|
t.Run("unauthenticated update", func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
acct := createAccount(t, "originalpassword")
|
|
|
|
changeResp, err := tested.ChangePassword(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), &pbs.ChangePasswordRequest{
|
|
Id: acct.GetId(),
|
|
Version: acct.GetVersion(),
|
|
CurrentPassword: "thewrongpassword",
|
|
NewPassword: "a different password",
|
|
})
|
|
assert.Error(err)
|
|
assert.Nil(changeResp)
|
|
})
|
|
|
|
defaultAcct := createAccount(t, "")
|
|
badRequestCases := []struct {
|
|
name string
|
|
authMethodId string
|
|
accountId string
|
|
version uint32
|
|
oldPW string
|
|
newPW string
|
|
}{
|
|
{
|
|
name: "empty auth method",
|
|
authMethodId: "",
|
|
accountId: defaultAcct.GetId(),
|
|
version: defaultAcct.GetVersion(),
|
|
oldPW: "somepassword",
|
|
newPW: "anewpassword",
|
|
},
|
|
{
|
|
name: "empty account id",
|
|
authMethodId: defaultAcct.GetAuthMethodId(),
|
|
accountId: "",
|
|
version: defaultAcct.GetVersion(),
|
|
oldPW: "somepassword",
|
|
newPW: "anewpassword",
|
|
},
|
|
{
|
|
name: "unset version",
|
|
authMethodId: defaultAcct.GetAuthMethodId(),
|
|
accountId: defaultAcct.GetId(),
|
|
version: 0,
|
|
oldPW: "somepassword",
|
|
newPW: "anewpassword",
|
|
},
|
|
{
|
|
name: "no old password",
|
|
authMethodId: defaultAcct.GetAuthMethodId(),
|
|
accountId: defaultAcct.GetId(),
|
|
version: defaultAcct.GetVersion(),
|
|
oldPW: "",
|
|
newPW: "anewpassword",
|
|
},
|
|
{
|
|
name: "no new password",
|
|
authMethodId: defaultAcct.GetAuthMethodId(),
|
|
accountId: defaultAcct.GetId(),
|
|
version: defaultAcct.GetVersion(),
|
|
oldPW: "somepassword",
|
|
newPW: "",
|
|
},
|
|
{
|
|
name: "matching old and new passwords",
|
|
authMethodId: defaultAcct.GetAuthMethodId(),
|
|
accountId: defaultAcct.GetId(),
|
|
version: defaultAcct.GetVersion(),
|
|
oldPW: "somepassword",
|
|
newPW: "somepassword",
|
|
},
|
|
{
|
|
name: "notfound old account id",
|
|
authMethodId: defaultAcct.GetAuthMethodId(),
|
|
accountId: globals.PasswordAccountPreviousPrefix + "_DoesntExis",
|
|
version: defaultAcct.GetVersion(),
|
|
oldPW: "somepassword",
|
|
newPW: "anewpassword",
|
|
},
|
|
{
|
|
name: "notfound new account id",
|
|
authMethodId: defaultAcct.GetAuthMethodId(),
|
|
accountId: globals.PasswordAccountPrefix + "_DoesntExis",
|
|
version: defaultAcct.GetVersion(),
|
|
oldPW: "somepassword",
|
|
newPW: "anewpassword",
|
|
},
|
|
{
|
|
name: "new password too short",
|
|
authMethodId: defaultAcct.GetAuthMethodId(),
|
|
accountId: defaultAcct.GetId(),
|
|
version: defaultAcct.GetVersion(),
|
|
oldPW: "somepassword",
|
|
newPW: "123",
|
|
},
|
|
}
|
|
|
|
for _, tt := range badRequestCases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
changeResp, err := tested.ChangePassword(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), &pbs.ChangePasswordRequest{
|
|
Id: tt.accountId,
|
|
Version: tt.version,
|
|
CurrentPassword: tt.oldPW,
|
|
NewPassword: tt.newPW,
|
|
})
|
|
assert.Error(err)
|
|
assert.Nil(changeResp)
|
|
})
|
|
}
|
|
}
|
|
|
|
// The purpose of this test is mainly to ensure that we are properly fetching
|
|
// membership information in GrantsForUser across managed group types
|
|
func TestGrantsAcrossManagedGroups(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)
|
|
|
|
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
|
|
|
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase)
|
|
require.NoError(t, err)
|
|
oidcAm := oidc.TestAuthMethod(
|
|
t, conn, databaseWrapper, org.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")
|
|
// Create a managed group that will always match, so we can test that it is
|
|
// returned in results
|
|
oidcMg := oidc.TestManagedGroup(t, conn, oidcAm, `"/token/sub" matches ".*"`)
|
|
oidc.TestManagedGroupMember(t, conn, oidcMg.GetPublicId(), oidcAcct.GetPublicId())
|
|
|
|
ldapAm := ldap.TestAuthMethod(t, conn, databaseWrapper, org.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"),
|
|
)
|
|
ldapMg := ldap.TestManagedGroup(t, conn, ldapAm, []string{"admin"})
|
|
|
|
iamRepoFn := func() (*iam.Repository, error) {
|
|
return iam.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
iamRepo, err := iamRepoFn()
|
|
require.NoError(t, err)
|
|
|
|
user := iam.TestUser(t, iamRepo, org.PublicId, iam.WithAccountIds(oidcAcct.GetPublicId(), ldapAcct.GetPublicId()))
|
|
|
|
// Create two roles, each containing a single managed group, and add a
|
|
// unique grant to each
|
|
oidcRole := iam.TestRole(t, conn, org.GetPublicId(), iam.WithGrantScopeIds([]string{globals.GrantScopeChildren}))
|
|
iam.TestManagedGroupRole(t, conn, oidcRole.GetPublicId(), oidcMg.GetPublicId())
|
|
iam.TestRoleGrant(t, conn, oidcRole.GetPublicId(), "ids=ttcp_oidc;actions=read")
|
|
ldapRole := iam.TestRole(t, conn, org.GetPublicId(), iam.WithGrantScopeIds([]string{globals.GrantScopeChildren}))
|
|
iam.TestManagedGroupRole(t, conn, ldapRole.GetPublicId(), ldapMg.GetPublicId())
|
|
iam.TestRoleGrant(t, conn, ldapRole.GetPublicId(), "ids=ttcp_ldap;actions=read")
|
|
|
|
grants, err := iamRepo.GrantsForUser(ctx, user.GetPublicId())
|
|
require.NoError(t, err)
|
|
|
|
// Verify we see both grants
|
|
var foundOidc, foundLdap bool
|
|
for _, grant := range grants {
|
|
if grant.Grant == "ids=ttcp_oidc;actions=read" {
|
|
foundOidc = true
|
|
}
|
|
if grant.Grant == "ids=ttcp_ldap;actions=read" {
|
|
foundLdap = true
|
|
}
|
|
}
|
|
assert.True(t, foundOidc)
|
|
assert.True(t, foundLdap)
|
|
|
|
// Delete the ldap managed group
|
|
ldapRepo, err := ldap.NewRepository(ctx, rw, rw, kmsCache)
|
|
require.NoError(t, err)
|
|
numDeleted, err := ldapRepo.DeleteManagedGroup(ctx, org.GetPublicId(), ldapMg.GetPublicId())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, numDeleted)
|
|
|
|
// Verify we don't see the ldap grant anymore
|
|
grants, err = iamRepo.GrantsForUser(ctx, user.GetPublicId())
|
|
require.NoError(t, err)
|
|
foundOidc = false
|
|
foundLdap = false
|
|
for _, grant := range grants {
|
|
if grant.Grant == "ids=ttcp_oidc;actions=read" {
|
|
foundOidc = true
|
|
}
|
|
if grant.Grant == "ids=ttcp_ldap;actions=read" {
|
|
foundLdap = true
|
|
}
|
|
}
|
|
assert.True(t, foundOidc)
|
|
assert.False(t, foundLdap)
|
|
|
|
// Delete the oidc managed group
|
|
oidcRepo, err := oidc.NewRepository(ctx, rw, rw, kmsCache)
|
|
require.NoError(t, err)
|
|
numDeleted, err = oidcRepo.DeleteManagedGroup(ctx, org.GetPublicId(), oidcMg.GetPublicId())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, numDeleted)
|
|
|
|
// Verify we don't see the oidc grant anymore
|
|
grants, err = iamRepo.GrantsForUser(ctx, user.GetPublicId())
|
|
require.NoError(t, err)
|
|
foundOidc = false
|
|
foundLdap = false
|
|
for _, grant := range grants {
|
|
if grant.Grant == "ids=ttcp_oidc;actions=read" {
|
|
foundOidc = true
|
|
}
|
|
if grant.Grant == "ids=ttcp_ldap;actions=read" {
|
|
foundLdap = true
|
|
}
|
|
}
|
|
assert.False(t, foundOidc)
|
|
assert.False(t, foundLdap)
|
|
}
|