210 lines
6.6 KiB
Go
210 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/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[password.Subtype] = 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
|
|
}
|