Skip to content

Commit

Permalink
Merge pull request #508 from DRK3/OpenID4VPParseResponseError
Browse files Browse the repository at this point in the history
feat(sdk): OpenID4VP error response parsing
  • Loading branch information
Derek Trider authored Jul 25, 2023
2 parents 1f6dc9f + 051ec2e commit 98c8307
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ func (i *IssuerInitiatedInteraction) CreateAuthorizationURL(clientID, redirectUR
goAPIOpts = append(goAPIOpts, openid4cigoapi.WithIssuerState(*opts.issuerState))
}

return i.goAPIInteraction.CreateAuthorizationURL(clientID, redirectURI, goAPIOpts...)
authorizationURL, err := i.goAPIInteraction.CreateAuthorizationURL(clientID, redirectURI, goAPIOpts...)
if err != nil {
return "", wrapper.ToMobileErrorWithTrace(err, i.oTel)
}

return authorizationURL, nil
}

// RequestCredentialWithPreAuth requests credential(s) from the issuer. This method can only be used for the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ func TestIssuerInitiatedInteraction_CreateAuthorizationURL(t *testing.T) {
nil, false)

authorizationLink, err := interaction.CreateAuthorizationURL("clientID", "redirectURI", nil)
require.EqualError(t, err, "issuer does not support the authorization code grant type")
requireErrorContains(t, err, "INVALID_SDK_USAGE")
requireErrorContains(t, err, "issuer does not support the authorization code grant type")
require.Empty(t, authorizationLink)
})
t.Run("Conflicting issuer state", func(t *testing.T) {
Expand All @@ -203,7 +204,8 @@ func TestIssuerInitiatedInteraction_CreateAuthorizationURL(t *testing.T) {

authorizationLink, err := interaction.CreateAuthorizationURL("clientID", "redirectURI",
createAuthorizationURLOpts)
require.EqualError(t, err, "INVALID_SDK_USAGE(OCI3-0000):the credential offer already specifies "+
requireErrorContains(t, err, "INVALID_SDK_USAGE")
requireErrorContains(t, err, "the credential offer already specifies "+
"an issuer state, and a conflicting issuer state value was provided. An issuer state should only be "+
"provided if required by the issuer and the credential offer does not specify one already")
require.Empty(t, authorizationLink)
Expand Down Expand Up @@ -472,7 +474,8 @@ func TestIssuerInitiatedInteractionAlias(t *testing.T) {
// IssuerInitiatedInteraction) See TestIssuerInitiatedInteraction_RequestCredential or the integration tests for
// better examples.
authURL, err := interaction.CreateAuthorizationURL("", "", nil)
require.EqualError(t, err, "issuer does not support the authorization code grant type")
requireErrorContains(t, err, "INVALID_SDK_USAGE")
requireErrorContains(t, err, "issuer does not support the authorization code grant type")
require.Empty(t, authURL)

credentials, err = interaction.RequestCredentialWithPreAuth(nil, nil)
Expand Down
4 changes: 2 additions & 2 deletions cmd/wallet-sdk-gomobile/walleterror/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ func Parse(message string) *Error {
err := json.Unmarshal([]byte(message), walletErr)
if err != nil {
return &Error{
Code: "GNR2-000",
Category: "GENERAL_ERROR",
Code: "UKN2-000",
Category: "OTHER_ERROR",
Details: message,
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/wallet-sdk-gomobile/wrapper/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func convertToGomobileError(err error, trace *otel.Trace) *walleterror.Error {
// gomobile wallet error using a generic code.
return &walleterror.Error{
Code: "UKN2-000",
Category: "UNEXPECTED_ERROR",
Category: "OTHER_ERROR",
Details: err.Error(),
TraceID: traceID,
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/wallet-sdk-gomobile/wrapper/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestToMobileError(t *testing.T) {
parsedErr := walleterror.Parse(err.Error())

require.Equal(t, "UKN2-000", parsedErr.Code)
require.Equal(t, "UNEXPECTED_ERROR", parsedErr.Category)
require.Equal(t, "OTHER_ERROR", parsedErr.Category)
require.Equal(t, "regular Go error", parsedErr.Details)
})
t.Run("Non-goapiwalleterror.Error wrapped with another Non-goapiwalleterror.Error passed in", func(t *testing.T) {
Expand All @@ -60,7 +60,7 @@ func TestToMobileError(t *testing.T) {
parsedErr := walleterror.Parse(err.Error())

require.Equal(t, "UKN2-000", parsedErr.Code)
require.Equal(t, "UNEXPECTED_ERROR", parsedErr.Category)
require.Equal(t, "OTHER_ERROR", parsedErr.Category)
require.Equal(t, "higher-level error: regular Go error", parsedErr.Details)
})
t.Run("goapiwalleterror.Error wrapped by one higher-level non-goapiwalleterror.Error is passed in",
Expand Down
3 changes: 2 additions & 1 deletion pkg/openid4ci/issuerinitiatedinteraction.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ func (i *IssuerInitiatedInteraction) CreateAuthorizationURL(clientID, redirectUR
opts ...CreateAuthorizationURLOpt,
) (string, error) {
if !i.AuthorizationCodeGrantTypeSupported() {
return "", errors.New("issuer does not support the authorization code grant type")
return "", walleterror.NewInvalidSDKUsageError(ErrorModule,
errors.New("issuer does not support the authorization code grant type"))
}

processedOpts := processCreateAuthorizationURLOpts(opts)
Expand Down
38 changes: 26 additions & 12 deletions pkg/openid4vp/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,34 @@ package openid4vp
// Constants' names and reasons are obvious so they do not require additional comments.
// nolint:golint,nolintlint
const (
module = "OVP"
RequestObjectFetchFailedError = "REQUEST_OBJECT_FETCH_FAILED"
VerifyAuthorizationRequestFailedError = "VERIFY_AUTHORIZATION_REQUEST_FAILED"
CreateAuthorizedResponseFailedError = "CREATE_AUTHORIZED_RESPONSE"
SendAuthorizedResponseFailedError = "SEND_AUTHORIZED_RESPONSE"
NotInitializedProperlyError = "NOT_INITIALIZED_PROPERLY"
ErrorModule = "OVP"
RequestObjectFetchFailedError = "REQUEST_OBJECT_FETCH_FAILED"
VerifyAuthorizationRequestFailedError = "VERIFY_AUTHORIZATION_REQUEST_FAILED"
CreateAuthorizedResponseFailedError = "CREATE_AUTHORIZED_RESPONSE_FAILED"
InvalidScopeError = "INVALID_SCOPE"
InvalidRequestError = "INVALID_REQUEST"
InvalidClientError = "INVALID_CLIENT"
VPFormatsNotSupportedError = "VP_FORMATS_NOT_SUPPORTED"
InvalidPresentationDefinitionURIError = "INVALID_PRESENTATION_DEFINITION_URI"
InvalidPresentationDefinitionReferenceError = "INVALID_PRESENTATION_DEFINITION_REFERENCE"
OtherAuthorizationResponseError = "OTHER_AUTHORIZATION_RESPONSE_ERROR"
)

// Constants' names and reasons are obvious so they do not require additional comments.
// Constants' names and reasons are obvious, so they do not require additional comments.
// nolint:golint,nolintlint
const (
RequestObjectFetchFailedCode = iota
VerifyAuthorizationRequestFailedCode
CreateAuthorizedResponseFailedCode
SendAuthorizedResponseFailedCode
NotInitializedProperlyErrorCode
RequestObjectFetchFailedCode = 0
VerifyAuthorizationRequestFailedCode = 1
CreateAuthorizedResponseFailedCode = 2
InvalidScopeErrorCode = 3
InvalidRequestErrorCode = 4
InvalidClientErrorCode = 5
VPFormatsNotSupportedErrorCode = 6
InvalidPresentationDefinitionURIErrorCode = 7
InvalidPresentationDefinitionReferenceErrorCode = 8
OtherAuthorizationResponseErrorCode = 9
)

type errorResponse struct {
Error string `json:"error,omitempty"`
}
85 changes: 64 additions & 21 deletions pkg/openid4vp/openid4vp.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (o *Interaction) GetQuery() (*presexch.PresentationDefinition, error) {
rawRequestObject, err := o.fetchRequestObject()
if err != nil {
return nil, walleterror.NewExecutionError(
module,
ErrorModule,
RequestObjectFetchFailedCode,
RequestObjectFetchFailedError,
fmt.Errorf("fetch request object: %w", err))
Expand All @@ -115,7 +115,7 @@ func (o *Interaction) GetQuery() (*presexch.PresentationDefinition, error) {
requestObject, err := verifyAuthorizationRequestAndDecodeClaims(rawRequestObject, o.signatureVerifier)
if err != nil {
return nil, walleterror.NewExecutionError(
module,
ErrorModule,
VerifyAuthorizationRequestFailedCode,
VerifyAuthorizationRequestFailedError,
fmt.Errorf("verify authorization request: %w", err))
Expand All @@ -133,10 +133,8 @@ func (o *Interaction) GetQuery() (*presexch.PresentationDefinition, error) {
// VerifierDisplayData returns display information about verifier.
func (o *Interaction) VerifierDisplayData() (*VerifierDisplayData, error) {
if o.requestObject == nil {
return nil, walleterror.NewExecutionError(
module,
NotInitializedProperlyErrorCode,
NotInitializedProperlyError,
return nil, walleterror.NewInvalidSDKUsageError(
ErrorModule,
fmt.Errorf("call GetQuery first"))
}

Expand Down Expand Up @@ -176,10 +174,8 @@ func (o *Interaction) presentCredentials(credentials []*verifiable.Credential, o
timeStartPresentCredential := time.Now()

if o.requestObject == nil {
return walleterror.NewExecutionError(
module,
NotInitializedProperlyErrorCode,
NotInitializedProperlyError,
return walleterror.NewInvalidSDKUsageError(
ErrorModule,
fmt.Errorf("call GetQuery first"))
}

Expand All @@ -193,7 +189,7 @@ func (o *Interaction) presentCredentials(credentials []*verifiable.Credential, o
)
if err != nil {
return walleterror.NewExecutionError(
module,
ErrorModule,
CreateAuthorizedResponseFailedCode,
CreateAuthorizedResponseFailedError,
fmt.Errorf("create authorized response failed: %w", err))
Expand All @@ -206,7 +202,7 @@ func (o *Interaction) presentCredentials(credentials []*verifiable.Credential, o

err = o.sendAuthorizedResponse(data.Encode())
if err != nil {
return err
return fmt.Errorf("send authorized response failed: %w", err)
}

err = o.metricsLogger.Log(&api.MetricsEvent{
Expand Down Expand Up @@ -250,16 +246,9 @@ func (o *Interaction) sendAuthorizedResponse(responseBody string) error {
o.requestObject.RedirectURI, "application/x-www-form-urlencoded",
bytes.NewBuffer([]byte(responseBody)),
fmt.Sprintf(sendAuthorizedResponseEventText, o.requestObject.RedirectURI),
presentCredentialEventText, nil)
if err != nil {
return walleterror.NewExecutionError(
module,
SendAuthorizedResponseFailedCode,
SendAuthorizedResponseFailedError,
fmt.Errorf("send authorized response failed: %w", err))
}
presentCredentialEventText, processAuthorizationErrorResponse)

return nil
return err
}

func verifyAuthorizationRequestAndDecodeClaims(
Expand Down Expand Up @@ -568,3 +557,57 @@ func (r *resolverAdapter) Resolve(did string, opts ...vdrspi.DIDMethodOption) (*
func wrapResolver(didResolver api.DIDResolver) *resolverAdapter {
return &resolverAdapter{didResolver: didResolver}
}

func processAuthorizationErrorResponse(statusCode int, respBytes []byte) error {
detailedErr := fmt.Errorf(
"received status code [%d] with body [%s] in response to the authorization request",
statusCode, string(respBytes))

var errResponse errorResponse

err := json.Unmarshal(respBytes, &errResponse)
if err != nil {
return walleterror.NewExecutionError(ErrorModule,
OtherAuthorizationResponseErrorCode,
OtherAuthorizationResponseError,
detailedErr)
}

switch errResponse.Error {
case "invalid_scope":
return walleterror.NewExecutionError(ErrorModule,
InvalidScopeErrorCode,
InvalidScopeError,
detailedErr)
case "invalid_request":
return walleterror.NewExecutionError(ErrorModule,
InvalidRequestErrorCode,
InvalidRequestError,
detailedErr)
case "invalid_client":
return walleterror.NewExecutionError(ErrorModule,
InvalidClientErrorCode,
InvalidClientError,
detailedErr)
case "vp_formats_not_supported":
return walleterror.NewExecutionError(ErrorModule,
VPFormatsNotSupportedErrorCode,
VPFormatsNotSupportedError,
detailedErr)
case "invalid_presentation_definition_uri":
return walleterror.NewExecutionError(ErrorModule,
InvalidPresentationDefinitionURIErrorCode,
InvalidPresentationDefinitionURIError,
detailedErr)
case "invalid_presentation_definition_reference":
return walleterror.NewExecutionError(ErrorModule,
InvalidPresentationDefinitionReferenceErrorCode,
InvalidPresentationDefinitionReferenceError,
detailedErr)
default:
return walleterror.NewExecutionError(ErrorModule,
OtherAuthorizationResponseErrorCode,
OtherAuthorizationResponseError,
detailedErr)
}
}
Loading

0 comments on commit 98c8307

Please sign in to comment.