Skip to content

Commit

Permalink
feat: change well-known url path for signed cred issuer config (#1431)
Browse files Browse the repository at this point in the history
Signed-off-by: Mykhailo Sizov <[email protected]>
  • Loading branch information
mishasizov-SK authored Sep 20, 2023
1 parent 94cb37d commit 7e624a4
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 12 deletions.
13 changes: 12 additions & 1 deletion pkg/service/oidc4ci/oidc4ci_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,18 @@ func (s *Service) PrepareCredential(
return nil, resterr.NewCustomError(resterr.OIDCCredentialTypeNotSupported, ErrCredentialTemplateNotConfigured)
}

expectedAudience := fmt.Sprintf("%v/issuer/%s/%s", s.issuerVCSPublicHost, tx.ProfileID, tx.ProfileVersion)
profile, err := s.profileService.GetProfile(tx.ProfileID, tx.ProfileVersion)
if err != nil {
return nil, err
}

var staticURLPathChunk string
if profile.OIDCConfig != nil && profile.OIDCConfig.SignedIssuerMetadataSupported {
staticURLPathChunk = "/static"
}

expectedAudience := fmt.Sprintf("%v/issuer%s/%s/%s",
s.issuerVCSPublicHost, staticURLPathChunk, tx.ProfileID, tx.ProfileVersion)

if req.AudienceClaim == "" || req.AudienceClaim != expectedAudience {
return nil, resterr.NewValidationError(resterr.InvalidOrMissingProofOIDCErr, req.AudienceClaim,
Expand Down
11 changes: 8 additions & 3 deletions pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,17 @@ func (s *Service) findCredentialTemplate(
}

func (s *Service) prepareCredentialOffer(
_ context.Context,
profile *profileapi.Issuer,
req *InitiateIssuanceRequest,
template *profileapi.CredentialTemplate,
tx *Transaction,
) *CredentialOfferResponse {
issuerURL, _ := url.JoinPath(s.issuerVCSPublicHost, "issuer", tx.ProfileID, tx.ProfileVersion)
var staticURLPathChunk string
if profile.OIDCConfig != nil && profile.OIDCConfig.SignedIssuerMetadataSupported {
staticURLPathChunk = "static"
}

issuerURL, _ := url.JoinPath(s.issuerVCSPublicHost, "issuer", staticURLPathChunk, tx.ProfileID, tx.ProfileVersion)

resp := &CredentialOfferResponse{
CredentialIssuer: issuerURL,
Expand Down Expand Up @@ -391,7 +396,7 @@ func (s *Service) buildInitiateIssuanceURL(
tx *Transaction,
profile *profileapi.Issuer,
) (string, InitiateIssuanceResponseContentType, error) {
credentialOffer := s.prepareCredentialOffer(ctx, req, template, tx)
credentialOffer := s.prepareCredentialOffer(profile, req, template, tx)

var (
signedCredentialOfferJWT string
Expand Down
22 changes: 17 additions & 5 deletions pkg/service/oidc4ci/oidc4ci_service_initiate_issuance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func TestService_InitiateIssuance(t *testing.T) {
check func(t *testing.T, resp *oidc4ci.InitiateIssuanceResponse, err error)
}{
{
name: "Success",
name: "Success and SignedIssuerMetadataSupported: true",
setup: func() {
mockTransactionStore.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any()).
DoAndReturn(func(
Expand Down Expand Up @@ -109,17 +109,25 @@ func TestService_InitiateIssuance(t *testing.T) {
Scope: []string{"openid", "profile"},
}

profile = &testProfile
var localTestProfile profileapi.Issuer
require.NoError(t, json.Unmarshal(profileJSON, &localTestProfile))
localTestProfile.OIDCConfig.SignedIssuerMetadataSupported = true

profile = &localTestProfile
},
check: func(t *testing.T, resp *oidc4ci.InitiateIssuanceResponse, err error) {
require.NoError(t, err)
assert.NotNil(t, resp.Tx)
require.Contains(t, resp.InitiateIssuanceURL, "https://wallet.example.com/initiate_issuance")
require.Equal(t, resp.InitiateIssuanceURL, "https://wallet.example.com/initiate_issuance?"+
"credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fvcs.pb.example.com%2Fissuer%2F"+
"static%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22%22%2C%22types%22%3A%5B%22VerifiableC"+
"redential%22%2C%22PermanentResidentCard%22%5D%7D%5D%2C%22grants%22%3A%7B%22authorization_code"+
"%22%3A%7B%22issuer_state%22%3A%22eyJhbGciOiJSU0Et%22%7D%7D%7D")
require.Equal(t, oidc4ci.ContentTypeApplicationJSON, resp.ContentType)
},
},
{
name: "Success wallet flow",
name: "Success wallet flow and SignedIssuerMetadataSupported: false",
setup: func() {
mockTransactionStore.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any()).
DoAndReturn(func(
Expand Down Expand Up @@ -173,7 +181,11 @@ func TestService_InitiateIssuance(t *testing.T) {
require.NoError(t, err)
assert.NotNil(t, resp.Tx)
assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, resp.Tx.State)
require.Contains(t, resp.InitiateIssuanceURL, "https://wallet.example.com/initiate_issuance")
require.Equal(t, resp.InitiateIssuanceURL, "https://wallet.example.com/initiate_issuance?"+
"credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fvcs.pb.example.com%2Fissuer%"+
"22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22%22%2C%22types%22%3A%5B%22VerifiableCredent"+
"ial%22%2C%22PermanentResidentCard%22%5D%7D%5D%2C%22grants%22%3A%7B%22authorization_code%22%3"+
"A%7B%22issuer_state%22%3A%22eyJhbGciOiJSU0Et%22%7D%7D%7D")
},
},
{
Expand Down
135 changes: 134 additions & 1 deletion pkg/service/oidc4ci/oidc4ci_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,7 @@ func TestService_PrepareCredential(t *testing.T) {
mockClaimDataStore = NewMockClaimDataStore(gomock.NewController(t))
eventMock = NewMockEventService(gomock.NewController(t))
crypto = NewMockDataProtector(gomock.NewController(t))
profileService = NewMockProfileService(gomock.NewController(t))
httpClient *http.Client
req *oidc4ci.PrepareCredential
)
Expand Down Expand Up @@ -1261,9 +1262,16 @@ func TestService_PrepareCredential(t *testing.T) {
return nil
})

profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).
Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{
SignedIssuerMetadataSupported: true,
},
}, nil)

req = &oidc4ci.PrepareCredential{
TxID: "txID",
AudienceClaim: "/issuer//",
AudienceClaim: "/issuer/static//",
}
},
check: func(t *testing.T, resp *oidc4ci.PrepareCredentialResult, err error) {
Expand Down Expand Up @@ -1314,6 +1322,11 @@ func TestService_PrepareCredential(t *testing.T) {
return nil
})

profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).
Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{},
}, nil)

req = &oidc4ci.PrepareCredential{
TxID: "txID",
AudienceClaim: "/issuer//",
Expand Down Expand Up @@ -1372,6 +1385,11 @@ func TestService_PrepareCredential(t *testing.T) {
return nil
})

profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).
Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{},
}, nil)

req = &oidc4ci.PrepareCredential{
TxID: "txID",
AudienceClaim: "/issuer//",
Expand Down Expand Up @@ -1420,6 +1438,11 @@ func TestService_PrepareCredential(t *testing.T) {
return nil
})

profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).
Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{},
}, nil)

clData := &oidc4ci.ClaimData{
EncryptedData: &dataprotect.EncryptedData{
Encrypted: []byte{0x1, 0x2, 0x3},
Expand Down Expand Up @@ -1461,6 +1484,11 @@ func TestService_PrepareCredential(t *testing.T) {
},
}, nil)

profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).
Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{},
}, nil)

eventMock.EXPECT().Publish(gomock.Any(), gomock.Any(), gomock.Any()).Times(0)
mockTransactionStore.EXPECT().Update(gomock.Any(), gomock.Any()).Times(0)

Expand Down Expand Up @@ -1526,6 +1554,11 @@ func TestService_PrepareCredential(t *testing.T) {
return nil
})

profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).
Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{},
}, nil)

req = &oidc4ci.PrepareCredential{
TxID: "txID",
AudienceClaim: "/issuer//",
Expand Down Expand Up @@ -1579,6 +1612,11 @@ func TestService_PrepareCredential(t *testing.T) {
return nil
})

profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).
Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{},
}, nil)

req = &oidc4ci.PrepareCredential{
TxID: "txID",
AudienceClaim: "/issuer//",
Expand Down Expand Up @@ -1629,6 +1667,30 @@ func TestService_PrepareCredential(t *testing.T) {
require.Nil(t, resp)
},
},
{
name: "Profile service error",
setup: func() {
mockTransactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{
TransactionData: oidc4ci.TransactionData{
ProfileID: "profileID",
ProfileVersion: "profileVersion",
CredentialTemplate: &profileapi.CredentialTemplate{},
},
}, nil)

profileService.EXPECT().GetProfile("profileID", "profileVersion").
Return(nil, errors.New("profile service error"))

req = &oidc4ci.PrepareCredential{
TxID: "txID",
}
},
check: func(t *testing.T, resp *oidc4ci.PrepareCredentialResult, err error) {
require.ErrorContains(t, err,
"profile service error")
require.Nil(t, resp)
},
},
{
name: "Fail to make request to claim endpoint",
setup: func() {
Expand All @@ -1646,6 +1708,11 @@ func TestService_PrepareCredential(t *testing.T) {
},
}

profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).
Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{},
}, nil)

req = &oidc4ci.PrepareCredential{
TxID: "txID",
AudienceClaim: "/issuer//",
Expand Down Expand Up @@ -1676,6 +1743,11 @@ func TestService_PrepareCredential(t *testing.T) {
},
}

profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).
Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{},
}, nil)

req = &oidc4ci.PrepareCredential{
TxID: "txID",
AudienceClaim: "/issuer//",
Expand Down Expand Up @@ -1706,6 +1778,11 @@ func TestService_PrepareCredential(t *testing.T) {
},
}

profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).
Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{},
}, nil)

req = &oidc4ci.PrepareCredential{
TxID: "txID",
AudienceClaim: "/issuer//",
Expand Down Expand Up @@ -1736,6 +1813,11 @@ func TestService_PrepareCredential(t *testing.T) {
},
}

profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).
Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{},
}, nil)

req = &oidc4ci.PrepareCredential{
TxID: "txID",
AudienceClaim: "/issuer//",
Expand Down Expand Up @@ -1774,6 +1856,11 @@ func TestService_PrepareCredential(t *testing.T) {
},
}

profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).
Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{},
}, nil)

req = &oidc4ci.PrepareCredential{
TxID: "txID",
AudienceClaim: "invalid",
Expand All @@ -1784,6 +1871,51 @@ func TestService_PrepareCredential(t *testing.T) {
require.Nil(t, resp)
},
},
{
name: "Invalid audience claim for SignedIssuerMetadataSupported:true",
setup: func() {
mockTransactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{
ID: "txID",
TransactionData: oidc4ci.TransactionData{
IssuerToken: "issuer-access-token",
CredentialTemplate: &profileapi.CredentialTemplate{
Type: "VerifiedEmployee",
},
CredentialFormat: vcsverifiable.Jwt,
},
}, nil)

claimData := `{"surname":"Smith","givenName":"Pat","jobTitle":"Worker"}`

httpClient = &http.Client{
Transport: &mockTransport{
func(req *http.Request) (*http.Response, error) {
assert.Contains(t, req.Header.Get("Authorization"), "Bearer issuer-access-token")
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBuffer([]byte(claimData))),
}, nil
},
},
}

profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).
Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{
SignedIssuerMetadataSupported: true,
},
}, nil)

req = &oidc4ci.PrepareCredential{
TxID: "txID",
AudienceClaim: "/issuer//",
}
},
check: func(t *testing.T, resp *oidc4ci.PrepareCredentialResult, err error) {
require.ErrorContains(t, err, "invalid aud")
require.Nil(t, resp)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -1796,6 +1928,7 @@ func TestService_PrepareCredential(t *testing.T) {
EventService: eventMock,
EventTopic: spi.IssuerEventTopic,
DataProtector: crypto,
ProfileService: profileService,
})
require.NoError(t, err)

Expand Down
26 changes: 26 additions & 0 deletions test/bdd/fixtures/krakend-config/settings/endpoint.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@
}
}
},
{
"endpoint": "/issuer/static/{profileID}/{profileVersion}/.well-known/openid-configuration",
"method": "GET",
"endpoint_override": "/issuer/{profileID}/{profileVersion}/.well-known/openid-configuration",
"input_query_strings": [
"*"
],
"backend_extra_config": {
"plugin/http-client": {
"name": "http-client-no-redirect"
}
}
},
{
"endpoint": "/issuer/{profileID}/{profileVersion}/.well-known/openid-credential-issuer",
"method": "GET",
Expand All @@ -63,6 +76,19 @@
}
}
},
{
"endpoint": "/issuer/static/{profileID}/{profileVersion}/.well-known/openid-credential-issuer",
"method": "GET",
"endpoint_override": "/issuer/{profileID}/{profileVersion}/.well-known/openid-credential-issuer",
"input_query_strings": [
"*"
],
"backend_extra_config": {
"plugin/http-client": {
"name": "http-client-no-redirect"
}
}
},
{
"endpoint": "/oidc/{profileID}/{profileVersion}/register",
"method": "POST",
Expand Down
Loading

0 comments on commit 7e624a4

Please sign in to comment.