2020-08-11 11:19:29 -04:00
// Copyright (c) HashiCorp, Inc.
2020-08-11 17:25:00 -04: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"
"fmt"
2020-07-31 14:35:56 -07:00
"strings"
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"
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
)
// 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 ) {
2021-02-11 13:32:45 -06:00
const op = "password.(Repository).CreateAuthMethod"
2020-07-24 10:23:35 -04:00
if m == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing AuthMethod" )
2020-07-24 10:23:35 -04:00
}
if m . AuthMethod == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing embedded AuthMethod" )
2020-07-24 10:23:35 -04:00
}
if m . ScopeId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing scope id" )
2020-07-24 10:23:35 -04:00
}
if m . PublicId != "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "public id not empty" )
2020-07-24 10:23:35 -04:00
}
2021-04-08 20:54:32 -04:00
m = m . Clone ()
2020-07-24 10:23:35 -04:00
2022-10-25 21:33:31 -04:00
opts := GetOpts ( opt ... )
2020-08-07 10:18:40 -07:00
if opts . withPublicId != "" {
2023-03-10 15:06:39 -05:00
if ! strings . HasPrefix ( opts . withPublicId , globals . PasswordAuthMethodPrefix + "_" ) {
return nil , errors . New ( ctx , errors . InvalidPublicId , op , fmt . Sprintf ( "passed-in public ID %q has wrong prefix, should be %q" , opts . withPublicId , globals . PasswordAuthMethodPrefix ))
2020-08-07 10:18:40 -07:00
}
m . PublicId = opts . withPublicId
} else {
2023-06-06 17:09:08 -04:00
id , err := newAuthMethodId ( ctx )
2020-08-07 10:18:40 -07:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-08-07 10:18:40 -07:00
}
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 {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . PasswordUnsupportedConfiguration , op , "unknown configuration" )
2020-07-29 07:58:19 -04:00
}
2023-06-06 17:09:08 -04:00
if err := c . validate ( ctx ); err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-07-29 07:58:19 -04:00
}
2020-08-07 10:18:40 -07:00
var err error
2023-06-06 17:09:08 -04:00
c . PrivateId , err = newArgon2ConfigurationId ( ctx )
2020-07-29 07:58:19 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-07-29 07:58:19 -04:00
}
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 {
2021-07-29 18:52:56 -06:00
return nil , 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-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 {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create argon conf" ))
2020-07-29 07:58:19 -04:00
}
2021-04-08 20:54:32 -04:00
newAuthMethod = m . Clone ()
2021-03-05 06:34:06 -08:00
if err := w . Create ( ctx , newAuthMethod , db . WithOplog ( oplogWrapper , m . oplog ( oplog . OpType_OP_TYPE_CREATE ))); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create auth method" ))
2021-03-05 06:34:06 -08:00
}
return nil
2020-07-24 10:23:35 -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 , errors . New ( ctx , errors . NotUnique , op , fmt . Sprintf ( "in scope: %s: name %s already exists" , m . ScopeId , m . Name ))
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 ( m . ScopeId ))
2020-07-24 10:23:35 -04:00
}
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.
2021-03-05 06:34:06 -08:00
func ( r * Repository ) LookupAuthMethod ( ctx context . Context , publicId string , _ ... Option ) ( * AuthMethod , error ) {
2021-02-11 13:32:45 -06:00
const op = "password.(Repository).LookupAuthMethod"
2020-07-31 14:35:56 -07:00
if publicId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidPublicId , op , "missing public id" )
2020-07-31 14:35:56 -07:00
}
2021-04-08 20:54:32 -04:00
return r . lookupAuthMethod ( ctx , publicId )
2020-07-31 14:35:56 -07:00
}
// 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 ) {
2021-02-11 13:32:45 -06:00
const op = "password.(Repository).DeleteAuthMethod"
2020-07-31 14:35:56 -07:00
if publicId == "" {
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . New ( ctx , errors . InvalidPublicId , op , "missing public id" )
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 {
2021-07-29 18:52:56 -06:00
return 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-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 )
2021-04-08 20:54:32 -04:00
dAc := am . 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 )
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" )
2020-07-31 14:35:56 -07:00
}
2021-03-05 06:34:06 -08:00
return nil
2020-07-31 14:35:56 -07:00
},
)
if err != nil {
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( publicId ))
2020-07-31 14:35:56 -07:00
}
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 ) {
2021-02-11 13:32:45 -06:00
const op = "password.(Repository).UpdateAuthMethod"
2020-07-31 14:35:56 -07:00
if authMethod == nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing authMethod" )
2020-07-31 14:35:56 -07:00
}
if authMethod . PublicId == "" {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing authMethod public id" )
2020-07-31 14:35:56 -07:00
}
2020-08-19 16:47:18 -04:00
if authMethod . 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-07-31 14:35:56 -07:00
for _ , f := range fieldMaskPaths {
switch {
2021-04-08 20:54:32 -04:00
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 :
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidFieldMask , op , f )
2020-07-31 14:35:56 -07: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 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 {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . EmptyFieldMask , op , "field mask must not be empty" )
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 {
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
}
2021-04-08 20:54:32 -04:00
upAuthMethod := authMethod . Clone ()
2020-07-31 14:35:56 -07:00
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 {},
2021-04-08 20:54:32 -04:00
func ( reader 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 ... ,
)
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 )
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" )
2020-07-31 14:35:56 -07:00
}
2021-04-08 20:54:32 -04:00
// we need a new repo, that's using the same reader/writer as this TxHandler
txRepo := & Repository {
reader : reader ,
writer : w ,
kms : r . kms ,
// intentionally not setting the defaultLimit, so we'll get all
// the account ids without a limit
}
upAuthMethod , err = txRepo . lookupAuthMethod ( ctx , upAuthMethod . PublicId )
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to lookup auth method after update" ))
2021-04-08 20:54:32 -04:00
}
if upAuthMethod == nil {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . RecordNotFound , op , "unable to lookup auth method after update" )
2021-04-08 20:54:32 -04:00
}
2021-03-05 06:34:06 -08:00
return nil
2020-07-31 14:35:56 -07: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 , fmt . Sprintf ( "authMethod %s already exists in scope %s" , authMethod . Name , authMethod . ScopeId ))
2020-07-31 14:35:56 -07:00
}
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( authMethod . PublicId ))
2020-07-31 14:35:56 -07:00
}
2021-03-05 06:34:06 -08:00
return upAuthMethod , rowsUpdated , nil
2020-07-31 14:35:56 -07:00
}
2021-04-08 20:54:32 -04:00
// lookupAuthMethod will lookup a single auth method
func ( r * Repository ) lookupAuthMethod ( ctx context . Context , authMethodId string , opt ... Option ) ( * AuthMethod , error ) {
const op = "oidc.(Repository).lookupAuthMethod"
var err error
ams , err := r . getAuthMethods ( ctx , authMethodId , nil , opt ... )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
switch {
case len ( ams ) == 0 :
return nil , nil // not an error to return no rows for a "lookup"
case len ( ams ) > 1 :
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . NotSpecificIntegrity , op , fmt . Sprintf ( "%s matched more than 1 " , authMethodId ))
2021-04-08 20:54:32 -04:00
default :
return ams [ 0 ], nil
}
}
// getAuthMethods allows the caller to either lookup a specific AuthMethod via
// its id or search for a set AuthMethods within a set of scopes. Passing both
// scopeIds and a authMethodId is an error. The WithLimit and
// WithOrderByCreateTime options are supported and all other options are
// ignored.
//
// The AuthMethod returned has its IsPrimaryAuthMethod bool set.
//
// When no record is found it returns nil, nil
func ( r * Repository ) getAuthMethods ( ctx context . Context , authMethodId string , scopeIds [] string , opt ... Option ) ([] * AuthMethod , error ) {
const op = "password.(Repository).getAuthMethods"
if authMethodId == "" && len ( scopeIds ) == 0 {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing search criteria: both auth method id and Scope IDs are empty" )
2021-04-08 20:54:32 -04:00
}
if authMethodId != "" && len ( scopeIds ) > 0 {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "searching for both an auth method id and Scope IDs is not supported" )
2021-04-08 20:54:32 -04:00
}
dbArgs := [] db . Option {}
2022-10-25 21:33:31 -04:00
opts := GetOpts ( opt ... )
2021-04-08 20:54:32 -04:00
limit := r . defaultLimit
if opts . withLimit != 0 {
// non-zero signals an override of the default limit for the repo.
limit = opts . withLimit
}
dbArgs = append ( dbArgs , db . WithLimit ( limit ))
if opts . withOrderByCreateTime {
if opts . ascending {
dbArgs = append ( dbArgs , db . WithOrder ( "create_time asc" ))
} else {
dbArgs = append ( dbArgs , db . WithOrder ( "create_time" ))
}
}
2022-10-25 18:29:38 -05:00
var args [] any
2021-04-08 20:54:32 -04:00
var where [] string
switch {
case authMethodId != "" :
where , args = append ( where , "public_id = ?" ), append ( args , authMethodId )
default :
where , args = append ( where , "scope_id in(?)" ), append ( args , scopeIds )
}
var views [] * authMethodView
err := r . reader . SearchWhere ( ctx , & views , strings . Join ( where , " and " ), args , dbArgs ... )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
if len ( views ) == 0 { // we're done if nothing is found.
return nil , nil
}
authMethods := make ([] * AuthMethod , 0 , len ( views ))
for _ , am := range views {
authMethods = append ( authMethods , & AuthMethod { AuthMethod : am . AuthMethod })
}
return authMethods , nil
}