Skip to content

Commit

Permalink
feat(sdk): Use issuer metadata as a fallback for token endpoint (#593)
Browse files Browse the repository at this point in the history
Signed-off-by: Derek Trider <[email protected]>
  • Loading branch information
Derek Trider authored Sep 8, 2023
1 parent ba89664 commit 252b61b
Show file tree
Hide file tree
Showing 8 changed files with 408 additions and 218 deletions.
1 change: 1 addition & 0 deletions pkg/models/issuer/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Metadata struct {
CredentialEndpoint string `json:"credential_endpoint,omitempty"`
CredentialsSupported []SupportedCredential `json:"credentials_supported,omitempty"`
LocalizedIssuerDisplays []LocalizedIssuerDisplay `json:"display,omitempty"`
TokenEndpoint string `json:"token_endpoint,omitempty"`
}

// SupportedCredential represents metadata about a credential type that a credential issuer can issue.
Expand Down
2 changes: 2 additions & 0 deletions pkg/openid4ci/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
UnsupportedCredentialTypeError = "UNSUPPORTED_CREDENTIAL_TYPE"
InvalidOrMissingProofError = "INVALID_OR_MISSING_PROOF"
UnsupportedIssuanceURISchemeError = "UNSUPPORTED_ISSUANCE_URI_SCHEME"
NoTokenEndpointAvailableError = "NO_TOKEN_ENDPOINT_AVAILABLE" //nolint:gosec //false positive
)

// Constants' names and reasons are obvious, so they do not require additional comments.
Expand All @@ -55,4 +56,5 @@ const (
UnsupportedCredentialTypeErrorCode = 17
InvalidOrMissingProofErrorCode = 18
UnsupportedIssuanceURISchemeCode
NoTokenEndpointAvailableErrorCode = 19
)
81 changes: 52 additions & 29 deletions pkg/openid4ci/interaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import (
"github.com/trustbloc/wallet-sdk/pkg/walleterror"
)

const getIssuerMetadataEventText = "Get issuer metadata"

// This is a common object shared by both the IssuerInitiatedInteraction and WalletInitiatedInteraction objects.
type interaction struct {
issuerURI string
Expand All @@ -57,7 +59,7 @@ type interaction struct {
func (i *interaction) createAuthorizationURL(clientID, redirectURI, format string, types []string, issuerState *string,
scopes []string, useOAuthDiscoverableClientIDScheme bool,
) (string, error) {
err := i.populateIssuerMetadata()
err := i.populateIssuerMetadata("Authorization")
if err != nil {
return "", err
}
Expand Down Expand Up @@ -191,16 +193,12 @@ func (i *interaction) requestAccessToken(redirectURIWithAuthCode string) error {
errors.New("state in redirect URI does not match the state from the authorization URL"))
}

i.openIDConfig, err = i.getOpenIDConfig()
tokenEndpoint, err := i.getTokenEndpoint()
if err != nil {
return walleterror.NewExecutionError(
ErrorModule,
IssuerOpenIDConfigFetchFailedCode,
IssuerOpenIDConfigFetchFailedError,
fmt.Errorf("failed to fetch issuer's OpenID configuration: %w", err))
return err
}

i.oAuth2Config.Endpoint.TokenURL = i.openIDConfig.TokenEndpoint
i.oAuth2Config.Endpoint.TokenURL = tokenEndpoint

ctx := context.WithValue(context.Background(), oauth2.HTTPClient, i.httpClient)

Expand All @@ -210,6 +208,41 @@ func (i *interaction) requestAccessToken(redirectURIWithAuthCode string) error {
return err
}

func (i *interaction) getTokenEndpoint() (string, error) {
var err error

i.openIDConfig, err = i.getOpenIDConfig()
if err != nil {
// Fall back to the issuer metadata. See if it defines the token endpoint instead.
if i.issuerMetadata.TokenEndpoint == "" {
return "", walleterror.NewExecutionError(
ErrorModule,
NoTokenEndpointAvailableErrorCode,
NoTokenEndpointAvailableError,
fmt.Errorf("no token endpoint available. An OpenID configuration couldn't be fetched, and "+
"the issuer's metadata doesn't specify a token endpoint. "+
"OpenID configuration fetch error: %w", err))
}

return i.issuerMetadata.TokenEndpoint, nil
}

if i.openIDConfig.TokenEndpoint != "" {
return i.openIDConfig.TokenEndpoint, nil
}

if i.issuerMetadata.TokenEndpoint != "" {
return i.issuerMetadata.TokenEndpoint, nil
}

return "", walleterror.NewExecutionError(
ErrorModule,
NoTokenEndpointAvailableErrorCode,
NoTokenEndpointAvailableError,
errors.New("no token endpoint available. Neither the OpenID configuration nor the issuer's "+
"metadata specify one"))
}

func (i *interaction) dynamicClientRegistrationSupported() (bool, error) {
var err error

Expand Down Expand Up @@ -272,33 +305,23 @@ func (i *interaction) getOpenIDConfig() (*OpenIDConfig, error) {
return &config, nil
}

// getIssuerMetadata returns the issuer's metadata. If the issuer's metadata has already been fetched before,
// then it's returned without making an additional call.
func (i *interaction) getIssuerMetadata() (*issuer.Metadata, error) {
// If the issuer's metadata has not been fetched before in this interaction's lifespan, then this method fetches the
// issuer's metadata and stores it within this interaction object. If the issuer's metadata has already been fetched
// before, then this method does nothing in order to avoid making an unnecessary GET call.
func (i *interaction) populateIssuerMetadata(parentEvent string) error {
if i.issuerMetadata == nil {
err := i.populateIssuerMetadata()
issuerMetadata, err := metadatafetcher.Get(i.issuerURI, i.httpClient, i.metricsLogger, parentEvent)
if err != nil {
return nil, err
return walleterror.NewExecutionError(
ErrorModule,
MetadataFetchFailedCode,
MetadataFetchFailedError,
fmt.Errorf("failed to get issuer metadata: %w", err))
}
}

return i.issuerMetadata, nil
}

// populateIssuerMetadata fetches the issuer's metadata and stores it within this interaction object.
func (i *interaction) populateIssuerMetadata() error {
issuerMetadata, err := metadatafetcher.Get(i.issuerURI, i.httpClient, i.metricsLogger,
"Authorization")
if err != nil {
return walleterror.NewExecutionError(
ErrorModule,
MetadataFetchFailedCode,
MetadataFetchFailedError,
fmt.Errorf("failed to get issuer metadata: %w", err))
i.issuerMetadata = issuerMetadata
}

i.issuerMetadata = issuerMetadata

return nil
}

Expand Down
42 changes: 17 additions & 25 deletions pkg/openid4ci/issuerinitiatedinteraction.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (

"github.com/trustbloc/wallet-sdk/pkg/api"
"github.com/trustbloc/wallet-sdk/pkg/internal/httprequest"
metadatafetcher "github.com/trustbloc/wallet-sdk/pkg/internal/issuermetadata"
"github.com/trustbloc/wallet-sdk/pkg/walleterror"
)

Expand Down Expand Up @@ -252,7 +251,12 @@ func (i *IssuerInitiatedInteraction) DynamicClientRegistrationEndpoint() (string

// IssuerMetadata returns the issuer's metadata.
func (i *IssuerInitiatedInteraction) IssuerMetadata() (*issuer.Metadata, error) {
return i.interaction.getIssuerMetadata()
err := i.interaction.populateIssuerMetadata(getIssuerMetadataEventText)
if err != nil {
return nil, err
}

return i.interaction.issuerMetadata, nil
}

func (i *IssuerInitiatedInteraction) requestCredentialWithPreAuth(jwtSigner api.JWTSigner,
Expand Down Expand Up @@ -304,21 +308,20 @@ func (i *IssuerInitiatedInteraction) requestCredentialWithPreAuth(jwtSigner api.
})
}

func (i *IssuerInitiatedInteraction) getCredentialResponsesWithPreAuth( //nolint:funlen // Difficult to decompose
func (i *IssuerInitiatedInteraction) getCredentialResponsesWithPreAuth(
pin string, signer api.JWTSigner,
) ([]CredentialResponse, error) {
var err error
err := i.interaction.populateIssuerMetadata(requestCredentialEventText)
if err != nil {
return nil, err
}

i.interaction.openIDConfig, err = i.interaction.getOpenIDConfig()
tokenEndpoint, err := i.interaction.getTokenEndpoint()
if err != nil {
return nil, walleterror.NewExecutionError(
ErrorModule,
IssuerOpenIDConfigFetchFailedCode,
IssuerOpenIDConfigFetchFailedError,
fmt.Errorf("failed to fetch issuer's OpenID configuration: %w", err))
return nil, err
}

tokenResponse, err := i.getPreAuthTokenResponse(pin)
tokenResponse, err := i.getPreAuthTokenResponse(pin, tokenEndpoint)
if err != nil {
return nil, fmt.Errorf("failed to get token response: %w", err)
}
Expand All @@ -328,17 +331,6 @@ func (i *IssuerInitiatedInteraction) getCredentialResponsesWithPreAuth( //nolint
return nil, err
}

i.interaction.issuerMetadata, err = metadatafetcher.Get(i.interaction.issuerURI, i.interaction.httpClient,
i.interaction.metricsLogger,
requestCredentialEventText)
if err != nil {
return nil, walleterror.NewExecutionError(
ErrorModule,
MetadataFetchFailedCode,
MetadataFetchFailedError,
fmt.Errorf("failed to get issuer metadata: %w", err))
}

credentialResponses := make([]CredentialResponse, len(i.credentialTypes))

for index := range i.credentialTypes {
Expand Down Expand Up @@ -372,7 +364,7 @@ func (i *IssuerInitiatedInteraction) getCredentialResponsesWithPreAuth( //nolint
return credentialResponses, nil
}

func (i *IssuerInitiatedInteraction) getPreAuthTokenResponse(pin string) (*preAuthTokenResponse, error) {
func (i *IssuerInitiatedInteraction) getPreAuthTokenResponse(pin, tokenEndpoint string) (*preAuthTokenResponse, error) {
params := url.Values{}
params.Add("grant_type", preAuthorizedGrantType)
params.Add("pre-authorized_code", i.preAuthorizedCodeGrantParams.preAuthorizedCode)
Expand All @@ -384,8 +376,8 @@ func (i *IssuerInitiatedInteraction) getPreAuthTokenResponse(pin string) (*preAu
paramsReader := strings.NewReader(params.Encode())

responseBytes, err := httprequest.New(i.interaction.httpClient, i.interaction.metricsLogger).Do(
http.MethodPost, i.interaction.openIDConfig.TokenEndpoint, "application/x-www-form-urlencoded", paramsReader,
fmt.Sprintf(fetchTokenViaPOSTReqEventText, i.interaction.openIDConfig.TokenEndpoint),
http.MethodPost, tokenEndpoint, "application/x-www-form-urlencoded", paramsReader,
fmt.Sprintf(fetchTokenViaPOSTReqEventText, tokenEndpoint),
requestCredentialEventText, tokenErrorResponseHandler)
if err != nil {
return nil, fmt.Errorf("issuer's token endpoint: %w", err)
Expand Down
Loading

0 comments on commit 252b61b

Please sign in to comment.