2020-07-24 10:23:35 -04:00
package password
import (
"context"
"fmt"
2020-07-31 14:35:56 -07:00
"strings"
2020-07-24 10:23:35 -04:00
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/db"
dbcommon "github.com/hashicorp/boundary/internal/db/common"
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"
2020-07-24 10:23:35 -04:00
)
// CreateAuthMethod inserts m into the repository and returns a new
// AuthMethod containing the auth method's PublicId. m is not changed. m must
// contain a valid ScopeId. m must not contain a PublicId. The PublicId is
// generated and assigned by this method.
//
2020-08-07 10:18:40 -07:00
// WithConfiguration and WithPublicId are the only valid options. All other
// options are ignored.
2020-07-29 07:58:19 -04:00
//
2020-07-24 10:23:35 -04:00
// Both m.Name and m.Description are optional. If m.Name is set, it must be
// unique within m.ScopeId.
func ( r * Repository ) CreateAuthMethod ( ctx context . Context , m * AuthMethod , opt ... Option ) ( * AuthMethod , error ) {
if m == nil {
2020-11-05 16:54:03 -05:00
return nil , fmt . Errorf ( "create: password auth method: %w" , errors . ErrInvalidParameter )
2020-07-24 10:23:35 -04:00
}
if m . AuthMethod == nil {
2020-11-05 16:54:03 -05:00
return nil , fmt . Errorf ( "create: password auth method: embedded AuthMethod: %w" , errors . ErrInvalidParameter )
2020-07-24 10:23:35 -04:00
}
if m . ScopeId == "" {
2020-11-05 16:54:03 -05:00
return nil , fmt . Errorf ( "create: password auth method: no scope id: %w" , errors . ErrInvalidParameter )
2020-07-24 10:23:35 -04:00
}
if m . PublicId != "" {
2020-11-05 16:54:03 -05:00
return nil , fmt . Errorf ( "create: password auth method: public id not empty: %w" , errors . ErrInvalidParameter )
2020-07-24 10:23:35 -04:00
}
m = m . clone ()
2020-08-07 10:18:40 -07:00
opts := getOpts ( opt ... )
if opts . withPublicId != "" {
if ! strings . HasPrefix ( opts . withPublicId , AuthMethodPrefix + "_" ) {
2020-11-05 16:54:03 -05:00
return nil , fmt . Errorf ( "create: password auth method: passed-in public ID %q has wrong prefix, should be %q: %w" , opts . withPublicId , AuthMethodPrefix , errors . ErrInvalidPublicId )
2020-08-07 10:18:40 -07:00
}
m . PublicId = opts . withPublicId
} else {
id , err := newAuthMethodId ()
if err != nil {
return nil , fmt . Errorf ( "create: password auth method: %w" , err )
}
m . PublicId = id
2020-07-24 10:23:35 -04:00
}
2020-07-29 07:58:19 -04:00
c , ok := opts . withConfig .( * Argon2Configuration )
if ! ok {
return nil , fmt . Errorf ( "create: password auth method: unknown configuration: %w" , ErrUnsupportedConfiguration )
}
if err := c . validate (); err != nil {
return nil , fmt . Errorf ( "create: password auth method: %w" , err )
}
2020-08-07 10:18:40 -07:00
var err error
2020-07-29 07:58:19 -04:00
c . PrivateId , err = newArgon2ConfigurationId ()
if err != nil {
return nil , fmt . Errorf ( "create: password auth method: %w" , err )
}
m . PasswordConfId , c . PasswordMethodId = c . PrivateId , m . PublicId
2020-08-19 16:47:18 -04:00
oplogWrapper , err := r . kms . GetWrapper ( ctx , m . GetScopeId (), kms . KeyPurposeOplog )
if err != nil {
return nil , fmt . Errorf ( "create: password auth method: unable to get oplog wrapper: %w" , err )
}
2020-07-24 10:23:35 -04:00
var newAuthMethod * AuthMethod
2020-07-29 07:58:19 -04:00
var newArgon2Conf * Argon2Configuration
2020-07-24 10:23:35 -04:00
_ , err = r . writer . DoTx ( ctx , db . StdRetryCnt , db . ExpBackoff {},
func ( _ db . Reader , w db . Writer ) error {
2020-07-29 07:58:19 -04:00
newArgon2Conf = c . clone ()
2020-08-19 16:47:18 -04:00
if err := w . Create ( ctx , newArgon2Conf , db . WithOplog ( oplogWrapper , c . oplog ( oplog . OpType_OP_TYPE_CREATE ))); err != nil {
2020-07-29 07:58:19 -04:00
return err
}
2020-07-24 10:23:35 -04:00
newAuthMethod = m . clone ()
2020-08-19 16:47:18 -04:00
return w . Create ( ctx , newAuthMethod , db . WithOplog ( oplogWrapper , m . oplog ( oplog . OpType_OP_TYPE_CREATE )))
2020-07-24 10:23:35 -04:00
},
)
if err != nil {
2020-11-05 16:54:03 -05:00
if errors . IsUniqueError ( err ) {
2020-07-24 10:23:35 -04:00
return nil , fmt . Errorf ( "create: password auth method: in scope: %s: name %s already exists: %w" ,
2020-11-05 16:54:03 -05:00
m . ScopeId , m . Name , errors . ErrNotUnique )
2020-07-24 10:23:35 -04:00
}
return nil , fmt . Errorf ( "create: password auth method: in scope: %s: %w" , m . ScopeId , err )
}
return newAuthMethod , nil
}
2020-07-31 14:35:56 -07:00
// LookupAuthMethod will look up an auth method in the repository. If the auth method is not
// found, it will return nil, nil. All options are ignored.
func ( r * Repository ) LookupAuthMethod ( ctx context . Context , publicId string , opt ... Option ) ( * AuthMethod , error ) {
if publicId == "" {
2020-11-05 16:54:03 -05:00
return nil , fmt . Errorf ( "lookup: password auth method: missing public id %w" , errors . ErrInvalidParameter )
2020-07-31 14:35:56 -07:00
}
a := allocAuthMethod ()
a . PublicId = publicId
if err := r . reader . LookupByPublicId ( ctx , & a ); err != nil {
2020-12-21 10:07:34 -08:00
if errors . IsNotFoundError ( err ) {
2020-07-31 14:35:56 -07:00
return nil , nil
}
return nil , fmt . Errorf ( "lookup: password auth method: failed %w for %s" , err , publicId )
}
return & a , nil
}
// ListAuthMethods returns a slice of AuthMethods for the scopeId. WithLimit is the only option supported.
2021-01-25 19:37:40 -05:00
func ( r * Repository ) ListAuthMethods ( ctx context . Context , scopeIds [] string , opt ... Option ) ([] * AuthMethod , error ) {
if len ( scopeIds ) == 0 {
2020-11-05 16:54:03 -05:00
return nil , fmt . Errorf ( "list: password auth method: missing scope id: %w" , errors . ErrInvalidParameter )
2020-07-31 14:35:56 -07:00
}
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 authMethods [] * AuthMethod
2021-01-25 19:37:40 -05:00
err := r . reader . SearchWhere ( ctx , & authMethods , "scope_id in (?)" , [] interface {}{ scopeIds }, db . WithLimit ( limit ))
2020-07-31 14:35:56 -07:00
if err != nil {
return nil , fmt . Errorf ( "list: password auth method: %w" , err )
}
return authMethods , nil
}
// DeleteAuthMethod deletes the auth method 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 ) DeleteAuthMethod ( ctx context . Context , scopeId , publicId string , opt ... Option ) ( int , error ) {
2020-07-31 14:35:56 -07:00
if publicId == "" {
2020-11-05 16:54:03 -05:00
return db . NoRowsAffected , fmt . Errorf ( "delete: password auth method: missing public id: %w" , errors . ErrInvalidParameter )
2020-07-31 14:35:56 -07:00
}
am := allocAuthMethod ()
am . PublicId = publicId
2020-08-19 16:47:18 -04:00
oplogWrapper , err := r . kms . GetWrapper ( ctx , scopeId , kms . KeyPurposeOplog )
if err != nil {
return db . NoRowsAffected , fmt . Errorf ( "delete: password auth method: unable to get oplog wrapper: %w" , err )
}
2020-07-31 14:35:56 -07:00
var rowsDeleted int
2020-08-19 16:47:18 -04:00
_ , err = r . writer . DoTx (
2020-07-31 14:35:56 -07:00
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( _ db . Reader , w db . Writer ) ( err error ) {
metadata := am . oplog ( oplog . OpType_OP_TYPE_DELETE )
dAc := am . clone ()
2020-08-19 16:47:18 -04:00
rowsDeleted , err = w . Delete ( ctx , dAc , db . WithOplog ( oplogWrapper , metadata ))
2020-07-31 14:35:56 -07:00
if err == nil && rowsDeleted > 1 {
2020-11-05 16:54:03 -05:00
return errors . ErrMultipleRecords
2020-07-31 14:35:56 -07:00
}
return err
},
)
if err != nil {
return db . NoRowsAffected , fmt . Errorf ( "delete: password auth method: %s: %w" , publicId , err )
}
return rowsDeleted , nil
}
2020-08-11 17:25:00 -04:00
// TODO: Fix the MinPasswordLength and MinLoginNameLength update path so they dont have
2020-07-31 14:35:56 -07:00
// to rely on the response of NewAuthMethod but instead can be unset in order to be
// set to the default values.
// UpdateAuthMethod will update an auth method in the repository and return
2020-08-11 17:25:00 -04:00
// the written auth method. MinPasswordLength and MinLoginNameLength should
2020-07-31 14:35:56 -07:00
// not be set to null, but instead use the default values returned by
// NewAuthMethod. fieldMaskPaths provides field_mask.proto paths for fields
// that should be updated. Fields will be set to NULL if the field is a zero
// value and included in fieldMask. Name, Description, MinPasswordLength,
2020-08-11 17:25:00 -04:00
// and MinLoginNameLength are the only updatable fields, If no updatable fields
2020-07-31 14:35:56 -07:00
// are included in the fieldMaskPaths, then an error is returned.
2020-08-11 11:19:29 -04:00
func ( r * Repository ) UpdateAuthMethod ( ctx context . Context , authMethod * AuthMethod , version uint32 , fieldMaskPaths [] string , opt ... Option ) ( * AuthMethod , int , error ) {
2020-07-31 14:35:56 -07:00
if authMethod == nil {
2020-11-05 16:54:03 -05:00
return nil , db . NoRowsAffected , fmt . Errorf ( "update: password auth method: missing authMethod: %w" , errors . ErrInvalidParameter )
2020-07-31 14:35:56 -07:00
}
if authMethod . PublicId == "" {
2020-11-05 16:54:03 -05:00
return nil , db . NoRowsAffected , fmt . Errorf ( "update: password auth method: missing authMethod public id: %w" , errors . ErrInvalidParameter )
2020-07-31 14:35:56 -07:00
}
2020-08-19 16:47:18 -04:00
if authMethod . ScopeId == "" {
2020-11-05 16:54:03 -05:00
return nil , db . NoRowsAffected , fmt . Errorf ( "update: password auth method: scope id empty: %w" , errors . ErrInvalidParameter )
2020-08-19 16:47:18 -04:00
}
2020-07-31 14:35:56 -07: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 ( "MinLoginNameLength" , f ):
2020-07-31 14:35:56 -07:00
case strings . EqualFold ( "MinPasswordLength" , f ):
default :
2020-11-05 16:54:03 -05:00
return nil , db . NoRowsAffected , fmt . Errorf ( "update: password auth method: field: %s: %w" , f , errors . ErrInvalidFieldMask )
2020-07-31 14:35:56 -07:00
}
}
var dbMask , nullFields [] string
dbMask , nullFields = dbcommon . BuildUpdatePaths (
map [ string ] interface {}{
2020-08-11 17:25:00 -04:00
"Name" : authMethod . Name ,
"Description" : authMethod . Description ,
"MinPasswordLength" : authMethod . MinPasswordLength ,
"MinLoginNameLength" : authMethod . MinLoginNameLength ,
2020-07-31 14:35:56 -07:00
},
fieldMaskPaths ,
2020-09-19 10:31:49 -04:00
nil ,
2020-07-31 14:35:56 -07:00
)
if len ( dbMask ) == 0 && len ( nullFields ) == 0 {
2020-11-05 16:54:03 -05:00
return nil , db . NoRowsAffected , fmt . Errorf ( "update: password auth method: %w" , errors . ErrEmptyFieldMask )
2020-07-31 14:35:56 -07:00
}
2020-08-19 16:47:18 -04:00
oplogWrapper , err := r . kms . GetWrapper ( ctx , authMethod . ScopeId , kms . KeyPurposeOplog )
if err != nil {
return nil , db . NoRowsAffected , fmt . Errorf ( "update: password auth method: unable to get oplog wrapper: %w" , err )
}
2020-07-31 14:35:56 -07:00
upAuthMethod := authMethod . clone ()
var rowsUpdated int
2020-08-19 16:47:18 -04:00
_ , err = r . writer . DoTx (
2020-07-31 14:35:56 -07:00
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( _ db . Reader , w db . Writer ) error {
2020-08-11 11:19:29 -04:00
dbOpts := [] db . Option {
2020-08-19 16:47:18 -04:00
db . WithOplog ( oplogWrapper , upAuthMethod . oplog ( oplog . OpType_OP_TYPE_UPDATE )),
2020-08-11 11:19:29 -04:00
db . WithVersion ( & version ),
}
2020-07-31 14:35:56 -07:00
var err error
rowsUpdated , err = w . Update (
ctx ,
upAuthMethod ,
dbMask ,
nullFields ,
dbOpts ... ,
)
if err == nil && rowsUpdated > 1 {
2020-11-05 16:54:03 -05:00
return errors . ErrMultipleRecords
2020-07-31 14:35:56 -07:00
}
return err
},
)
if err != nil {
2020-11-05 16:54:03 -05:00
if errors . IsUniqueError ( err ) {
return nil , db . NoRowsAffected , fmt . Errorf ( "update: password auth method: authMethod %s already exists in scope %s: %w" , authMethod . Name , authMethod . ScopeId , errors . ErrNotUnique )
2020-07-31 14:35:56 -07:00
}
return nil , db . NoRowsAffected , fmt . Errorf ( "update: password auth method: %w for %s" , err , authMethod . PublicId )
}
return upAuthMethod , rowsUpdated , err
}