2021-04-08 20:54:32 -04:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2021-04-08 20:54:32 -04:00
package oidc
import (
"context"
2023-09-17 19:35:43 -04:00
stderrors "errors"
2021-04-08 20:54:32 -04:00
"fmt"
"net/http"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
2022-08-24 14:11:49 -04:00
"github.com/hashicorp/go-dbw"
2021-07-14 17:37:40 -07:00
"github.com/hashicorp/go-secure-stdlib/strutil"
2021-04-08 20:54:32 -04:00
)
2021-04-28 06:53:13 -04:00
const (
OperationalStateField = "OperationalState"
DisableDiscoveredConfigValidationField = "DisableDiscoveredConfigValidation"
VersionField = "Version"
NameField = "Name"
DescriptionField = "Description"
2021-06-01 14:12:34 -05:00
FilterField = "Filter"
2021-04-28 06:53:13 -04:00
IssuerField = "Issuer"
ClientIdField = "ClientId"
ClientSecretField = "ClientSecret"
CtClientSecretField = "CtClientSecret"
ClientSecretHmacField = "ClientSecretHmac"
MaxAgeField = "MaxAge"
SigningAlgsField = "SigningAlgs"
ApiUrlField = "ApiUrl"
AudClaimsField = "AudClaims"
CertificatesField = "Certificates"
ClaimsScopesField = "ClaimsScopes"
2021-05-04 08:41:04 -04:00
AccountClaimMapsField = "AccountClaimMaps"
2021-07-26 17:52:18 -04:00
TokenClaimsField = "TokenClaims"
UserinfoClaimsField = "UserinfoClaims"
2022-11-16 17:17:28 -06:00
KeyIdField = "KeyId"
2023-11-21 11:30:02 -05:00
PromptsField = "Prompts"
2021-04-28 06:53:13 -04:00
)
2021-04-08 20:54:32 -04:00
// UpdateAuthMethod will retrieve the auth method from the repository,
// and update it based on the field masks provided.
//
// The auth method will not be persisted in the repository if the auth
// method's OperationalStatus is currently ActivePublic or ActivePrivate
// and the update would have resulted in an incomplete/non-operational
// auth method.
//
// During update, the auth method will be tested/validated against its
// provider's published OIDC discovery document. If this validation
// succeeds, the auth method is persisted in the repository, and the
// written auth method is returned.
//
// fieldMaskPaths provides field_mask.proto paths for fields that should
// be updated. Fields will be set to NULL if the field is a
// zero value and included in fieldMask. Name, Description, Issuer,
// ClientId, ClientSecret, MaxAge are all updatable fields. The AuthMethod's
2023-11-21 11:30:02 -05:00
// Value Objects of SigningAlgs, Prompts, CallbackUrls, AudClaims and Certificates are
2021-04-08 20:54:32 -04:00
// also updatable. if no updatable fields are included in the fieldMaskPaths,
// then an error is returned.
//
// Options supported:
//
// * WithDryRun: when this option is provided, the auth method is retrieved from
// the repo, updated based on the fieldMask, tested via Repository.ValidateDiscoveryInfo,
// the results of the update are returned, and and any errors reported. The
// updates are not peristed to the repository.
//
// * WithForce: when this option is provided, the auth method is persisted in
// the repository without testing it's validity against its provider's published
// OIDC discovery document. Even if this option is provided, the auth method will
// not be persisted in the repository when the update would have resulted in
// an incomplete/non-operational auth method and it's OperationalStatus is
// currently ActivePublic or ActivePrivate.
//
// Also, a successful update will invalidate (delete) the Repository's
// cache of the oidc.Provider for the AuthMethod.
func ( r * Repository ) UpdateAuthMethod ( ctx context . Context , am * AuthMethod , version uint32 , fieldMaskPaths [] string , opt ... Option ) ( * AuthMethod , int , error ) {
const op = "oidc.(Repository).UpdateAuthMethod"
if am == nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing auth method" )
2021-04-08 20:54:32 -04:00
}
if am . AuthMethod == nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing auth method store" )
2021-04-08 20:54:32 -04:00
}
if am . PublicId == "" {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing public id" )
2021-04-08 20:54:32 -04:00
}
2021-07-29 18:52:56 -06:00
if err := validateFieldMask ( ctx , fieldMaskPaths ); err != nil {
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
2022-08-24 14:11:49 -04:00
dbMask , nullFields := dbw . BuildUpdatePaths (
2022-10-25 18:29:38 -05:00
map [ string ] any {
2021-05-04 08:41:04 -04:00
NameField : am . Name ,
DescriptionField : am . Description ,
IssuerField : am . Issuer ,
ClientIdField : am . ClientId ,
ClientSecretField : am . ClientSecret ,
MaxAgeField : am . MaxAge ,
SigningAlgsField : am . SigningAlgs ,
ApiUrlField : am . ApiUrl ,
AudClaimsField : am . AudClaims ,
CertificatesField : am . Certificates ,
ClaimsScopesField : am . ClaimsScopes ,
AccountClaimMapsField : am . AccountClaimMaps ,
2023-11-21 11:30:02 -05:00
PromptsField : am . Prompts ,
2021-04-08 20:54:32 -04:00
},
fieldMaskPaths ,
nil ,
)
if len ( dbMask ) == 0 && len ( nullFields ) == 0 {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . EmptyFieldMask , op , "empty field mask" )
2021-04-08 20:54:32 -04:00
}
origAm , err := r . lookupAuthMethod ( ctx , am . PublicId )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
if origAm == nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . RecordNotFound , op , fmt . Sprintf ( "auth method %s" , am . PublicId ))
2021-04-08 20:54:32 -04:00
}
// there's no reason to continue if another controller has already updated this auth method.
if origAm . Version != version {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . VersionMismatch , op , fmt . Sprintf ( "update version %d doesn't match db version %d" , version , origAm . Version ))
2021-04-08 20:54:32 -04:00
}
opts := getOpts ( opt ... )
if opts . withDryRun {
updated := applyUpdate ( am , origAm , fieldMaskPaths )
2021-07-29 18:52:56 -06:00
if err := updated . isComplete ( ctx ); err != nil {
2021-04-08 20:54:32 -04:00
return updated , db . NoRowsAffected , err
}
err := r . ValidateDiscoveryInfo ( ctx , WithAuthMethod ( updated ))
return updated , db . NoRowsAffected , err
}
// prevent an "active" auth method from being updated in a manner that would create
// an incomplete and unusable auth method.
if origAm . OperationalState != string ( InactiveState ) {
2021-07-29 18:52:56 -06:00
if err := applyUpdate ( am , origAm , fieldMaskPaths ). isComplete ( ctx ); err != nil {
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "update would result in an incomplete auth method" ))
2021-04-08 20:54:32 -04:00
}
}
if ! opts . withForce {
if err := r . ValidateDiscoveryInfo ( ctx , WithAuthMethod ( applyUpdate ( am , origAm , fieldMaskPaths ))); err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
}
2024-08-01 11:18:53 -04:00
aa , ad , err := valueObjectChanges ( ctx , origAm . PublicId , SigningAlgVO , am . SigningAlgs , origAm . SigningAlgs , dbMask , nullFields )
2021-04-08 20:54:32 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
2024-08-01 11:18:53 -04:00
addAlgs := [] * SigningAlg {}
for _ , a := range aa {
addAlgs = append ( addAlgs , a .( * SigningAlg ))
}
deleteAlgs := [] * SigningAlg {}
for _ , a := range ad {
deleteAlgs = append ( deleteAlgs , a .( * SigningAlg ))
}
2021-04-08 20:54:32 -04:00
2024-08-01 11:18:53 -04:00
ac , dc , err := valueObjectChanges ( ctx , origAm . PublicId , CertificateVO , am . Certificates , origAm . Certificates , dbMask , nullFields )
2021-04-08 20:54:32 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
2024-08-01 11:18:53 -04:00
addCerts := [] * Certificate {}
for _ , c := range ac {
addCerts = append ( addCerts , c .( * Certificate ))
}
deleteCerts := [] * Certificate {}
for _ , c := range dc {
deleteCerts = append ( deleteCerts , c .( * Certificate ))
}
2021-04-08 20:54:32 -04:00
2024-08-01 11:18:53 -04:00
aa , ad , err = valueObjectChanges ( ctx , origAm . PublicId , AudClaimVO , am . AudClaims , origAm . AudClaims , dbMask , nullFields )
2021-04-08 20:54:32 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
2024-08-01 11:18:53 -04:00
addAuds := [] * AudClaim {}
for _ , a := range aa {
addAuds = append ( addAuds , a .( * AudClaim ))
}
deleteAuds := [] * AudClaim {}
for _ , a := range ad {
deleteAuds = append ( deleteAuds , a .( * AudClaim ))
}
2021-04-08 20:54:32 -04:00
2024-08-01 11:18:53 -04:00
as , ds , err := valueObjectChanges ( ctx , origAm . PublicId , ClaimsScopesVO , am . ClaimsScopes , origAm . ClaimsScopes , dbMask , nullFields )
2021-04-28 06:53:13 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-04-28 06:53:13 -04:00
}
2024-08-01 11:18:53 -04:00
addScopes := [] * ClaimsScope {}
for _ , s := range as {
addScopes = append ( addScopes , s .( * ClaimsScope ))
}
deleteScopes := [] * ClaimsScope {}
for _ , s := range ds {
deleteScopes = append ( deleteScopes , s .( * ClaimsScope ))
}
2021-04-28 06:53:13 -04:00
2024-08-01 11:18:53 -04:00
aacm , dacm , err := valueObjectChanges ( ctx , origAm . PublicId , AccountClaimMapsVO , am . AccountClaimMaps , origAm . AccountClaimMaps , dbMask , nullFields )
2021-05-04 08:41:04 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-05-04 08:41:04 -04:00
}
2024-08-01 11:18:53 -04:00
addMaps := [] * AccountClaimMap {}
for _ , m := range aacm {
addMaps = append ( addMaps , m .( * AccountClaimMap ))
}
deleteMaps := [] * AccountClaimMap {}
for _ , m := range dacm {
deleteMaps = append ( deleteMaps , m .( * AccountClaimMap ))
}
2023-11-21 11:30:02 -05:00
2024-08-01 11:18:53 -04:00
ap , dp , err := valueObjectChanges ( ctx , origAm . PublicId , PromptsVO , am . Prompts , origAm . Prompts , dbMask , nullFields )
2023-11-21 11:30:02 -05:00
if err != nil {
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
}
2024-08-01 11:18:53 -04:00
addPrompts := [] * Prompt {}
for _ , p := range ap {
addPrompts = append ( addPrompts , p .( * Prompt ))
}
deletePrompts := [] * Prompt {}
for _ , p := range dp {
deletePrompts = append ( deletePrompts , p .( * Prompt ))
}
2023-11-21 11:30:02 -05:00
2021-05-04 08:41:04 -04:00
// we don't allow updates for "sub" claim maps, because we have no way to
// determine if the updated "from" claim in the map might create collisions
// with any existing account's subject.
2024-08-01 11:18:53 -04:00
for _ , cm := range addMaps {
2021-05-04 08:41:04 -04:00
if cm . ToClaim == string ( ToSubClaim ) {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "you cannot update account claim map %s=%s for the \"sub\" claim" , cm . FromClaim , cm . ToClaim ))
2021-05-04 08:41:04 -04:00
}
}
2024-08-01 11:18:53 -04:00
for _ , cm := range deleteMaps {
2021-05-04 08:41:04 -04:00
if cm . ToClaim == string ( ToSubClaim ) {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "you cannot update account claim map %s=%s for the \"sub\" claim" , cm . FromClaim , cm . ToClaim ))
2021-05-04 08:41:04 -04:00
}
}
2021-04-08 20:54:32 -04:00
var filteredDbMask , filteredNullFields [] string
for _ , f := range dbMask {
switch f {
2023-11-21 11:30:02 -05:00
case SigningAlgsField , AudClaimsField , CertificatesField , ClaimsScopesField , AccountClaimMapsField , PromptsField :
2021-04-08 20:54:32 -04:00
continue
default :
filteredDbMask = append ( filteredDbMask , f )
}
}
for _ , f := range nullFields {
switch f {
2023-11-21 11:30:02 -05:00
case SigningAlgsField , AudClaimsField , CertificatesField , ClaimsScopesField , AccountClaimMapsField , PromptsField :
2021-04-08 20:54:32 -04:00
continue
default :
filteredNullFields = append ( filteredNullFields , f )
}
}
// handle no changes...
if len ( filteredDbMask ) == 0 &&
len ( filteredNullFields ) == 0 &&
len ( addAlgs ) == 0 &&
len ( deleteAlgs ) == 0 &&
len ( addCerts ) == 0 &&
len ( deleteCerts ) == 0 &&
len ( addAuds ) == 0 &&
2021-04-28 06:53:13 -04:00
len ( deleteAuds ) == 0 &&
len ( addScopes ) == 0 &&
2021-05-04 08:41:04 -04:00
len ( deleteScopes ) == 0 &&
len ( addMaps ) == 0 &&
2023-11-21 11:30:02 -05:00
len ( deleteMaps ) == 0 &&
len ( addPrompts ) == 0 &&
len ( deletePrompts ) == 0 {
2021-04-08 20:54:32 -04:00
return origAm , db . NoRowsAffected , nil
}
2021-04-09 15:33:21 -04:00
// ClientSecret is a bit odd, because it uses the Struct wrapping, we need
// to add the encrypted fields to the dbMask or nullFields
2021-04-28 06:53:13 -04:00
if strutil . StrListContains ( filteredDbMask , ClientSecretField ) {
2022-11-16 17:17:28 -06:00
filteredDbMask = append ( filteredDbMask , CtClientSecretField , ClientSecretHmacField , KeyIdField )
2021-04-09 15:33:21 -04:00
}
2021-04-28 06:53:13 -04:00
if strutil . StrListContains ( filteredNullFields , ClientSecretField ) {
2022-11-16 17:17:28 -06:00
filteredNullFields = append ( filteredNullFields , CtClientSecretField , ClientSecretHmacField , KeyIdField )
2021-04-09 15:33:21 -04:00
}
2021-04-08 20:54:32 -04:00
databaseWrapper , err := r . kms . GetWrapper ( ctx , origAm . ScopeId , kms . KeyPurposeDatabase )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get database wrapper" ))
2021-04-08 20:54:32 -04:00
}
if err := am . encrypt ( ctx , databaseWrapper ); err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
oplogWrapper , err := r . kms . GetWrapper ( ctx , origAm . ScopeId , kms . KeyPurposeOplog )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get oplog wrapper" ))
2021-04-08 20:54:32 -04:00
}
// we always set this to the current value of opts.withForce
am . DisableDiscoveredConfigValidation = opts . withForce
var updatedAm * AuthMethod
var rowsUpdated int
_ , err = r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( reader db . Reader , w db . Writer ) error {
2023-11-21 11:30:02 -05:00
msgs := make ([] * oplog . Message , 0 , 9 ) // AuthMethod, Algs*2, Certs*2, Audiences*2, Prompts*2
2022-01-05 14:38:38 -05:00
ticket , err := w . GetTicket ( ctx , am )
2021-04-08 20:54:32 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get ticket" ))
2021-04-08 20:54:32 -04:00
}
var authMethodOplogMsg oplog . Message
switch {
case len ( filteredDbMask ) == 0 && len ( filteredNullFields ) == 0 :
// the auth method's fields are not being updated, just it's value objects, so we need to just update the auth
// method's version.
updatedAm = am . Clone ()
updatedAm . Version = uint32 ( version ) + 1
2021-04-28 06:53:13 -04:00
rowsUpdated , err = w . Update ( ctx , updatedAm , [] string { VersionField , DisableDiscoveredConfigValidationField }, nil , db . NewOplogMsg ( & authMethodOplogMsg ), db . WithVersion ( & version ))
2021-04-08 20:54:32 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to update auth method version" ))
2021-04-08 20:54:32 -04:00
}
if rowsUpdated != 1 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "updated auth method version and %d rows updated" , rowsUpdated ))
2021-04-08 20:54:32 -04:00
}
default :
2021-04-28 06:53:13 -04:00
filteredDbMask = append ( filteredDbMask , DisableDiscoveredConfigValidationField )
2021-04-08 20:54:32 -04:00
updatedAm = am . Clone ()
rowsUpdated , err = w . Update ( ctx , updatedAm , filteredDbMask , filteredNullFields , db . NewOplogMsg ( & authMethodOplogMsg ), db . WithVersion ( & version ))
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to update auth method" ))
2021-04-08 20:54:32 -04:00
}
if rowsUpdated != 1 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "updated auth method and %d rows updated" , rowsUpdated ))
2021-04-08 20:54:32 -04:00
}
}
msgs = append ( msgs , & authMethodOplogMsg )
if len ( deleteAlgs ) > 0 {
deleteAlgOplogMsgs := make ([] * oplog . Message , 0 , len ( deleteAlgs ))
rowsDeleted , err := w . DeleteItems ( ctx , deleteAlgs , db . NewOplogMsgs ( & deleteAlgOplogMsgs ))
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to delete signing algorithms" ))
2021-04-08 20:54:32 -04:00
}
if rowsDeleted != len ( deleteAlgs ) {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "signing algorithms deleted %d did not match request for %d" , rowsDeleted , len ( deleteAlgs )))
2021-04-08 20:54:32 -04:00
}
msgs = append ( msgs , deleteAlgOplogMsgs ... )
}
if len ( addAlgs ) > 0 {
addAlgsOplogMsgs := make ([] * oplog . Message , 0 , len ( addAlgs ))
if err := w . CreateItems ( ctx , addAlgs , db . NewOplogMsgs ( & addAlgsOplogMsgs )); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to add signing algorithms" ))
2021-04-08 20:54:32 -04:00
}
msgs = append ( msgs , addAlgsOplogMsgs ... )
}
2023-11-21 11:30:02 -05:00
if len ( deletePrompts ) > 0 {
deletePromptOplogMsgs := make ([] * oplog . Message , 0 , len ( deletePrompts ))
rowsDeleted , err := w . DeleteItems ( ctx , deletePrompts , db . NewOplogMsgs ( & deletePromptOplogMsgs ))
if err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to delete prompts" ))
}
if rowsDeleted != len ( deletePrompts ) {
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "prompts deleted %d did not match request for %d" , rowsDeleted , len ( deletePrompts )))
}
msgs = append ( msgs , deletePromptOplogMsgs ... )
}
if len ( addPrompts ) > 0 {
addPromptsOplogMsgs := make ([] * oplog . Message , 0 , len ( addPrompts ))
if err := w . CreateItems ( ctx , addPrompts , db . NewOplogMsgs ( & addPromptsOplogMsgs )); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to add prompts" ))
}
msgs = append ( msgs , addPromptsOplogMsgs ... )
}
2021-04-08 20:54:32 -04:00
if len ( deleteCerts ) > 0 {
deleteCertOplogMsgs := make ([] * oplog . Message , 0 , len ( deleteCerts ))
rowsDeleted , err := w . DeleteItems ( ctx , deleteCerts , db . NewOplogMsgs ( & deleteCertOplogMsgs ))
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to delete certificates" ))
2021-04-08 20:54:32 -04:00
}
if rowsDeleted != len ( deleteCerts ) {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "certificates deleted %d did not match request for %d" , rowsDeleted , len ( deleteCerts )))
2021-04-08 20:54:32 -04:00
}
msgs = append ( msgs , deleteCertOplogMsgs ... )
}
if len ( addCerts ) > 0 {
addCertsOplogMsgs := make ([] * oplog . Message , 0 , len ( addCerts ))
if err := w . CreateItems ( ctx , addCerts , db . NewOplogMsgs ( & addCertsOplogMsgs )); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to add certificates" ))
2021-04-08 20:54:32 -04:00
}
msgs = append ( msgs , addCertsOplogMsgs ... )
}
if len ( deleteAuds ) > 0 {
deleteAudsOplogMsgs := make ([] * oplog . Message , 0 , len ( deleteAuds ))
rowsDeleted , err := w . DeleteItems ( ctx , deleteAuds , db . NewOplogMsgs ( & deleteAudsOplogMsgs ))
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to delete audiences URLs" ))
2021-04-08 20:54:32 -04:00
}
if rowsDeleted != len ( deleteAuds ) {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "audiences deleted %d did not match request for %d" , rowsDeleted , len ( deleteAuds )))
2021-04-08 20:54:32 -04:00
}
msgs = append ( msgs , deleteAudsOplogMsgs ... )
}
if len ( addAuds ) > 0 {
addAudsOplogMsgs := make ([] * oplog . Message , 0 , len ( addAuds ))
if err := w . CreateItems ( ctx , addAuds , db . NewOplogMsgs ( & addAudsOplogMsgs )); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to add audiences URLs" ))
2021-04-08 20:54:32 -04:00
}
msgs = append ( msgs , addAudsOplogMsgs ... )
}
2021-04-28 06:53:13 -04:00
if len ( deleteScopes ) > 0 {
deleteScopesOplogMsgs := make ([] * oplog . Message , 0 , len ( deleteScopes ))
rowsDeleted , err := w . DeleteItems ( ctx , deleteScopes , db . NewOplogMsgs ( & deleteScopesOplogMsgs ))
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to delete claims scopes" ))
2021-04-28 06:53:13 -04:00
}
if rowsDeleted != len ( deleteScopes ) {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "claims scopes deleted %d did not match request for %d" , rowsDeleted , len ( deleteScopes )))
2021-04-28 06:53:13 -04:00
}
msgs = append ( msgs , deleteScopesOplogMsgs ... )
}
if len ( addScopes ) > 0 {
addScopesOplogMsgs := make ([] * oplog . Message , 0 , len ( addScopes ))
if err := w . CreateItems ( ctx , addScopes , db . NewOplogMsgs ( & addScopesOplogMsgs )); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to add claims scopes" ))
2021-04-28 06:53:13 -04:00
}
msgs = append ( msgs , addScopesOplogMsgs ... )
}
2021-05-04 08:41:04 -04:00
if len ( deleteMaps ) > 0 {
deleteMapsOplogMsgs := make ([] * oplog . Message , 0 , len ( deleteMaps ))
rowsDeleted , err := w . DeleteItems ( ctx , deleteMaps , db . NewOplogMsgs ( & deleteMapsOplogMsgs ))
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to delete account claim maps" ))
2021-05-04 08:41:04 -04:00
}
if rowsDeleted != len ( deleteMaps ) {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "account claim maps deleted %d did not match request for %d" , rowsDeleted , len ( deleteMaps )))
2021-05-04 08:41:04 -04:00
}
msgs = append ( msgs , deleteMapsOplogMsgs ... )
}
if len ( addMaps ) > 0 {
addMapsOplogMsgs := make ([] * oplog . Message , 0 , len ( addMaps ))
if err := w . CreateItems ( ctx , addMaps , db . NewOplogMsgs ( & addMapsOplogMsgs )); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to add account claim maps" ))
2021-05-04 08:41:04 -04:00
}
msgs = append ( msgs , addMapsOplogMsgs ... )
}
2021-04-08 20:54:32 -04:00
metadata := updatedAm . oplog ( oplog . OpType_OP_TYPE_UPDATE )
if err := w . WriteOplogEntryWith ( ctx , oplogWrapper , ticket , metadata , msgs ); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to write oplog" ))
2021-04-08 20:54:32 -04:00
}
// we need a new repo, that's using the same reader/writer as this TxHandler
txRepo := & Repository {
reader : reader ,
writer : w ,
kms : r . kms ,
// intentionally not setting the defaultLimit, so we'll get all
// the account ids without a limit
}
updatedAm , err = txRepo . lookupAuthMethod ( ctx , updatedAm . PublicId )
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to lookup auth method after update" ))
2021-04-08 20:54:32 -04:00
}
if updatedAm == nil {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . RecordNotFound , op , "unable to lookup auth method after update" )
2021-04-08 20:54:32 -04:00
}
return nil
},
)
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
providerCache (). delete ( ctx , updatedAm . PublicId )
return updatedAm , rowsUpdated , nil
}
// voName represents the names of auth method value objects
type voName string
const (
2021-05-04 08:41:04 -04:00
SigningAlgVO voName = "SigningAlgs"
CertificateVO voName = "Certificates"
AudClaimVO voName = "AudClaims"
ClaimsScopesVO voName = "ClaimsScopes"
AccountClaimMapsVO voName = "AccountClaimMaps"
2023-11-21 11:30:02 -05:00
PromptsVO voName = "Prompts"
2021-04-08 20:54:32 -04:00
)
// validVoName decides if the name is valid
func validVoName ( name voName ) bool {
switch name {
2023-11-21 11:30:02 -05:00
case SigningAlgVO , CertificateVO , AudClaimVO , ClaimsScopesVO , AccountClaimMapsVO , PromptsVO :
2021-04-08 20:54:32 -04:00
return true
default :
return false
}
}
// factoryFunc defines a func type for value object factories
2022-10-25 18:29:38 -05:00
type factoryFunc func ( ctx context . Context , publicId string , i any ) ( any , error )
2021-04-08 20:54:32 -04:00
// supportedFactories are the currently supported factoryFunc for value objects
var supportedFactories = map [ voName ] factoryFunc {
2022-10-25 18:29:38 -05:00
SigningAlgVO : func ( ctx context . Context , publicId string , i any ) ( any , error ) {
2021-04-08 20:54:32 -04:00
str := fmt . Sprintf ( "%s" , i )
2021-07-29 18:52:56 -06:00
return NewSigningAlg ( ctx , publicId , Alg ( str ))
2021-04-08 20:54:32 -04:00
},
2022-10-25 18:29:38 -05:00
CertificateVO : func ( ctx context . Context , publicId string , i any ) ( any , error ) {
2021-04-08 20:54:32 -04:00
str := fmt . Sprintf ( "%s" , i )
2021-07-29 18:52:56 -06:00
return NewCertificate ( ctx , publicId , str )
2021-04-08 20:54:32 -04:00
},
2022-10-25 18:29:38 -05:00
AudClaimVO : func ( ctx context . Context , publicId string , i any ) ( any , error ) {
2021-04-08 20:54:32 -04:00
str := fmt . Sprintf ( "%s" , i )
2021-07-29 18:52:56 -06:00
return NewAudClaim ( ctx , publicId , str )
2021-04-08 20:54:32 -04:00
},
2022-10-25 18:29:38 -05:00
ClaimsScopesVO : func ( ctx context . Context , publicId string , i any ) ( any , error ) {
2021-04-28 06:53:13 -04:00
str := fmt . Sprintf ( "%s" , i )
2021-07-29 18:52:56 -06:00
return NewClaimsScope ( ctx , publicId , str )
2021-04-28 06:53:13 -04:00
},
2022-10-25 18:29:38 -05:00
AccountClaimMapsVO : func ( ctx context . Context , publicId string , i any ) ( any , error ) {
2021-05-04 08:41:04 -04:00
const op = "oidc.AccountClaimMapsFactory"
str := fmt . Sprintf ( "%s" , i )
2021-07-29 18:52:56 -06:00
acm , err := ParseAccountClaimMaps ( ctx , str )
2021-05-04 08:41:04 -04:00
if err != nil {
return nil , err
}
if len ( acm ) > 1 {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "unable to parse account claim map %s" , str ))
2021-05-04 08:41:04 -04:00
}
2021-06-02 07:09:41 -04:00
var m ClaimMap
for _ , m = range acm {
2021-05-04 08:41:04 -04:00
}
2021-07-29 18:52:56 -06:00
to , err := ConvertToAccountToClaim ( ctx , m . To )
2021-05-04 08:41:04 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2021-05-04 08:41:04 -04:00
}
2021-07-29 18:52:56 -06:00
return NewAccountClaimMap ( ctx , publicId , m . From , to )
2021-05-04 08:41:04 -04:00
},
2023-11-21 11:30:02 -05:00
PromptsVO : func ( ctx context . Context , publicId string , i any ) ( any , error ) {
str := fmt . Sprintf ( "%s" , i )
return NewPrompt ( ctx , publicId , PromptParam ( str ))
},
2021-04-08 20:54:32 -04:00
}
// valueObjectChanges takes the new and old list of VOs (value objects) and
// using the dbMasks/nullFields it will return lists of VOs where need to be
// added and deleted in order to reconcile auth method's value objects.
func valueObjectChanges (
2021-07-29 18:52:56 -06:00
ctx context . Context ,
2021-04-08 20:54:32 -04:00
publicId string ,
valueObjectName voName ,
newVOs ,
oldVOs ,
dbMask ,
nullFields [] string ,
2022-10-25 18:29:38 -05:00
) ( add [] any , del [] any , e error ) {
2021-04-08 20:54:32 -04:00
const op = "valueObjectChanges"
if publicId == "" {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "missing public id" )
2021-04-08 20:54:32 -04:00
}
if ! validVoName ( valueObjectName ) {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "invalid value object name: %s" , valueObjectName ))
2021-04-08 20:54:32 -04:00
}
if ! strutil . StrListContains ( dbMask , string ( valueObjectName )) && ! strutil . StrListContains ( nullFields , string ( valueObjectName )) {
return nil , nil , nil
}
if len ( strutil . RemoveDuplicates ( newVOs , false )) != len ( newVOs ) {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "duplicate new %s" , valueObjectName ))
2021-04-08 20:54:32 -04:00
}
if len ( strutil . RemoveDuplicates ( oldVOs , false )) != len ( oldVOs ) {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "duplicate old %s" , valueObjectName ))
2021-04-08 20:54:32 -04:00
}
factory , ok := supportedFactories [ valueObjectName ]
if ! ok {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "unsupported factory for value object: %s" , valueObjectName ))
2021-04-08 20:54:32 -04:00
}
if len ( dbMask ) == 0 && len ( nullFields ) == 0 {
return nil , nil , nil
}
foundVOs := map [ string ] bool {}
for _ , a := range oldVOs {
foundVOs [ a ] = true
}
2022-10-25 18:29:38 -05:00
var adds [] any
var deletes [] any
2021-04-08 20:54:32 -04:00
if strutil . StrListContains ( nullFields , string ( valueObjectName )) {
2022-10-25 18:29:38 -05:00
deletes = make ([] any , 0 , len ( oldVOs ))
2021-05-04 08:41:04 -04:00
for _ , v := range oldVOs {
2021-07-29 18:52:56 -06:00
deleteObj , err := factory ( ctx , publicId , v )
2021-04-08 20:54:32 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
2021-05-04 08:41:04 -04:00
deletes = append ( deletes , deleteObj )
delete ( foundVOs , v )
2021-04-08 20:54:32 -04:00
}
}
if strutil . StrListContains ( dbMask , string ( valueObjectName )) {
2022-10-25 18:29:38 -05:00
adds = make ([] any , 0 , len ( newVOs ))
2021-05-04 08:41:04 -04:00
for _ , v := range newVOs {
if _ , ok := foundVOs [ v ]; ok {
delete ( foundVOs , v )
2021-04-08 20:54:32 -04:00
continue
}
2021-07-29 18:52:56 -06:00
obj , err := factory ( ctx , publicId , v )
2021-04-08 20:54:32 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
2021-05-04 08:41:04 -04:00
adds = append ( adds , obj )
delete ( foundVOs , v )
2021-04-08 20:54:32 -04:00
}
}
if len ( foundVOs ) > 0 {
2021-05-04 08:41:04 -04:00
for v := range foundVOs {
2021-07-29 18:52:56 -06:00
obj , err := factory ( ctx , publicId , v )
2021-04-08 20:54:32 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
2021-05-04 08:41:04 -04:00
deletes = append ( deletes , obj )
delete ( foundVOs , v )
2021-04-08 20:54:32 -04:00
}
}
return adds , deletes , nil
}
// validateFieldMask check the field mask to ensure all the fields are updatable
2021-07-29 18:52:56 -06:00
func validateFieldMask ( ctx context . Context , fieldMaskPaths [] string ) error {
2021-04-08 20:54:32 -04:00
const op = "validateFieldMask"
for _ , f := range fieldMaskPaths {
switch {
2021-04-28 06:53:13 -04:00
case strings . EqualFold ( NameField , f ):
case strings . EqualFold ( DescriptionField , f ):
case strings . EqualFold ( IssuerField , f ):
case strings . EqualFold ( ClientIdField , f ):
case strings . EqualFold ( ClientSecretField , f ):
case strings . EqualFold ( MaxAgeField , f ):
case strings . EqualFold ( SigningAlgsField , f ):
case strings . EqualFold ( ApiUrlField , f ):
case strings . EqualFold ( AudClaimsField , f ):
case strings . EqualFold ( CertificatesField , f ):
case strings . EqualFold ( ClaimsScopesField , f ):
2021-05-04 08:41:04 -04:00
case strings . EqualFold ( AccountClaimMapsField , f ):
2023-11-21 11:30:02 -05:00
case strings . EqualFold ( PromptsField , f ):
2021-04-08 20:54:32 -04:00
default :
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "invalid field mask: %s" , f ))
2021-04-08 20:54:32 -04:00
}
}
return nil
}
// applyUpdate takes the new and applies it to the orig using the field masks
func applyUpdate ( new , orig * AuthMethod , fieldMaskPaths [] string ) * AuthMethod {
cp := orig . Clone ()
for _ , f := range fieldMaskPaths {
switch f {
2021-04-28 06:53:13 -04:00
case NameField :
2021-04-08 20:54:32 -04:00
cp . Name = new . Name
2021-04-28 06:53:13 -04:00
case DescriptionField :
2021-04-08 20:54:32 -04:00
cp . Description = new . Description
2021-04-28 06:53:13 -04:00
case IssuerField :
2021-04-08 20:54:32 -04:00
cp . Issuer = new . Issuer
2021-04-28 06:53:13 -04:00
case ClientIdField :
2021-04-08 20:54:32 -04:00
cp . ClientId = new . ClientId
2021-04-28 06:53:13 -04:00
case ClientSecretField :
2021-04-08 20:54:32 -04:00
cp . ClientSecret = new . ClientSecret
2021-04-28 06:53:13 -04:00
case MaxAgeField :
2021-04-08 20:54:32 -04:00
cp . MaxAge = new . MaxAge
2021-04-28 06:53:13 -04:00
case ApiUrlField :
2021-04-08 20:54:32 -04:00
cp . ApiUrl = new . ApiUrl
2021-04-28 06:53:13 -04:00
case SigningAlgsField :
2021-04-08 20:54:32 -04:00
switch {
case len ( new . SigningAlgs ) == 0 :
cp . SigningAlgs = nil
default :
cp . SigningAlgs = make ([] string , 0 , len ( new . SigningAlgs ))
cp . SigningAlgs = append ( cp . SigningAlgs , new . SigningAlgs ... )
}
2021-04-28 06:53:13 -04:00
case AudClaimsField :
2021-04-08 20:54:32 -04:00
switch {
case len ( new . AudClaims ) == 0 :
cp . AudClaims = nil
default :
cp . AudClaims = make ([] string , 0 , len ( new . AudClaims ))
cp . AudClaims = append ( cp . AudClaims , new . AudClaims ... )
}
2021-04-28 06:53:13 -04:00
case CertificatesField :
2021-04-08 20:54:32 -04:00
switch {
case len ( new . Certificates ) == 0 :
cp . Certificates = nil
default :
cp . Certificates = make ([] string , 0 , len ( new . Certificates ))
cp . Certificates = append ( cp . Certificates , new . Certificates ... )
}
2021-04-28 06:53:13 -04:00
case ClaimsScopesField :
switch {
case len ( new . ClaimsScopes ) == 0 :
cp . ClaimsScopes = nil
default :
cp . ClaimsScopes = make ([] string , 0 , len ( new . ClaimsScopes ))
cp . ClaimsScopes = append ( cp . ClaimsScopes , new . ClaimsScopes ... )
}
2021-05-04 08:41:04 -04:00
case AccountClaimMapsField :
switch {
case len ( new . AccountClaimMaps ) == 0 :
cp . AccountClaimMaps = nil
default :
cp . AccountClaimMaps = make ([] string , 0 , len ( new . AccountClaimMaps ))
cp . AccountClaimMaps = append ( cp . AccountClaimMaps , new . AccountClaimMaps ... )
}
2023-11-21 11:30:02 -05:00
case PromptsField :
switch {
case len ( new . Prompts ) == 0 :
cp . Prompts = nil
default :
cp . Prompts = make ([] string , 0 , len ( new . Prompts ))
cp . Prompts = append ( cp . Prompts , new . Prompts ... )
}
2021-04-08 20:54:32 -04:00
}
}
return cp
}
// ValidateDiscoveryInfo will test/validate the provided AuthMethod against
// the info from it's discovery URL.
//
// It will verify that all required fields for a working AuthMethod have values.
//
// If the AuthMethod is complete, ValidateDiscoveryInfo retrieves the auth
// method's OpenID Configuration document. The values in the AuthMethod
// (and associated data) are validated with the retrieved document. The issuer and
// id token signing algorithm in the configuration are validated with the
// retrieved document. ValidateDiscoveryInfo also verifies the authorization, token,
2021-07-26 17:52:18 -04:00
// and userinfo endpoints by connecting to each and uses any certificates in the
2021-04-08 20:54:32 -04:00
// configuration as trust anchors to confirm connectivity.
//
// Options supported are: WithPublicId, WithAuthMethod
func ( r * Repository ) ValidateDiscoveryInfo ( ctx context . Context , opt ... Option ) error {
const op = "oidc.(Repository).ValidateDiscoveryInfo"
opts := getOpts ( opt ... )
var am * AuthMethod
switch {
case opts . withPublicId != "" :
var err error
am , err = r . lookupAuthMethod ( ctx , opts . withPublicId )
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
if am == nil {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . RecordNotFound , op , fmt . Sprintf ( "unable to lookup auth method %s" , opts . withPublicId ))
2021-04-08 20:54:32 -04:00
}
case opts . withAuthMethod != nil :
am = opts . withAuthMethod
default :
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . InvalidParameter , op , "neither WithPublicId(...) nor WithAuthMethod(...) options were provided" )
2021-04-08 20:54:32 -04:00
}
// FYI: once converted to an oidc.Provider, any certs configured will be used as trust anchors for all HTTP requests
provider , err := convertToProvider ( ctx , am )
if err != nil && am . OperationalState == string ( InactiveState ) {
return nil
}
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
info , err := provider . DiscoveryInfo ( ctx )
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
2023-09-17 19:35:43 -04:00
var result error
2021-04-08 20:54:32 -04:00
if info . Issuer != am . Issuer {
2023-09-17 19:35:43 -04:00
result = stderrors . Join ( result , errors . New ( ctx , errors . InvalidParameter , op ,
2021-04-08 20:54:32 -04:00
fmt . Sprintf ( "auth method issuer doesn't match discovery issuer: expected %s and got %s" , am . Issuer , info . Issuer )))
}
for _ , a := range am . SigningAlgs {
if ! strutil . StrListContains ( info . IdTokenSigningAlgsSupported , a ) {
2023-09-17 19:35:43 -04:00
result = stderrors . Join ( result , errors . New ( ctx , errors . InvalidParameter , op ,
2021-04-08 20:54:32 -04:00
fmt . Sprintf ( "auth method signing alg is not in discovered supported algs: expected %s and got %s" , a , info . IdTokenSigningAlgsSupported )))
}
}
providerClient , err := provider . HTTPClient ()
if err != nil {
2023-09-17 19:35:43 -04:00
result = stderrors . Join ( result , errors . New ( ctx , errors . Unknown , op , "unable to get oidc http client" , errors . WithWrap ( err )))
return result
2021-04-08 20:54:32 -04:00
}
// we need to prevent redirects during these tests... we don't want to have
// redirects going to the controller's callback (aka the configured provider's callback)
providerClient . CheckRedirect = func ( req * http . Request , via [] * http . Request ) error {
return http . ErrUseLastResponse
}
// test JWKs URL
statusCode , err := pingEndpoint ( ctx , providerClient , "JWKs" , "GET" , info . JWKSURL )
if err != nil {
2023-09-17 19:35:43 -04:00
result = stderrors . Join ( result , errors . New ( ctx , errors . Unknown , op , fmt . Sprintf ( "unable to verify JWKs endpoint: %s" , info . JWKSURL ), errors . WithWrap ( err )))
2021-04-08 20:54:32 -04:00
}
if statusCode != 200 {
2023-09-17 19:35:43 -04:00
result = stderrors . Join ( result , errors . New ( ctx , errors . Unknown , op , fmt . Sprintf ( "non-200 status (%d) from JWKs endpoint: %s" , statusCode , info . JWKSURL ), errors . WithWrap ( err )))
2021-04-08 20:54:32 -04:00
}
// test Auth URL
if _ , err := pingEndpoint ( ctx , providerClient , "AuthURL" , "GET" , info . AuthURL ); err != nil {
2023-09-17 19:35:43 -04:00
result = stderrors . Join ( result , errors . New ( ctx , errors . Unknown , op , fmt . Sprintf ( "unable to verify authorize endpoint: %s" , info . AuthURL ), errors . WithWrap ( err )))
2021-04-08 20:54:32 -04:00
}
// test Token URL
if _ , err := pingEndpoint ( ctx , providerClient , "TokenURL" , "POST" , info . TokenURL ); err != nil {
2023-09-17 19:35:43 -04:00
result = stderrors . Join ( result , errors . New ( ctx , errors . Unknown , op , fmt . Sprintf ( "unable to verify token endpoint: %s" , info . TokenURL ), errors . WithWrap ( err )))
2021-04-08 20:54:32 -04:00
}
// we're not verifying the UserInfo URL, since it's not a required dependency.
2023-09-17 19:35:43 -04:00
return result
2021-04-08 20:54:32 -04:00
}
type HTTPClient interface {
Do ( req * http . Request ) ( * http . Response , error )
}
// pingEndpoint will make an attempted http request, return status code and errors
func pingEndpoint ( ctx context . Context , client HTTPClient , endpointType , method , url string ) ( int , error ) {
const op = "oidc.pingEndpoint"
req , err := http . NewRequestWithContext ( ctx , method , url , nil )
if err != nil {
2021-07-29 18:52:56 -06:00
return 0 , errors . New ( ctx , errors . Unknown , op , fmt . Sprintf ( "unable to create %s http request" , endpointType ), errors . WithWrap ( err ))
2021-04-08 20:54:32 -04:00
}
resp , err := client . Do ( req )
if err != nil {
2021-07-29 18:52:56 -06:00
return 0 , errors . New ( ctx , errors . Unknown , op , fmt . Sprintf ( "request to %s endpoint failed" , endpointType ), errors . WithWrap ( err ))
2021-04-08 20:54:32 -04:00
}
2024-02-16 08:30:16 -05:00
if resp . Body != nil {
resp . Body . Close ()
}
2021-04-08 20:54:32 -04:00
return resp . StatusCode , nil
}