fix (auth/oidc): Revert to working version of oidc acct upsert (#1719)
This commit is contained in:
1 file changed
+103
-37
@@ -2,9 +2,11 @@ package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
@@ -70,84 +72,148 @@ func (r *Repository) upsertAccount(ctx context.Context, am *AuthMethod, IdTokenC
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
columns := []string{"public_id", "auth_method_id", "issuer", "subject"}
|
||||
values := []interface{}{
|
||||
sql.Named("1", pubId),
|
||||
sql.Named("2", am.PublicId),
|
||||
sql.Named("3", iss),
|
||||
sql.Named("4", sub),
|
||||
}
|
||||
var conflictClauses, fieldMasks, nullMasks []string
|
||||
|
||||
{
|
||||
marshaledTokenClaims, err := json.Marshal(IdTokenClaims)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
columns, values = append(columns, "token_claims"), append(values, sql.Named(fmt.Sprintf("%d", len(values)+1), string(marshaledTokenClaims)))
|
||||
conflictClauses = append(conflictClauses, fmt.Sprintf("token_claims = @%d", len(values)))
|
||||
fieldMasks = append(fieldMasks, TokenClaimsField)
|
||||
}
|
||||
{
|
||||
marshaledAccessTokenClaims, err := json.Marshal(AccessTokenClaims)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
columns, values = append(columns, "userinfo_claims"), append(values, sql.Named(fmt.Sprintf("%d", len(values)+1), string(marshaledAccessTokenClaims)))
|
||||
conflictClauses = append(conflictClauses, fmt.Sprintf("userinfo_claims = @%d", len(values)))
|
||||
fieldMasks = append(fieldMasks, UserinfoClaimsField)
|
||||
}
|
||||
|
||||
issAsUrl, err := url.Parse(iss)
|
||||
if err != nil {
|
||||
return nil, errors.New(ctx, errors.Unknown, op, "unable to parse issuer", errors.WithWrap(err))
|
||||
}
|
||||
|
||||
acct := AllocAccount()
|
||||
acct.PublicId = pubId
|
||||
acct.AuthMethodId = am.PublicId
|
||||
acct.Issuer = issAsUrl.String()
|
||||
acct.Subject = sub
|
||||
|
||||
oc := db.OnConflict{
|
||||
Target: db.Constraint("auth_oidc_account_auth_method_id_issuer_subject_uq"),
|
||||
Action: db.SetColumns([]string{"public_id", "auth_method_id", "issuer", "subject", "userinfo_claims", "token_claims"}),
|
||||
}
|
||||
|
||||
marshaledTokenClaims, err := json.Marshal(IdTokenClaims)
|
||||
acctForOplog, err := NewAccount(ctx, am.PublicId, sub, WithIssuer(issAsUrl))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create new acct for oplog"))
|
||||
}
|
||||
acct.TokenClaims = string(marshaledTokenClaims)
|
||||
|
||||
marshaledAccessTokenClaims, err := json.Marshal(AccessTokenClaims)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
acct.UserinfoClaims = string(marshaledAccessTokenClaims)
|
||||
|
||||
var foundName, foundEmail bool
|
||||
var foundName interface{}
|
||||
switch {
|
||||
case AccessTokenClaims[fromName] != nil:
|
||||
foundName = true
|
||||
acct.FullName = AccessTokenClaims[fromName].(string)
|
||||
foundName = AccessTokenClaims[fromName]
|
||||
columns, values = append(columns, "full_name"), append(values, sql.Named(fmt.Sprintf("%d", len(values)+1), foundName))
|
||||
case IdTokenClaims[fromName] != nil:
|
||||
foundName = true
|
||||
acct.FullName = IdTokenClaims[fromName].(string)
|
||||
foundName = IdTokenClaims[fromName]
|
||||
columns, values = append(columns, "full_name"), append(values, sql.Named(fmt.Sprintf("%d", len(values)+1), foundName))
|
||||
}
|
||||
if foundName != nil {
|
||||
acctForOplog.FullName = foundName.(string)
|
||||
conflictClauses = append(conflictClauses, fmt.Sprintf("full_name = @%d", len(values)))
|
||||
fieldMasks = append(fieldMasks, NameField)
|
||||
} else {
|
||||
conflictClauses = append(conflictClauses, "full_name = NULL")
|
||||
nullMasks = append(nullMasks, NameField)
|
||||
}
|
||||
|
||||
var foundEmail interface{}
|
||||
switch {
|
||||
case AccessTokenClaims[fromEmail] != nil:
|
||||
foundEmail = true
|
||||
acct.Email = AccessTokenClaims[fromEmail].(string)
|
||||
foundEmail = AccessTokenClaims[fromEmail]
|
||||
columns, values = append(columns, "email"), append(values, sql.Named(fmt.Sprintf("%d", len(values)+1), foundEmail))
|
||||
case IdTokenClaims[fromEmail] != nil:
|
||||
foundEmail = true
|
||||
acct.Email = IdTokenClaims[fromEmail].(string)
|
||||
foundEmail = IdTokenClaims[fromEmail]
|
||||
columns, values = append(columns, "email"), append(values, sql.Named(fmt.Sprintf("%d", len(values)+1), foundEmail))
|
||||
}
|
||||
if foundName {
|
||||
oc.Action = append(oc.Action.([]db.ColumnValue), db.SetColumns([]string{"full_name"})...)
|
||||
if foundEmail != nil {
|
||||
acctForOplog.Email = foundEmail.(string)
|
||||
conflictClauses = append(conflictClauses, fmt.Sprintf("email = @%d", len(values)))
|
||||
fieldMasks = append(fieldMasks, "Email")
|
||||
} else {
|
||||
conflictClauses = append(conflictClauses, "email = NULL")
|
||||
nullMasks = append(nullMasks, "Email")
|
||||
}
|
||||
if foundEmail {
|
||||
oc.Action = append(oc.Action.([]db.ColumnValue), db.SetColumns([]string{"email"})...)
|
||||
|
||||
placeHolders := make([]string, 0, len(columns))
|
||||
for colNum := range columns {
|
||||
placeHolders = append(placeHolders, fmt.Sprintf("@%d", colNum+1))
|
||||
}
|
||||
query := fmt.Sprintf(acctUpsertQuery, strings.Join(columns, ", "), strings.Join(placeHolders, ", "), strings.Join(conflictClauses, ", "))
|
||||
|
||||
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"))
|
||||
}
|
||||
|
||||
updatedAcct := AllocAccount()
|
||||
_, err = r.writer.DoTx(
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(reader db.Reader, w db.Writer) error {
|
||||
var rowCnt int64
|
||||
err := w.Create(ctx, acct, db.WithOnConflict(&oc), db.WithReturnRowsAffected(&rowCnt), db.WithOplog(oplogWrapper, acct.oplog(oplog.OpType_OP_TYPE_CREATE, am.ScopeId)))
|
||||
var err error
|
||||
rows, err := w.Query(ctx, query, values)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to insert/update auth oidc account"))
|
||||
}
|
||||
defer rows.Close()
|
||||
result := struct {
|
||||
PublicId string
|
||||
Version int
|
||||
}{}
|
||||
var rowCnt int
|
||||
for rows.Next() {
|
||||
rowCnt += 1
|
||||
err = r.reader.ScanRows(rows, &result)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to scan rows for account"))
|
||||
}
|
||||
}
|
||||
if rowCnt > 1 {
|
||||
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 row but got: %d", rowCnt))
|
||||
}
|
||||
if err := reader.LookupWhere(ctx, &updatedAcct, "auth_method_id = ? and issuer = ? and subject = ?", am.PublicId, iss, sub); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to look up auth oidc account for: %s / %s / %s", am.PublicId, iss, sub)))
|
||||
}
|
||||
// include the version incase of predictable account public ids based on a calculation using authmethod id and subject
|
||||
if result.Version == 1 && updatedAcct.PublicId == pubId {
|
||||
if err := upsertOplog(ctx, w, oplogWrapper, oplog.OpType_OP_TYPE_CREATE, am.ScopeId, updatedAcct, nil, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write create oplog for account"))
|
||||
}
|
||||
} else {
|
||||
if len(fieldMasks) > 0 || len(nullMasks) > 0 {
|
||||
acctForOplog := AllocAccount()
|
||||
acctForOplog.PublicId = updatedAcct.PublicId
|
||||
if foundEmail != nil {
|
||||
acctForOplog.Email = foundEmail.(string)
|
||||
}
|
||||
if foundName != nil {
|
||||
acctForOplog.FullName = foundName.(string)
|
||||
}
|
||||
if err := upsertOplog(ctx, w, oplogWrapper, oplog.OpType_OP_TYPE_UPDATE, am.ScopeId, acctForOplog, fieldMasks, nullMasks); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write update oplog for account"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return acct, nil
|
||||
return updatedAcct, nil
|
||||
}
|
||||
|
||||
// upsertOplog will write oplog msgs for account upserts. The db.Writer needs to be the writer for the current
|
||||
|
||||
Reference in new issue
Block a user