You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/auth/password/repository_configuration.go

184 lines
5.7 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package password
import (
"context"
"database/sql"
"github.com/hashicorp/boundary/internal/auth/password/store"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
)
// A Configuration is an interface holding one of the configuration types
// for a specific key derivation function. Argon2Configuration is currently
// the only configuration type.
type Configuration interface {
AuthMethodId() string
validate(context.Context) error
}
// GetConfiguration returns the current configuration for authMethodId.
func (r *Repository) GetConfiguration(ctx context.Context, authMethodId string) (Configuration, error) {
const op = "password.(Repository).GetConfiguration"
if authMethodId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing auth method id")
}
cc, err := r.currentConfig(ctx, authMethodId)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return cc.argon2(), nil
}
// SetConfiguration sets the configuration for c.AuthMethodId to c and
// returns a new Configuration. c is not changed. c must contain a valid
// AuthMethodId. c.PrivateId is ignored.
//
// If c contains new settings for c.AuthMethodId, SetConfiguration inserts
// c into the repository and updates AuthMethod to use the new
// configuration. If c contains settings equal to the current configuration
// for c.AuthMethodId, SetConfiguration ignores c. If c contains settings
// equal to a previous configuration for c.AuthMethodId, SetConfiguration
// updates AuthMethod to use the previous configuration.
func (r *Repository) SetConfiguration(ctx context.Context, scopeId string, c Configuration) (Configuration, error) {
const op = "password.(Repository).SetConfiguration"
if c == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing configuration")
}
if c.AuthMethodId() == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing auth method id")
}
if err := c.validate(ctx); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
switch v := c.(type) {
case *Argon2Configuration:
out, err := r.setArgon2Conf(ctx, scopeId, v)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return out, nil
default:
return nil, errors.New(ctx, errors.PasswordUnsupportedConfiguration, op, "unknown configuration")
}
}
func (r *Repository) setArgon2Conf(ctx context.Context, scopeId string, c *Argon2Configuration) (*Argon2Configuration, error) {
const op = "password.(Repository).setArgon2Conf"
c = c.clone()
id, err := newArgon2ConfigurationId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
c.PrivateId = id
a := &AuthMethod{
AuthMethod: &store.AuthMethod{
PublicId: c.PasswordMethodId,
},
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get oplog wrapper"))
}
newArgon2Conf := &Argon2Configuration{Argon2Configuration: &store.Argon2Configuration{}}
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(rr db.Reader, w db.Writer) error {
where, args := c.whereDup()
if err := rr.LookupWhere(ctx, newArgon2Conf, where, args); err != nil {
if !errors.IsNotFoundError(err) {
return errors.Wrap(ctx, err, op)
}
newArgon2Conf = c.clone()
if err := w.Create(ctx, newArgon2Conf, db.WithOplog(oplogWrapper, c.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil {
return errors.Wrap(ctx, err, op)
}
}
a.PasswordConfId = newArgon2Conf.PrivateId
rowsUpdated, err := w.Update(ctx, a, []string{"PasswordConfId"}, nil, db.WithOplog(oplogWrapper, a.oplog(oplog.OpType_OP_TYPE_UPDATE)))
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsUpdated > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated")
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return newArgon2Conf, nil
}
type currentConfig struct {
ConfType string
MinLoginNameLength int
MinPasswordLength int
*Argon2Configuration
}
func (c *currentConfig) TableName() string {
return "auth_password_current_conf"
}
func (r *Repository) currentConfig(ctx context.Context, authMethodId string) (*currentConfig, error) {
const op = "password.(Repository).currentConfig"
var cc currentConfig
if err := r.reader.LookupWhere(ctx, &cc, "password_method_id = ?", []any{authMethodId}); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &cc, nil
}
func (r *Repository) currentConfigForAccount(ctx context.Context, accountId string) (*currentConfig, error) {
const op = "password.(Repository).currentConfigForAccount"
var confs []currentConfig
rows, err := r.reader.Query(ctx, currentConfigForAccountQuery, []any{sql.Named("public_id", accountId)})
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
defer rows.Close()
for rows.Next() {
var conf currentConfig
if err := r.reader.ScanRows(ctx, rows, &conf); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
confs = append(confs, conf)
}
if err := rows.Err(); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var cc currentConfig
switch {
case len(confs) == 0:
return nil, nil
case len(confs) > 1:
// this should never happen
return nil, errors.New(ctx, errors.Unknown, op, "multiple current configs returned for account")
default:
cc = confs[0]
}
return &cc, nil
}
func (c *currentConfig) argon2() *Argon2Configuration {
if c.ConfType != "argon2" {
return nil
}
return c.Argon2Configuration
}