Files
threshold/internal/auth/oidc/repository_auth_method_create.go
T
Elim Tsiagbey 8b8d2822df feat(oidc): OIDC Prompt (#4053)
Boundary OIDC method does not currently support passing in prompts during authentication. This change adds the capability of passing OIDC prompts. Prompts are optional OIDC parameters that determine the behaviour of the authentication server: https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest

## Changes

- New `auth_oidc_prompt` table which contains all the prompts for OIDC auth method
- New `auth_oidc_prompt_enm` table which contains possible enum values for a prompt
  - Currently supported:
    - `none`: 
The Authorization Server MUST NOT display any authentication or consent user interface pages
    - `login`: The Authorization Server SHOULD prompt the End-User for reauthentication
    - `consent`: The Authorization Server SHOULD prompt the End-User for consent before returning information to the Client
    - `select_account`: The Authorization Server SHOULD prompt the End-User to select a user account
- `oidc_auth_method_with_value_obj` view has been updated to return `prompt` value
- Add `prompt` option for OIDC auth method CLI create and update
- Pass configured prompt during OIDC authentication
- Add `prompt` API validation for create and update
2023-11-21 11:30:02 -05:00

145 lines
4.7 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package oidc
import (
"context"
"strings"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
)
// CreateAuthMethod creates am (*AuthMethod) in the repo along with its
// associated embedded optional value objects of SigningAlgs, AudClaims,
// Prompts, and Certificates and returns the newly created AuthMethod
// (with its PublicId set)
//
// The AuthMethod's public id and version must be empty (zero values).
//
// All options are ignored.
func (r *Repository) CreateAuthMethod(ctx context.Context, am *AuthMethod, opt ...Option) (*AuthMethod, error) {
const op = "oidc.(Repository).CreateAuthMethod"
if am == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing auth method")
}
if am.PublicId != "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "public id must be empty")
}
if am.Version != 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "version must be empty")
}
if err := am.validate(ctx, op); err != nil {
return nil, err // validate properly sets the op to the caller, the code and the msg, so just return it.
}
opts := getOpts(opt...)
am.PublicId = opts.withPublicId
if am.PublicId == "" {
id, err := newAuthMethodId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
am.PublicId = id
} else {
if !strings.HasPrefix(am.PublicId, globals.OidcAuthMethodPrefix+"_") {
return nil, errors.New(ctx, errors.InvalidParameter, op, "wrong auth method id prefix")
}
}
vo, err := am.convertValueObjects(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
oplogWrapper, err := r.kms.GetWrapper(ctx, am.ScopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
}
databaseWrapper, err := r.kms.GetWrapper(context.Background(), am.ScopeId, kms.KeyPurposeDatabase)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper"))
}
if err := am.encrypt(ctx, databaseWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedAuthMethod *AuthMethod
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
msgs := make([]*oplog.Message, 0, 5)
ticket, err := w.GetTicket(ctx, am)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
}
returnedAuthMethod = am.Clone()
var amOplogMsg oplog.Message
if err := w.Create(ctx, returnedAuthMethod, db.NewOplogMsg(&amOplogMsg)); err != nil {
return err
}
msgs = append(msgs, &amOplogMsg)
if len(vo.Algs) > 0 {
algOplogMsgs := make([]*oplog.Message, 0, len(vo.Algs))
if err := w.CreateItems(ctx, vo.Algs, db.NewOplogMsgs(&algOplogMsgs)); err != nil {
return err
}
msgs = append(msgs, algOplogMsgs...)
}
if len(vo.Auds) > 0 {
audOplogMsgs := make([]*oplog.Message, 0, len(vo.Auds))
if err := w.CreateItems(ctx, vo.Auds, db.NewOplogMsgs(&audOplogMsgs)); err != nil {
return err
}
msgs = append(msgs, audOplogMsgs...)
}
if len(vo.Certs) > 0 {
certOplogMsgs := make([]*oplog.Message, 0, len(vo.Certs))
if err := w.CreateItems(ctx, vo.Certs, db.NewOplogMsgs(&certOplogMsgs)); err != nil {
return err
}
msgs = append(msgs, certOplogMsgs...)
}
if len(vo.ClaimsScopes) > 0 {
scopesOplogMsgs := make([]*oplog.Message, 0, len(vo.ClaimsScopes))
if err := w.CreateItems(ctx, vo.ClaimsScopes, db.NewOplogMsgs(&scopesOplogMsgs)); err != nil {
return err
}
msgs = append(msgs, scopesOplogMsgs...)
}
if len(vo.AccountClaimMaps) > 0 {
accountClaimMapsOplogMsgs := make([]*oplog.Message, 0, len(vo.AccountClaimMaps))
if err := w.CreateItems(ctx, vo.AccountClaimMaps, db.NewOplogMsgs(&accountClaimMapsOplogMsgs)); err != nil {
return err
}
msgs = append(msgs, accountClaimMapsOplogMsgs...)
}
if len(vo.Prompts) > 0 {
promptOplogMsgs := make([]*oplog.Message, 0, len(vo.Prompts))
if err := w.CreateItems(ctx, vo.Prompts, db.NewOplogMsgs(&promptOplogMsgs)); err != nil {
return err
}
msgs = append(msgs, promptOplogMsgs...)
}
metadata := am.oplog(oplog.OpType_OP_TYPE_CREATE)
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return returnedAuthMethod, nil
}