2020-08-19 16:47:18 -04:00
// Copyright (c) HashiCorp, Inc.
2021-07-29 18:52:56 -06:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-07-24 10:23:35 -04:00
package password
import (
"context"
2023-12-21 20:44:16 -08:00
"database/sql"
2020-07-24 10:23:35 -04:00
"fmt"
"regexp"
2020-08-11 10:12:36 -04:00
"strings"
2023-12-21 20:44:16 -08:00
"time"
2020-07-24 10:23:35 -04:00
2023-03-10 15:06:39 -05:00
"github.com/hashicorp/boundary/globals"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/db"
2023-12-21 20:44:16 -08:00
"github.com/hashicorp/boundary/internal/db/timestamp"
2020-11-05 16:54:03 -05:00
"github.com/hashicorp/boundary/internal/errors"
2020-08-19 16:47:18 -04:00
"github.com/hashicorp/boundary/internal/kms"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/oplog"
2022-08-24 14:11:49 -04:00
"github.com/hashicorp/go-dbw"
2020-07-24 10:23:35 -04:00
)
2020-08-11 10:12:36 -04:00
// CreateAccount inserts a into the repository and returns a new Account
2021-06-04 09:35:51 -05:00
// containing the account's PublicId. a is not changed. a must contain a valid
// AuthMethodId. a must not contain a PublicId. The PublicId is generated and
// assigned by this method.
2020-07-24 10:23:35 -04:00
//
2020-08-11 17:25:00 -04:00
// a must contain a valid LoginName. a.LoginName must be unique within
2020-08-11 10:12:36 -04:00
// a.AuthMethodId.
//
2021-06-04 09:35:51 -05:00
// WithPassword and WithPublicId are the only valid options. All other options
// are ignored.
2020-07-31 11:18:41 -04:00
//
2020-07-24 10:23:35 -04:00
// Both a.Name and a.Description are optional. If a.Name is set, it must be
// unique within a.AuthMethodId.
2020-08-19 16:47:18 -04:00
func ( r * Repository ) CreateAccount ( ctx context . Context , scopeId string , a * Account , opt ... Option ) ( * Account , error ) {
2021-02-11 13:32:45 -06:00
const op = "password.(Repository).CreateAccount"
2020-07-24 10:23:35 -04:00
if a == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing Account" )
2020-07-24 10:23:35 -04:00
}
if a . Account == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing embedded Account" )
2020-07-24 10:23:35 -04:00
}
if a . AuthMethodId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing auth method id" )
2020-07-24 10:23:35 -04:00
}
if a . PublicId != "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "public id must be empty" )
2020-07-24 10:23:35 -04:00
}
2020-08-19 16:47:18 -04:00
if scopeId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing scope id" )
2020-08-19 16:47:18 -04:00
}
2020-08-11 17:25:00 -04:00
if ! validLoginName ( a . LoginName ) {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "login name must be all-lowercase alphanumeric, period or hyphen. got: %s" , a . LoginName ))
2020-07-24 10:23:35 -04:00
}
2020-07-31 11:18:41 -04:00
cc , err := r . currentConfig ( ctx , a . AuthMethodId )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "retrieve current configuration" ))
2020-07-31 11:18:41 -04:00
}
2020-08-11 17:25:00 -04:00
if cc . MinLoginNameLength > len ( a . LoginName ) {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . TooShort , op , fmt . Sprintf ( "username: %s, must be longer than %d" , a . LoginName , cc . MinLoginNameLength ))
2020-07-31 11:18:41 -04:00
}
2022-10-25 21:33:31 -04:00
opts := GetOpts ( opt ... )
2021-06-04 09:35:51 -05:00
2020-07-24 10:23:35 -04:00
a = a . clone ()
2021-06-04 09:35:51 -05:00
if opts . withPublicId != "" {
2023-03-10 15:06:39 -05:00
if ! strings . HasPrefix ( opts . withPublicId , globals . PasswordAccountPrefix + "_" ) {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "chosen account id does not have a valid prefix" )
2021-06-04 09:35:51 -05:00
}
a . PublicId = opts . withPublicId
} else {
2023-06-06 17:09:08 -04:00
id , err := newAccountId ( ctx )
2021-06-04 09:35:51 -05:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2021-06-04 09:35:51 -05:00
}
a . PublicId = id
2020-07-24 10:23:35 -04:00
}
2020-07-31 11:18:41 -04:00
var cred * Argon2Credential
if opts . withPassword {
if cc . MinPasswordLength > len ( opts . password ) {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . PasswordTooShort , op , fmt . Sprintf ( "must be longer than %v" , cc . MinPasswordLength ))
2020-07-31 11:18:41 -04:00
}
2023-06-06 17:09:08 -04:00
if cred , err = newArgon2Credential ( ctx , a . PublicId , opts . password , cc . argon2 ()); err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-07-31 11:18:41 -04:00
}
}
2020-08-19 16:47:18 -04:00
oplogWrapper , err := r . kms . GetWrapper ( ctx , scopeId , kms . KeyPurposeOplog )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get oplog wrapper" ), errors . WithCode ( errors . Encrypt ))
2020-08-19 16:47:18 -04:00
}
databaseWrapper , err := r . kms . GetWrapper ( ctx , scopeId , kms . KeyPurposeDatabase )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get database wrapper" ), errors . WithCode ( errors . Encrypt ))
2020-08-19 16:47:18 -04:00
}
2020-07-31 11:18:41 -04:00
var newCred * Argon2Credential
2020-07-24 10:23:35 -04:00
var newAccount * Account
_ , err = r . writer . DoTx ( ctx , db . StdRetryCnt , db . ExpBackoff {},
func ( _ db . Reader , w db . Writer ) error {
newAccount = a . clone ()
2020-08-19 16:47:18 -04:00
if err := w . Create ( ctx , newAccount , db . WithOplog ( oplogWrapper , a . oplog ( oplog . OpType_OP_TYPE_CREATE ))); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2020-07-24 10:23:35 -04:00
}
2020-07-31 11:18:41 -04:00
if cred != nil {
newCred = cred . clone ()
2020-08-19 16:47:18 -04:00
if err := newCred . encrypt ( ctx , databaseWrapper ); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2020-07-31 11:18:41 -04:00
}
2020-08-19 16:47:18 -04:00
if err := w . Create ( ctx , newCred , db . WithOplog ( oplogWrapper , cred . oplog ( oplog . OpType_OP_TYPE_CREATE ))); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2020-07-31 11:18:41 -04:00
}
}
2020-07-24 10:23:35 -04:00
return nil
},
)
if err != nil {
2020-11-05 16:54:03 -05:00
if errors . IsUniqueError ( err ) {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . NotUnique , op , fmt . Sprintf ( "in auth method %s: name %q or loginName %q already exists" ,
2021-02-11 13:32:45 -06:00
a . AuthMethodId , a . Name , a . LoginName ))
2020-07-24 10:23:35 -04:00
}
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( a . AuthMethodId ))
2020-07-24 10:23:35 -04:00
}
return newAccount , nil
}
2020-07-24 11:38:27 -07:00
// LookupAccount will look up an account in the repository. If the account is not
// found, it will return nil, nil. All options are ignored.
func ( r * Repository ) LookupAccount ( ctx context . Context , withPublicId string , opt ... Option ) ( * Account , error ) {
2021-02-11 13:32:45 -06:00
const op = "password.(Repository).LookupAccount"
2020-07-24 11:38:27 -07:00
if withPublicId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidPublicId , op , "missing public id" )
2020-07-24 11:38:27 -07:00
}
a := allocAccount ()
a . PublicId = withPublicId
2020-08-17 09:37:15 -07:00
if err := r . reader . LookupByPublicId ( ctx , a ); err != nil {
2020-12-21 10:07:34 -08:00
if errors . IsNotFoundError ( err ) {
2020-07-24 11:38:27 -07:00
return nil , nil
}
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , withPublicId )))
2020-07-24 11:38:27 -07:00
}
2020-08-17 09:37:15 -07:00
return a , nil
2020-07-24 11:38:27 -07:00
}
2023-12-21 20:44:16 -08:00
// listAccounts returns a slice of accounts in the auth method.
// Supported options:
// - WithLimit which overrides the limit set in the Repository object
// - WithStartPageAfterItem which sets where to start listing from
func ( r * Repository ) listAccounts ( ctx context . Context , withAuthMethodId string , opt ... Option ) ([] * Account , time . Time , error ) {
const op = "password.(Repository).listAccounts"
2020-07-24 11:38:27 -07:00
if withAuthMethodId == "" {
2023-12-21 20:44:16 -08:00
return nil , time . Time {}, errors . New ( ctx , errors . InvalidParameter , op , "missing auth method id" )
2020-07-24 11:38:27 -07:00
}
2022-10-25 21:33:31 -04:00
opts := GetOpts ( opt ... )
2023-12-21 20:44:16 -08:00
2020-07-24 11:38:27 -07:00
limit := r . defaultLimit
if opts . withLimit != 0 {
// non-zero signals an override of the default limit for the repo.
limit = opts . withLimit
}
2023-12-21 20:44:16 -08:00
var args [] any
whereClause := "auth_method_id = @auth_method_id"
args = append ( args , sql . Named ( "auth_method_id" , withAuthMethodId ))
if opts . withStartPageAfterItem != nil {
whereClause = fmt . Sprintf ( "(create_time, public_id) < (@last_item_create_time, @last_item_id) and %s" , whereClause )
args = append ( args ,
sql . Named ( "last_item_create_time" , opts . withStartPageAfterItem . GetCreateTime ()),
sql . Named ( "last_item_id" , opts . withStartPageAfterItem . GetPublicId ()),
)
2020-07-24 11:38:27 -07:00
}
2023-12-21 20:44:16 -08:00
dbOpts := [] db . Option { db . WithLimit ( limit ), db . WithOrder ( "create_time desc, public_id desc" )}
return r . queryAccounts ( ctx , whereClause , args , dbOpts ... )
}
// listAccountsRefresh returns a slice of accounts in the auth method.
// Supported options:
// - WithLimit which overrides the limit set in the Repository object
// - WithStartPageAfterItem which sets where to start listing from
func ( r * Repository ) listAccountsRefresh ( ctx context . Context , withAuthMethodId string , updatedAfter time . Time , opt ... Option ) ([] * Account , time . Time , error ) {
const op = "password.(Repository).listAccountsRefresh"
switch {
case withAuthMethodId == "" :
return nil , time . Time {}, errors . New ( ctx , errors . InvalidParameter , op , "missing auth method id" )
case updatedAfter . IsZero ():
return nil , time . Time {}, errors . New ( ctx , errors . InvalidParameter , op , "missing updated after time" )
}
opts := GetOpts ( opt ... )
limit := r . defaultLimit
if opts . withLimit != 0 {
// non-zero signals an override of the default limit for the repo.
limit = opts . withLimit
}
var args [] any
whereClause := "update_time > @updated_after_time and auth_method_id = @auth_method_id"
args = append ( args ,
sql . Named ( "updated_after_time" , timestamp . New ( updatedAfter )),
sql . Named ( "auth_method_id" , withAuthMethodId ),
)
if opts . withStartPageAfterItem != nil {
whereClause = fmt . Sprintf ( "(update_time, public_id) < (@last_item_update_time, @last_item_id) and %s" , whereClause )
args = append ( args ,
sql . Named ( "last_item_update_time" , opts . withStartPageAfterItem . GetUpdateTime ()),
sql . Named ( "last_item_id" , opts . withStartPageAfterItem . GetPublicId ()),
)
}
dbOpts := [] db . Option { db . WithLimit ( limit ), db . WithOrder ( "update_time desc, public_id desc" )}
return r . queryAccounts ( ctx , whereClause , args , dbOpts ... )
}
func ( r * Repository ) queryAccounts ( ctx context . Context , whereClause string , args [] any , opt ... db . Option ) ([] * Account , time . Time , error ) {
const op = "password.(Repository).queryAccounts"
var accts [] * Account
var transactionTimestamp time . Time
if _ , err := r . writer . DoTx ( ctx , db . StdRetryCnt , db . ExpBackoff {}, func ( rd db . Reader , w db . Writer ) error {
var inAccts [] * Account
if err := rd . SearchWhere ( ctx , & inAccts , whereClause , args , opt ... ); err != nil {
return errors . Wrap ( ctx , err , op )
}
accts = inAccts
var err error
transactionTimestamp , err = rd . Now ( ctx )
return err
}); err != nil {
return nil , time . Time {}, errors . Wrap ( ctx , err , op )
}
return accts , transactionTimestamp , nil
2020-07-24 11:38:27 -07:00
}
2020-07-28 09:06:50 -07:00
// DeleteAccount deletes the account for the provided id from the repository returning a count of the
// number of records deleted. All options are ignored.
2020-08-19 16:47:18 -04:00
func ( r * Repository ) DeleteAccount ( ctx context . Context , scopeId , withPublicId string , opt ... Option ) ( int , error ) {
2021-02-11 13:32:45 -06:00
const op = "password.(Repository).DeleteAccount"
2020-07-28 09:06:50 -07:00
if withPublicId == "" {
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . New ( ctx , errors . InvalidPublicId , op , "missing public id" )
2020-07-28 09:06:50 -07:00
}
2020-08-19 16:47:18 -04:00
if scopeId == "" {
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing scope id" )
2020-08-19 16:47:18 -04:00
}
2020-07-28 09:06:50 -07:00
ac := allocAccount ()
ac . PublicId = withPublicId
2020-08-19 16:47:18 -04:00
oplogWrapper , err := r . kms . GetWrapper ( ctx , scopeId , kms . KeyPurposeOplog )
if err != nil {
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithCode ( errors . Encrypt ), errors . WithMsg ( "unable to get oplog wrapper" ))
2020-08-19 16:47:18 -04:00
}
2020-07-28 09:06:50 -07:00
var rowsDeleted int
2020-08-19 16:47:18 -04:00
_ , err = r . writer . DoTx (
2020-07-28 09:06:50 -07:00
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( _ db . Reader , w db . Writer ) ( err error ) {
metadata := ac . oplog ( oplog . OpType_OP_TYPE_DELETE )
dAc := ac . clone ()
2020-08-19 16:47:18 -04:00
rowsDeleted , err = w . Delete ( ctx , dAc , db . WithOplog ( oplogWrapper , metadata ))
2021-03-05 06:34:06 -08:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2020-07-28 09:06:50 -07:00
}
2021-03-05 06:34:06 -08:00
if rowsDeleted > 1 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , "more than 1 resource would have been deleted" )
2021-03-05 06:34:06 -08:00
}
return nil
2020-07-28 09:06:50 -07:00
},
)
if err != nil {
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( withPublicId ))
2020-07-28 09:06:50 -07:00
}
return rowsDeleted , nil
}
2020-12-02 09:56:19 -08:00
var reInvalidLoginName = regexp . MustCompile ( "[^a-z0-9.-]" )
2020-07-24 10:23:35 -04:00
2020-08-11 17:25:00 -04:00
func validLoginName ( u string ) bool {
2020-07-24 10:23:35 -04:00
if u == "" {
return false
}
2021-10-21 15:02:15 +03:00
return ! reInvalidLoginName . MatchString ( u )
2020-07-24 10:23:35 -04:00
}
2020-08-11 10:12:36 -04:00
// UpdateAccount updates the repository entry for a.PublicId with the
// values in a for the fields listed in fieldMaskPaths. It returns a new
// Account containing the updated values and a count of the number of
// records updated. a is not changed.
//
// a must contain a valid PublicId. Only a.Name, a.Description and
2020-08-11 17:25:00 -04:00
// a.LoginName can be updated. If a.Name is set to a non-empty string, it
// must be unique within a.AuthMethodId. If a.LoginName is set to a
2020-08-11 10:12:36 -04:00
// non-empty string, it must be unique within a.AuthMethodId.
//
// An attribute of a will be set to NULL in the database if the attribute
2020-08-11 17:25:00 -04:00
// in a is the zero value and it is included in fieldMaskPaths. a.LoginName
2020-08-11 10:12:36 -04:00
// cannot be set to NULL.
2020-08-19 16:47:18 -04:00
func ( r * Repository ) UpdateAccount ( ctx context . Context , scopeId string , a * Account , version uint32 , fieldMaskPaths [] string , opt ... Option ) ( * Account , int , error ) {
2021-02-11 13:32:45 -06:00
const op = "password.(Repository).UpdateAccount"
2020-08-11 10:12:36 -04:00
if a == nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing Account" )
2020-08-11 10:12:36 -04:00
}
if a . Account == nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing embedded Account" )
2020-08-11 10:12:36 -04:00
}
if a . PublicId == "" {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidPublicId , op , "missing public id" )
2020-08-11 10:12:36 -04:00
}
2020-08-11 12:28:11 -04:00
if version == 0 {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing version" )
2020-08-11 12:28:11 -04:00
}
2020-08-19 16:47:18 -04:00
if scopeId == "" {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing scope id" )
2020-08-19 16:47:18 -04:00
}
2020-08-11 10:12:36 -04:00
2020-08-11 17:25:00 -04:00
var changeLoginName bool
2020-08-11 10:12:36 -04:00
for _ , f := range fieldMaskPaths {
switch {
case strings . EqualFold ( "Name" , f ):
case strings . EqualFold ( "Description" , f ):
2020-08-11 17:25:00 -04:00
case strings . EqualFold ( "LoginName" , f ):
if ! validLoginName ( a . LoginName ) {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op ,
2021-02-11 13:32:45 -06:00
fmt . Sprintf ( "invalid username: must be all-lowercase alphanumeric, period or hyphen, got %s" , a . LoginName ))
2020-08-11 10:12:36 -04:00
}
2020-08-11 17:25:00 -04:00
changeLoginName = true
2020-08-11 10:12:36 -04:00
default :
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidFieldMask , op , f )
2020-08-11 10:12:36 -04:00
}
}
var dbMask , nullFields [] string
2022-08-24 14:11:49 -04:00
dbMask , nullFields = dbw . BuildUpdatePaths (
2022-10-25 18:29:38 -05:00
map [ string ] any {
2020-08-11 10:12:36 -04:00
"Name" : a . Name ,
"Description" : a . Description ,
2020-08-11 17:25:00 -04:00
"LoginName" : a . LoginName ,
2020-08-11 10:12:36 -04:00
},
fieldMaskPaths ,
2020-09-19 10:31:49 -04:00
nil ,
2020-08-11 10:12:36 -04:00
)
if len ( dbMask ) == 0 && len ( nullFields ) == 0 {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . EmptyFieldMask , op , "missing field mask" )
2020-08-11 10:12:36 -04:00
}
2020-08-11 17:25:00 -04:00
if changeLoginName {
2020-08-11 10:12:36 -04:00
cc , err := r . currentConfigForAccount ( ctx , a . PublicId )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "retrieve current configuration" ))
2020-08-11 10:12:36 -04:00
}
2020-08-11 17:25:00 -04:00
if cc . MinLoginNameLength > len ( a . LoginName ) {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . TooShort , op ,
2021-02-11 13:32:45 -06:00
fmt . Sprintf ( "username: %s, must be longer than %v" , a . LoginName , cc . MinLoginNameLength ))
2020-08-11 10:12:36 -04:00
}
}
2020-08-19 16:47:18 -04:00
oplogWrapper , err := r . kms . GetWrapper ( ctx , scopeId , kms . KeyPurposeOplog )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithCode ( errors . Encrypt ),
2021-02-11 13:32:45 -06:00
errors . WithMsg (( "unable to get oplog wrapper" )))
2020-08-19 16:47:18 -04:00
}
2020-08-11 10:12:36 -04:00
a = a . clone ()
metadata := a . oplog ( oplog . OpType_OP_TYPE_UPDATE )
var rowsUpdated int
var returnedAccount * Account
2020-08-19 16:47:18 -04:00
_ , err = r . writer . DoTx ( ctx , db . StdRetryCnt , db . ExpBackoff {},
2020-08-11 10:12:36 -04:00
func ( _ db . Reader , w db . Writer ) error {
returnedAccount = a . clone ()
var err error
2020-08-19 16:47:18 -04:00
rowsUpdated , err = w . Update ( ctx , returnedAccount , dbMask , nullFields , db . WithOplog ( oplogWrapper , metadata ), db . WithVersion ( & version ))
2021-03-05 06:34:06 -08:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2020-08-11 10:12:36 -04:00
}
2021-03-05 06:34:06 -08:00
if rowsUpdated > 1 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , "more than 1 resource would have been updated" )
2021-03-05 06:34:06 -08:00
}
return nil
2020-08-11 10:12:36 -04:00
},
)
if err != nil {
2020-11-05 16:54:03 -05:00
if errors . IsUniqueError ( err ) {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . NotUnique , op ,
2021-02-11 13:32:45 -06:00
fmt . Sprintf ( "name %s already exists: %s" , a . Name , a . PublicId ))
2020-08-11 10:12:36 -04:00
}
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( a . PublicId ))
2020-08-11 10:12:36 -04:00
}
return returnedAccount , rowsUpdated , nil
}
2023-12-21 20:44:16 -08:00
// listDeletedAccountIds lists the public IDs of any accounts deleted since the timestamp provided,
// and the timestamp of the transaction within which the accounts were listed.
func ( r * Repository ) listDeletedAccountIds ( ctx context . Context , since time . Time ) ([] string , time . Time , error ) {
const op = "password.(Repository).listDeletedAccountIds"
var deleteAccounts [] * deletedAccount
var transactionTimestamp time . Time
if _ , err := r . writer . DoTx ( ctx , db . StdRetryCnt , db . ExpBackoff {}, func ( r db . Reader , _ db . Writer ) error {
if err := r . SearchWhere ( ctx , & deleteAccounts , "delete_time >= ?" , [] any { since }); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query deleted accounts" ))
}
var err error
transactionTimestamp , err = r . Now ( ctx )
if err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to get transaction timestamp" ))
}
return nil
}); err != nil {
return nil , time . Time {}, err
}
var accountIds [] string
for _ , a := range deleteAccounts {
accountIds = append ( accountIds , a . PublicId )
}
return accountIds , transactionTimestamp , nil
}
// estimatedAccountCount returns an estimate of the total number of accounts.
func ( r * Repository ) estimatedAccountCount ( ctx context . Context ) ( int , error ) {
const op = "password.(Repository).estimatedAccountCount"
rows , err := r . reader . Query ( ctx , estimateCountAccounts , nil )
if err != nil {
return 0 , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query ldap account counts" ))
}
var count int
for rows . Next () {
if err := r . reader . ScanRows ( ctx , rows , & count ); err != nil {
return 0 , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query ldap account counts" ))
}
}
2024-01-22 14:03:40 -05:00
if err := rows . Err (); err != nil {
return 0 , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query ldap account counts" ))
}
2023-12-21 20:44:16 -08:00
return count , nil
}