Files
threshold/internal/daemon/controller/handlers/authmethods/password.go
T
Jeff MitchellandJohan Brandhorst-Satzkorn 0bf60996c5 Add generic read/update/delete commands (#3967)
* Add generic read/update/delete commands

This adds three generic commands that use the resource prefix to
determine the subcommand to run and pass through args over to that
subcommand. Since this occurs very early on and to avoid loading
essentially every internal resource package, I moved subtype definitions
to globals.

* Update main.go

Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>

* Update changelog and remove another deprecated command

---------

Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
2023-11-01 16:24:16 -04:00

211 lines
6.6 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package authmethods
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth/password"
pwstore "github.com/hashicorp/boundary/internal/auth/password/store"
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/event"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/types/action"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authmethods"
pba "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens"
"google.golang.org/grpc/codes"
)
const (
// password field names
loginNameField = "login_name"
passwordField = "password"
loginCommand = "login"
)
var pwMaskManager handlers.MaskManager
func init() {
var err error
if pwMaskManager, err = handlers.NewMaskManager(context.Background(), handlers.MaskDestination{&pwstore.AuthMethod{}}, handlers.MaskSource{&pb.AuthMethod{}, &pb.PasswordAuthMethodAttributes{}}); err != nil {
panic(err)
}
IdActions[globals.PasswordSubtype] = action.ActionSet{
action.NoOp,
action.Read,
action.Update,
action.Delete,
action.Authenticate,
}
}
// createPwInRepo creates a password auth method in a repo and returns the result.
// This method should never return a nil AuthMethod without returning an error.
func (s Service) createPwInRepo(ctx context.Context, scopeId string, item *pb.AuthMethod) (*password.AuthMethod, error) {
u, err := toStoragePwAuthMethod(ctx, scopeId, item)
if err != nil {
return nil, err
}
repo, err := s.pwRepoFn()
if err != nil {
return nil, err
}
out, err := repo.CreateAuthMethod(ctx, u)
if err != nil {
return nil, fmt.Errorf("unable to create auth method: %w", err)
}
return out, err
}
func (s Service) updatePwInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.AuthMethod) (*password.AuthMethod, error) {
u, err := toStoragePwAuthMethod(ctx, scopeId, item)
if err != nil {
return nil, err
}
version := item.GetVersion()
u.PublicId = id
dbMask := pwMaskManager.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 fields provided in the update mask."})
}
repo, err := s.pwRepoFn()
if err != nil {
return nil, err
}
out, rowsUpdated, err := repo.UpdateAuthMethod(ctx, u, version, dbMask)
if err != nil {
return nil, fmt.Errorf("unable to update auth method: %w", err)
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("AuthMethod %q doesn't exist or incorrect version provided.", id)
}
return out, nil
}
func (s Service) authenticatePassword(ctx context.Context, req *pbs.AuthenticateRequest, authResults *auth.VerifyResults) (*pbs.AuthenticateResponse, error) {
reqAttrs := req.GetPasswordLoginAttributes()
tok, err := s.authenticateWithPwRepo(ctx, authResults.Scope.GetId(), req.GetAuthMethodId(), reqAttrs.LoginName, reqAttrs.Password)
if err != nil {
return nil, err
}
return s.convertToAuthenticateResponse(ctx, req, authResults, tok)
}
func (s Service) authenticateWithPwRepo(ctx context.Context, scopeId, authMethodId, loginName, pw string) (*pba.AuthToken, error) {
const op = "authmethods.(Service).authenticateWithPwRepo"
iamRepo, err := s.iamRepoFn()
if err != nil {
return nil, err
}
atRepo, err := s.atRepoFn()
if err != nil {
return nil, err
}
pwRepo, err := s.pwRepoFn()
if err != nil {
return nil, err
}
acct, err := pwRepo.Authenticate(ctx, scopeId, authMethodId, loginName, pw)
if err != nil {
return nil, err
}
if acct == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Unauthenticated, "Unable to authenticate.")
}
u, err := iamRepo.LookupUserWithLogin(ctx, acct.GetPublicId())
if err != nil {
return nil, err
}
tok, err := atRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
if err != nil {
return nil, err
}
if err := event.WriteObservation(ctx, op, event.WithDetails("user_id", u.GetPublicId(), "auth_token_start",
tok.GetCreateTime(), "auth_token_end", tok.GetExpirationTime())); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("Unable to write observation event for authenticate method"))
}
return s.ConvertInternalAuthTokenToApiAuthToken(
ctx,
tok,
)
}
func validateAuthenticatePasswordRequest(req *pbs.AuthenticateRequest) error {
badFields := make(map[string]string)
attrs := req.GetPasswordLoginAttributes()
switch {
case attrs == nil:
badFields["attributes"] = "This is a required field."
default:
if attrs.LoginName == "" {
badFields["attributes.login_name"] = "This is a required field."
}
if attrs.Password == "" {
badFields["attributes.password"] = "This is a required field."
}
if req.GetCommand() == "" {
// TODO: Eventually, require a command. For now, fall back to "login" for backwards compat.
req.Command = loginCommand
}
if req.Command != loginCommand {
badFields[commandField] = "Invalid command for this auth method type."
}
tokenType := req.GetType()
if tokenType == "" {
// Fall back to deprecated field if type is not set
tokenType = req.GetTokenType()
}
tType := strings.ToLower(strings.TrimSpace(tokenType))
if tType != "" && tType != "token" && tType != "cookie" {
badFields[tokenTypeField] = `The only accepted types are "token" and "cookie".`
}
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Invalid fields provided in request.", badFields)
}
return nil
}
func toStoragePwAuthMethod(ctx context.Context, scopeId string, item *pb.AuthMethod) (*password.AuthMethod, error) {
const op = "authmethod_service.toStoragePwAuthMethod"
if item == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil auth method.")
}
var opts []password.Option
if item.GetName() != nil {
opts = append(opts, password.WithName(item.GetName().GetValue()))
}
if item.GetDescription() != nil {
opts = append(opts, password.WithDescription(item.GetDescription().GetValue()))
}
u, err := password.NewAuthMethod(ctx, scopeId, opts...)
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build auth method for creation: %v.", err)
}
pwAttrs := item.GetPasswordAuthMethodAttributes()
if pwAttrs.GetMinLoginNameLength() != 0 {
u.MinLoginNameLength = pwAttrs.GetMinLoginNameLength()
}
if pwAttrs.GetMinPasswordLength() != 0 {
u.MinPasswordLength = pwAttrs.GetMinPasswordLength()
}
return u, nil
}