Add Account Update handler (#257)
* Allow updating accounts (not username or pw). * Updating new field names to LoginName. * Adding creation with password test. * Fixing documentation for WithLoginName
This commit is contained in:
1 parent
203e2b5dc2
commit
245c7fbb44
12 files changed
+512
-53
No files matched your search
@@ -25,24 +25,20 @@ func allocAccount() Account {
|
||||
}
|
||||
}
|
||||
|
||||
// NewAccount creates a new in memory Account with loginName assigned to
|
||||
// authMethodId. Name and description are the only valid options. All other
|
||||
// options are ignored.
|
||||
func NewAccount(authMethodId string, loginName string, opt ...Option) (*Account, error) {
|
||||
// NewAccount creates a new in memory Account. LoginName, name, and
|
||||
// description are the only valid options. All other options are ignored.
|
||||
func NewAccount(authMethodId string, opt ...Option) (*Account, error) {
|
||||
// NOTE(mgaffney): The scopeId in the embedded *store.Account is
|
||||
// populated by a trigger in the database.
|
||||
if authMethodId == "" {
|
||||
return nil, fmt.Errorf("new: password account: no auth method id: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if loginName == "" {
|
||||
return nil, fmt.Errorf("new: password account: no user name: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
|
||||
opts := getOpts(opt...)
|
||||
a := &Account{
|
||||
Account: &store.Account{
|
||||
AuthMethodId: authMethodId,
|
||||
LoginName: loginName,
|
||||
LoginName: opts.withLoginName,
|
||||
Name: opts.withName,
|
||||
Description: opts.withDescription,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package password
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/auth/password/store"
|
||||
@@ -14,7 +13,6 @@ import (
|
||||
func TestAccount_New(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
|
||||
w := db.New(conn)
|
||||
o, _ := iam.TestScopes(t, conn)
|
||||
auts := TestAuthMethods(t, conn, o.GetPublicId(), 1)
|
||||
aut := auts[0]
|
||||
@@ -43,12 +41,25 @@ func TestAccount_New(t *testing.T) {
|
||||
name: "valid-no-options",
|
||||
args: args{
|
||||
authMethodId: aut.GetPublicId(),
|
||||
loginName: "kazmierczak",
|
||||
},
|
||||
want: &Account{
|
||||
Account: &store.Account{
|
||||
AuthMethodId: aut.GetPublicId(),
|
||||
LoginName: "kazmierczak",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-with-user-name",
|
||||
args: args{
|
||||
authMethodId: aut.GetPublicId(),
|
||||
opts: []Option{
|
||||
WithLoginName("kazmierczak1"),
|
||||
},
|
||||
},
|
||||
want: &Account{
|
||||
Account: &store.Account{
|
||||
AuthMethodId: aut.GetPublicId(),
|
||||
LoginName: "kazmierczak1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -56,7 +67,6 @@ func TestAccount_New(t *testing.T) {
|
||||
name: "valid-with-name",
|
||||
args: args{
|
||||
authMethodId: aut.GetPublicId(),
|
||||
loginName: "kazmierczak1",
|
||||
opts: []Option{
|
||||
WithName("test-name"),
|
||||
},
|
||||
@@ -64,7 +74,6 @@ func TestAccount_New(t *testing.T) {
|
||||
want: &Account{
|
||||
Account: &store.Account{
|
||||
AuthMethodId: aut.GetPublicId(),
|
||||
LoginName: "kazmierczak1",
|
||||
Name: "test-name",
|
||||
},
|
||||
},
|
||||
@@ -73,7 +82,6 @@ func TestAccount_New(t *testing.T) {
|
||||
name: "valid-with-description",
|
||||
args: args{
|
||||
authMethodId: aut.GetPublicId(),
|
||||
loginName: "kazmierczak2",
|
||||
opts: []Option{
|
||||
WithDescription("test-description"),
|
||||
},
|
||||
@@ -81,7 +89,6 @@ func TestAccount_New(t *testing.T) {
|
||||
want: &Account{
|
||||
Account: &store.Account{
|
||||
AuthMethodId: aut.GetPublicId(),
|
||||
LoginName: "kazmierczak2",
|
||||
Description: "test-description",
|
||||
},
|
||||
},
|
||||
@@ -92,7 +99,7 @@ func TestAccount_New(t *testing.T) {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
got, err := NewAccount(tt.args.authMethodId, tt.args.loginName, tt.args.opts...)
|
||||
got, err := NewAccount(tt.args.authMethodId, tt.args.opts...)
|
||||
if tt.wantErr {
|
||||
assert.Error(err)
|
||||
require.Nil(got)
|
||||
@@ -103,15 +110,6 @@ func TestAccount_New(t *testing.T) {
|
||||
|
||||
assert.Emptyf(got.PublicId, "PublicId set")
|
||||
assert.Equal(tt.want, got)
|
||||
|
||||
id, err := newAccountId()
|
||||
assert.NoError(err)
|
||||
|
||||
tt.want.PublicId = id
|
||||
got.PublicId = id
|
||||
|
||||
err2 := w.Create(context.Background(), got)
|
||||
assert.NoError(err2)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ type Option func(*options)
|
||||
type options struct {
|
||||
withName string
|
||||
withDescription string
|
||||
withLoginName string
|
||||
withLimit int
|
||||
withConfig Configuration
|
||||
withPublicId string
|
||||
@@ -50,6 +51,13 @@ func WithName(name string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithLoginName provides an optional login name.
|
||||
func WithLoginName(loginName string) Option {
|
||||
return func(o *options) {
|
||||
o.withLoginName = loginName
|
||||
}
|
||||
}
|
||||
|
||||
// WithLimit provides an option to provide a limit. Intentionally allowing
|
||||
// negative integers. If WithLimit < 0, then unlimited results are returned.
|
||||
// If WithLimit == 0, then default limits are used for results.
|
||||
|
||||
@@ -15,6 +15,12 @@ func Test_GetOpts(t *testing.T) {
|
||||
testOpts.withPublicId = "test id"
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithLoginName", func(t *testing.T) {
|
||||
opts := getOpts(WithLoginName("test"))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withLoginName = "test"
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithName", func(t *testing.T) {
|
||||
opts := getOpts(WithName("test"))
|
||||
testOpts := getDefaultOptions()
|
||||
|
||||
@@ -93,8 +93,8 @@ func (r *Repository) CreateAccount(ctx context.Context, a *Account, opt ...Optio
|
||||
|
||||
if err != nil {
|
||||
if db.IsUniqueError(err) {
|
||||
return nil, fmt.Errorf("create: password account: in auth method: %s: name %s already exists: %w",
|
||||
a.AuthMethodId, a.Name, db.ErrNotUnique)
|
||||
return nil, fmt.Errorf("create: password account: in auth method: %s: name %q or loginName %q already exists: %w",
|
||||
a.AuthMethodId, a.Name, a.LoginName, db.ErrNotUnique)
|
||||
}
|
||||
return nil, fmt.Errorf("create: password account: in auth method: %s: %w", a.AuthMethodId, err)
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func TestAccounts(t *testing.T, conn *gorm.DB, authMethodId string, count int) [
|
||||
w := db.New(conn)
|
||||
var auts []*Account
|
||||
for i := 0; i < count; i++ {
|
||||
cat, err := NewAccount(authMethodId, fmt.Sprintf("name%d", i))
|
||||
cat, err := NewAccount(authMethodId, WithLoginName(fmt.Sprintf("name%d", i)))
|
||||
assert.NoError(err)
|
||||
require.NotNil(cat)
|
||||
id, err := newAccountId()
|
||||
|
||||
@@ -535,7 +535,7 @@ func (b *Server) CreateDevDatabase(dialect string) error {
|
||||
}
|
||||
}
|
||||
|
||||
acct, err := password.NewAccount(amId, acctLoginName)
|
||||
acct, err := password.NewAccount(amId, password.WithLoginName(acctLoginName))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating new in memory auth account: %w", err)
|
||||
}
|
||||
|
||||
@@ -103,7 +103,19 @@ func (s Service) CreateAccount(ctx context.Context, req *pbs.CreateAccountReques
|
||||
|
||||
// UpdateAccount implements the interface pbs.AccountServiceServer.
|
||||
func (s Service) UpdateAccount(ctx context.Context, req *pbs.UpdateAccountRequest) (*pbs.UpdateAccountResponse, error) {
|
||||
panic("UpdateAccount is not implemented.")
|
||||
authResults := auth.Verify(ctx)
|
||||
if !authResults.Valid {
|
||||
return nil, handlers.ForbiddenError()
|
||||
}
|
||||
if err := validateUpdateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u, err := s.updateInRepo(ctx, req.GetAuthMethodId(), req.GetId(), req.GetVersion(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
return &pbs.UpdateAccountResponse{Item: u}, nil
|
||||
}
|
||||
|
||||
// DeleteAccount implements the interface pbs.AccountServiceServer.
|
||||
@@ -145,14 +157,17 @@ func (s Service) createInRepo(ctx context.Context, authMethodId string, item *pb
|
||||
if err := handlers.StructToProto(item.GetAttributes(), pwAttrs); err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Provided attributes don't match expected format.")
|
||||
}
|
||||
var opts []password.Option
|
||||
opts := []password.Option{password.WithLoginName(pwAttrs.GetLoginName())}
|
||||
if pwAttrs.GetPassword() != nil {
|
||||
opts = append(opts, password.WithPassword(pwAttrs.GetPassword().GetValue()))
|
||||
}
|
||||
if item.GetName() != nil {
|
||||
opts = append(opts, password.WithName(item.GetName().GetValue()))
|
||||
}
|
||||
if item.GetDescription() != nil {
|
||||
opts = append(opts, password.WithDescription(item.GetDescription().GetValue()))
|
||||
}
|
||||
a, err := password.NewAccount(authMethodId, pwAttrs.GetLoginName(), opts...)
|
||||
a, err := password.NewAccount(authMethodId, opts...)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Unable to build user for creation: %v.", err)
|
||||
}
|
||||
@@ -170,6 +185,37 @@ func (s Service) createInRepo(ctx context.Context, authMethodId string, item *pb
|
||||
return toProto(out)
|
||||
}
|
||||
|
||||
func (s Service) updateInRepo(ctx context.Context, authMethodId, id string, version uint32, mask []string, item *pb.Account) (*pb.Account, error) {
|
||||
var opts []password.Option
|
||||
if desc := item.GetDescription(); desc != nil {
|
||||
opts = append(opts, password.WithDescription(desc.GetValue()))
|
||||
}
|
||||
if name := item.GetName(); name != nil {
|
||||
opts = append(opts, password.WithName(name.GetValue()))
|
||||
}
|
||||
u, err := password.NewAccount(authMethodId, opts...)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Unable to build auth method for update: %v.", err)
|
||||
}
|
||||
u.PublicId = id
|
||||
dbMask := maskManager.Translate(mask)
|
||||
if len(dbMask) == 0 {
|
||||
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
|
||||
}
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, rowsUpdated, err := repo.UpdateAccount(ctx, u, version, dbMask)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Unable to update auth method: %v.", err)
|
||||
}
|
||||
if rowsUpdated == 0 {
|
||||
return nil, handlers.NotFoundErrorf("AuthMethod %q doesn't exist.", id)
|
||||
}
|
||||
return toProto(out)
|
||||
}
|
||||
|
||||
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
@@ -308,6 +354,49 @@ func validateListRequest(req *pbs.ListAccountsRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateUpdateRequest(req *pbs.UpdateAccountRequest) error {
|
||||
badFields := map[string]string{}
|
||||
if !validId(req.GetId(), password.AccountPrefix+"_") {
|
||||
badFields["id"] = "Improperly formatted identifier."
|
||||
}
|
||||
if !validId(req.GetAuthMethodId(), password.AuthMethodPrefix+"_") {
|
||||
badFields["auth_method_id"] = "Improperly formatted identifier."
|
||||
}
|
||||
if req.GetUpdateMask() == nil {
|
||||
badFields["update_mask"] = "UpdateMask not provided but is required to update this resource."
|
||||
}
|
||||
if req.GetVersion() == 0 {
|
||||
badFields["version"] = "Existing resource version is required for an update."
|
||||
}
|
||||
|
||||
item := req.GetItem()
|
||||
if item == nil {
|
||||
// It is legitimate for no item to be specified in an update request as it indicates all fields provided in
|
||||
// the mask will be marked as unset.
|
||||
return nil
|
||||
}
|
||||
if item.GetId() != "" {
|
||||
badFields["id"] = "This is a read only field and cannot be specified in an update request."
|
||||
}
|
||||
if item.GetAuthMethodId() != "" {
|
||||
badFields["auth_method_id"] = "This is a read only field and cannot be specified in an update request."
|
||||
}
|
||||
if item.GetCreatedTime() != nil {
|
||||
badFields["created_time"] = "This is a read only field and cannot be specified in an update request."
|
||||
}
|
||||
if item.GetUpdatedTime() != nil {
|
||||
badFields["updated_time"] = "This is a read only field and cannot be specified in an update request."
|
||||
}
|
||||
if item.GetType() != "" {
|
||||
badFields["type"] = "This is a read only field and cannot be specified in an update request."
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validId(id, prefix string) bool {
|
||||
if !strings.HasPrefix(id, prefix) {
|
||||
return false
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/authtoken"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/authmethods"
|
||||
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes"
|
||||
scopepb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
@@ -20,8 +20,10 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/genproto/protobuf/field_mask"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
@@ -47,7 +49,7 @@ func TestGet(t *testing.T) {
|
||||
AuthMethodId: aa.GetAuthMethodId(),
|
||||
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
||||
Scope: &scopes.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String()},
|
||||
Scope: &scopepb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String()},
|
||||
Version: 1,
|
||||
Type: "password",
|
||||
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{"login_name": structpb.NewStringValue(aa.GetLoginName())}},
|
||||
@@ -113,7 +115,7 @@ func TestList(t *testing.T) {
|
||||
AuthMethodId: aa.GetAuthMethodId(),
|
||||
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
||||
Scope: &scopes.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String()},
|
||||
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String()},
|
||||
Version: 1,
|
||||
Type: "password",
|
||||
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{"login_name": structpb.NewStringValue(aa.GetLoginName())}},
|
||||
@@ -127,7 +129,7 @@ func TestList(t *testing.T) {
|
||||
AuthMethodId: aa.GetAuthMethodId(),
|
||||
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
||||
Scope: &scopes.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String()},
|
||||
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String()},
|
||||
Version: 1,
|
||||
Type: "password",
|
||||
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{"login_name": structpb.NewStringValue(aa.GetLoginName())}},
|
||||
@@ -302,7 +304,9 @@ func TestCreate(t *testing.T) {
|
||||
defaultCreated, err := ptypes.Timestamp(defaultAccount.GetCreateTime().GetTimestamp())
|
||||
require.NoError(t, err, "Error converting proto to timestamp.")
|
||||
|
||||
defaultSt, err := handlers.ProtoToStruct(&pb.PasswordAccountAttributes{LoginName: "thetestloginname"})
|
||||
structNoPw, err := handlers.ProtoToStruct(&pb.PasswordAccountAttributes{LoginName: "thetestloginname"})
|
||||
require.NoError(t, err, "Error converting proto to struct.")
|
||||
structWithPw, err := handlers.ProtoToStruct(&pb.PasswordAccountAttributes{LoginName: "adifferentusername", Password: wrapperspb.String("somerandompassword")})
|
||||
require.NoError(t, err, "Error converting proto to struct.")
|
||||
|
||||
cases := []struct {
|
||||
@@ -319,7 +323,7 @@ func TestCreate(t *testing.T) {
|
||||
Name: &wrapperspb.StringValue{Value: "name"},
|
||||
Description: &wrapperspb.StringValue{Value: "desc"},
|
||||
Type: "password",
|
||||
Attributes: defaultSt,
|
||||
Attributes: structNoPw,
|
||||
},
|
||||
},
|
||||
res: &pbs.CreateAccountResponse{
|
||||
@@ -328,10 +332,39 @@ func TestCreate(t *testing.T) {
|
||||
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
||||
Name: &wrapperspb.StringValue{Value: "name"},
|
||||
Description: &wrapperspb.StringValue{Value: "desc"},
|
||||
Scope: &scopes.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String()},
|
||||
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String()},
|
||||
Version: 1,
|
||||
Type: "password",
|
||||
Attributes: defaultSt,
|
||||
Attributes: structNoPw,
|
||||
},
|
||||
},
|
||||
errCode: codes.OK,
|
||||
},
|
||||
{
|
||||
name: "Create a valid Account with password defined",
|
||||
req: &pbs.CreateAccountRequest{
|
||||
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "name_with_password"},
|
||||
Description: &wrapperspb.StringValue{Value: "desc"},
|
||||
Type: "password",
|
||||
Attributes: structWithPw,
|
||||
},
|
||||
},
|
||||
res: &pbs.CreateAccountResponse{
|
||||
Uri: fmt.Sprintf("scopes/%s/auth-methods/%s/accounts/%s_", o.GetPublicId(), defaultAccount.GetAuthMethodId(), password.AccountPrefix),
|
||||
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()},
|
||||
Version: 1,
|
||||
Type: "password",
|
||||
Attributes: func() *structpb.Struct {
|
||||
newSt := proto.Clone(structWithPw).(*structpb.Struct)
|
||||
delete(newSt.Fields, "password")
|
||||
return newSt
|
||||
}(),
|
||||
},
|
||||
},
|
||||
errCode: codes.OK,
|
||||
@@ -343,7 +376,7 @@ func TestCreate(t *testing.T) {
|
||||
Item: &pb.Account{
|
||||
Id: password.AccountPrefix + "_notallowed",
|
||||
Type: "password",
|
||||
Attributes: defaultSt,
|
||||
Attributes: structNoPw,
|
||||
},
|
||||
},
|
||||
res: nil,
|
||||
@@ -356,7 +389,7 @@ func TestCreate(t *testing.T) {
|
||||
Item: &pb.Account{
|
||||
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
||||
Type: "password",
|
||||
Attributes: defaultSt,
|
||||
Attributes: structNoPw,
|
||||
},
|
||||
},
|
||||
res: nil,
|
||||
@@ -369,7 +402,7 @@ func TestCreate(t *testing.T) {
|
||||
Item: &pb.Account{
|
||||
CreatedTime: ptypes.TimestampNow(),
|
||||
Type: "password",
|
||||
Attributes: defaultSt,
|
||||
Attributes: structNoPw,
|
||||
},
|
||||
},
|
||||
res: nil,
|
||||
@@ -382,7 +415,7 @@ func TestCreate(t *testing.T) {
|
||||
Item: &pb.Account{
|
||||
UpdatedTime: ptypes.TimestampNow(),
|
||||
Type: "password",
|
||||
Attributes: defaultSt,
|
||||
Attributes: structNoPw,
|
||||
},
|
||||
},
|
||||
res: nil,
|
||||
@@ -393,7 +426,7 @@ func TestCreate(t *testing.T) {
|
||||
req: &pbs.CreateAccountRequest{
|
||||
AuthMethodId: defaultAccount.GetAuthMethodId(),
|
||||
Item: &pb.Account{
|
||||
Attributes: defaultSt,
|
||||
Attributes: structNoPw,
|
||||
},
|
||||
},
|
||||
res: nil,
|
||||
@@ -436,3 +469,324 @@ func TestCreate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrap := db.TestWrapper(t)
|
||||
repoFn := func() (*password.Repository, error) {
|
||||
return password.NewRepository(rw, rw, wrap)
|
||||
}
|
||||
|
||||
o, _ := iam.TestScopes(t, conn)
|
||||
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
|
||||
tested, err := accounts.NewService(repoFn)
|
||||
require.NoError(t, err, "Error when getting new auth_method service.")
|
||||
|
||||
defaultScopeInfo := &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: o.GetType()}
|
||||
defaultAttributes := &structpb.Struct{Fields: map[string]*structpb.Value{
|
||||
"login_name": structpb.NewStringValue("default"),
|
||||
}}
|
||||
|
||||
freshAccount := func(t *testing.T) (*pb.Account, func()) {
|
||||
t.Helper()
|
||||
acc, err := tested.CreateAccount(auth.DisabledAuthTestContext(auth.WithScopeId(o.GetPublicId())),
|
||||
&pbs.CreateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
Item: &pb.Account{
|
||||
Name: wrapperspb.String("default"),
|
||||
Description: wrapperspb.String("default"),
|
||||
Type: "password",
|
||||
Attributes: defaultAttributes,
|
||||
}},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
clean := func() {
|
||||
_, err := tested.DeleteAccount(auth.DisabledAuthTestContext(auth.WithScopeId(o.GetPublicId())),
|
||||
&pbs.DeleteAccountRequest{AuthMethodId: am.GetPublicId(), Id: acc.GetItem().GetId()})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
return acc.GetItem(), clean
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
req *pbs.UpdateAccountRequest
|
||||
res *pbs.UpdateAccountResponse
|
||||
errCode codes.Code
|
||||
}{
|
||||
{
|
||||
name: "Update an Existing AuthMethod",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"name", "description"},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "new"},
|
||||
Description: &wrapperspb.StringValue{Value: "desc"},
|
||||
},
|
||||
},
|
||||
res: &pbs.UpdateAccountResponse{
|
||||
Item: &pb.Account{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
Name: &wrapperspb.StringValue{Value: "new"},
|
||||
Description: &wrapperspb.StringValue{Value: "desc"},
|
||||
Type: "password",
|
||||
Attributes: defaultAttributes,
|
||||
Scope: defaultScopeInfo,
|
||||
},
|
||||
},
|
||||
errCode: codes.OK,
|
||||
},
|
||||
{
|
||||
name: "Multiple Paths in single string",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"name,description"},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "new"},
|
||||
Description: &wrapperspb.StringValue{Value: "desc"},
|
||||
},
|
||||
},
|
||||
res: &pbs.UpdateAccountResponse{
|
||||
Item: &pb.Account{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
Name: &wrapperspb.StringValue{Value: "new"},
|
||||
Description: &wrapperspb.StringValue{Value: "desc"},
|
||||
Type: "password",
|
||||
Attributes: defaultAttributes,
|
||||
Scope: defaultScopeInfo,
|
||||
},
|
||||
},
|
||||
errCode: codes.OK,
|
||||
},
|
||||
{
|
||||
name: "No Update Mask",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "updated name"},
|
||||
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
||||
},
|
||||
},
|
||||
errCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "No Paths in Mask",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "updated name"},
|
||||
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
||||
},
|
||||
},
|
||||
errCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "Only non-existant paths in Mask",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
UpdateMask: &field_mask.FieldMask{Paths: []string{"nonexistant_field"}},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "updated name"},
|
||||
Description: &wrapperspb.StringValue{Value: "updated desc"},
|
||||
},
|
||||
},
|
||||
errCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "Unset Name",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"name"},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Description: &wrapperspb.StringValue{Value: "ignored"},
|
||||
},
|
||||
},
|
||||
res: &pbs.UpdateAccountResponse{
|
||||
Item: &pb.Account{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
Description: &wrapperspb.StringValue{Value: "default"},
|
||||
Type: "password",
|
||||
Attributes: defaultAttributes,
|
||||
Scope: defaultScopeInfo,
|
||||
},
|
||||
},
|
||||
errCode: codes.OK,
|
||||
},
|
||||
{
|
||||
name: "Update Only Name",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"name"},
|
||||
},
|
||||
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: "password",
|
||||
Attributes: defaultAttributes,
|
||||
Scope: defaultScopeInfo,
|
||||
},
|
||||
},
|
||||
errCode: codes.OK,
|
||||
},
|
||||
{
|
||||
name: "Update Only Description",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"description"},
|
||||
},
|
||||
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: "password",
|
||||
Attributes: defaultAttributes,
|
||||
Scope: defaultScopeInfo,
|
||||
},
|
||||
},
|
||||
errCode: codes.OK,
|
||||
},
|
||||
// TODO: Updating a non existant auth_method should result in a NotFound exception but currently results in
|
||||
// the repoFn returning an internal error.
|
||||
{
|
||||
name: "Update a Non Existing Account",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
Id: password.AccountPrefix + "_DoesntExis",
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"description"},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "new"},
|
||||
Description: &wrapperspb.StringValue{Value: "desc"},
|
||||
},
|
||||
},
|
||||
errCode: codes.Internal,
|
||||
},
|
||||
{
|
||||
name: "Cant change Id",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"id"},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Id: password.AccountPrefix + "_somethinge",
|
||||
Name: &wrapperspb.StringValue{Value: "new"},
|
||||
Description: &wrapperspb.StringValue{Value: "new desc"},
|
||||
}},
|
||||
res: nil,
|
||||
errCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "Cant specify Created Time",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"created_time"},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
CreatedTime: ptypes.TimestampNow(),
|
||||
},
|
||||
},
|
||||
res: nil,
|
||||
errCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "Cant specify Updated Time",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"updated_time"},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
UpdatedTime: ptypes.TimestampNow(),
|
||||
},
|
||||
},
|
||||
res: nil,
|
||||
errCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "Cant specify Type",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
AuthMethodId: am.GetPublicId(),
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"type"},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Type: "oidc",
|
||||
},
|
||||
},
|
||||
res: nil,
|
||||
errCode: 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.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(auth.DisabledAuthTestContext(auth.WithScopeId(o.GetPublicId())), tc.req)
|
||||
assert.Equal(tc.errCode, status.Code(gErr), "UpdateAccount(%+v) got error %v, wanted %v", tc.req, gErr, tc.errCode)
|
||||
|
||||
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, err := ptypes.Timestamp(got.GetItem().GetUpdatedTime())
|
||||
require.NoError(err, "Error converting proto to timestamp")
|
||||
|
||||
created, err := ptypes.Timestamp(acc.GetCreatedTime())
|
||||
require.NoError(err, "Error converting proto to timestamp")
|
||||
|
||||
// Verify it is a auth_method updated after it was created
|
||||
assert.True(gotUpdateTime.After(created), "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()), "UpdateAccount(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func TestAuthenticate(t *testing.T) {
|
||||
passwordRepoFn := func() (*password.Repository, error) { return password.NewRepository(rw, rw, wrapper) }
|
||||
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
|
||||
|
||||
acct, err := password.NewAccount(am.GetPublicId(), testLoginName)
|
||||
acct, err := password.NewAccount(am.GetPublicId(), password.WithLoginName(testLoginName))
|
||||
require.NoError(t, err)
|
||||
|
||||
pwRepo, err := passwordRepoFn()
|
||||
@@ -178,7 +178,7 @@ func TestAuthenticate_AuthAccountConnectedToIamUser(t *testing.T) {
|
||||
iamRepoFn := func() (*iam.Repository, error) { return iam.NewRepository(rw, rw, wrapper) }
|
||||
|
||||
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
|
||||
acct, err := password.NewAccount(am.GetPublicId(), testLoginName)
|
||||
acct, err := password.NewAccount(am.GetPublicId(), password.WithLoginName(testLoginName))
|
||||
require.NoError(err)
|
||||
|
||||
pwRepo, err := passwordRepoFn()
|
||||
|
||||
@@ -112,6 +112,7 @@ func (s Service) UpdateAuthMethod(ctx context.Context, req *pbs.UpdateAuthMethod
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
return &pbs.UpdateAuthMethodResponse{Item: u}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/authmethods"
|
||||
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes"
|
||||
scopepb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers/authmethods"
|
||||
@@ -48,7 +48,7 @@ func TestGet(t *testing.T) {
|
||||
"min_login_name_length": structpb.NewNumberValue(3),
|
||||
}},
|
||||
Version: 1,
|
||||
Scope: &scopes.ScopeInfo{
|
||||
Scope: &scopepb.ScopeInfo{
|
||||
Id: o.GetPublicId(),
|
||||
Type: o.GetType(),
|
||||
},
|
||||
@@ -122,7 +122,7 @@ func TestList(t *testing.T) {
|
||||
Id: am.GetPublicId(),
|
||||
CreatedTime: am.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: am.GetUpdateTime().GetTimestamp(),
|
||||
Scope: &scopes.ScopeInfo{Id: oWithAuthMethods.GetPublicId(), Type: scope.Org.String()},
|
||||
Scope: &scopepb.ScopeInfo{Id: oWithAuthMethods.GetPublicId(), Type: scope.Org.String()},
|
||||
Version: 1,
|
||||
Type: "password",
|
||||
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
|
||||
@@ -138,7 +138,7 @@ func TestList(t *testing.T) {
|
||||
Id: aa.GetPublicId(),
|
||||
CreatedTime: aa.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: aa.GetUpdateTime().GetTimestamp(),
|
||||
Scope: &scopes.ScopeInfo{Id: oWithOtherAuthMethods.GetPublicId(), Type: scope.Org.String()},
|
||||
Scope: &scopepb.ScopeInfo{Id: oWithOtherAuthMethods.GetPublicId(), Type: scope.Org.String()},
|
||||
Version: 1,
|
||||
Type: "password",
|
||||
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
|
||||
@@ -312,7 +312,7 @@ func TestCreate(t *testing.T) {
|
||||
UpdatedTime: defaultAm.GetUpdateTime().GetTimestamp(),
|
||||
Name: &wrapperspb.StringValue{Value: "name"},
|
||||
Description: &wrapperspb.StringValue{Value: "desc"},
|
||||
Scope: &scopes.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String()},
|
||||
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: o.GetType()},
|
||||
Version: 1,
|
||||
Type: "password",
|
||||
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
|
||||
@@ -427,6 +427,8 @@ func TestUpdate(t *testing.T) {
|
||||
tested, err := authmethods.NewService(repoFn)
|
||||
require.NoError(t, err, "Error when getting new auth_method service.")
|
||||
|
||||
defaultScopeInfo := &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: o.GetType()}
|
||||
|
||||
freshAuthMethod := func() (*pb.AuthMethod, func()) {
|
||||
am, err := tested.CreateAuthMethod(auth.DisabledAuthTestContext(auth.WithScopeId(o.GetPublicId())),
|
||||
&pbs.CreateAuthMethodRequest{Item: &pb.AuthMethod{
|
||||
@@ -471,6 +473,7 @@ func TestUpdate(t *testing.T) {
|
||||
"min_password_length": structpb.NewNumberValue(8),
|
||||
"min_login_name_length": structpb.NewNumberValue(3),
|
||||
}},
|
||||
Scope: defaultScopeInfo,
|
||||
},
|
||||
},
|
||||
errCode: codes.OK,
|
||||
@@ -495,6 +498,7 @@ func TestUpdate(t *testing.T) {
|
||||
"min_password_length": structpb.NewNumberValue(8),
|
||||
"min_login_name_length": structpb.NewNumberValue(3),
|
||||
}},
|
||||
Scope: defaultScopeInfo,
|
||||
},
|
||||
},
|
||||
errCode: codes.OK,
|
||||
@@ -549,6 +553,7 @@ func TestUpdate(t *testing.T) {
|
||||
"min_password_length": structpb.NewNumberValue(8),
|
||||
"min_login_name_length": structpb.NewNumberValue(3),
|
||||
}},
|
||||
Scope: defaultScopeInfo,
|
||||
},
|
||||
},
|
||||
errCode: codes.OK,
|
||||
@@ -573,6 +578,7 @@ func TestUpdate(t *testing.T) {
|
||||
"min_password_length": structpb.NewNumberValue(8),
|
||||
"min_login_name_length": structpb.NewNumberValue(3),
|
||||
}},
|
||||
Scope: defaultScopeInfo,
|
||||
},
|
||||
},
|
||||
errCode: codes.OK,
|
||||
@@ -597,6 +603,7 @@ func TestUpdate(t *testing.T) {
|
||||
"min_password_length": structpb.NewNumberValue(8),
|
||||
"min_login_name_length": structpb.NewNumberValue(3),
|
||||
}},
|
||||
Scope: defaultScopeInfo,
|
||||
},
|
||||
},
|
||||
errCode: codes.OK,
|
||||
|
||||
Reference in new issue
Block a user