Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: profile specific transient data configuration #1691

Merged
merged 1 commit into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
377 changes: 189 additions & 188 deletions api/spec/openapi.gen.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions cmd/vc-rest/startcmd/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ const (
"OIDC4CI issuance auth state store " + "(TTL configurable via " + oidc4ciAuthStateTTLEnvKey + "), " +
"OIDC4VP transaction mapping " + "(TTL configurable via " + oidc4vpNonceTTLEnvKey + "), " +
"OIDC4VP transaction data " + "(TTL configurable via " + oidc4vpTransactionDataTTLEnvKey + "), " +
"notification data " + "(TTL configurable via " + oidc4ciAckDataTTLEnvKey + "), " +
"encrypted claim data of OIDC4VP presentation transaction. " + "(TTL configurable via " + oidc4vpReceivedClaimsDataTTLEnvKey + "). " +
"Possible values are \"redis\" or \"mongo\". Default is \"mongo\". " +
commonEnvVarUsageText + transientDataStoreTypeFlagEnvKey
Expand Down
4 changes: 2 additions & 2 deletions cmd/vc-rest/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -1178,14 +1178,14 @@ func getOIDC4CITransactionStore(

func getAckStore(
redisClient *redis.Client,
oidc4ciTransactionDataTTL int32,
oidc4ciAckDataTTL int32,
) *ackstore.Store {
if redisClient == nil {
logger.Warn("Redis client is not configured. Acknowledgement store will not be used")
return nil
}

return ackstore.New(redisClient, oidc4ciTransactionDataTTL)
return ackstore.New(redisClient, oidc4ciAckDataTTL)
}

func createRequestObjectStore(
Expand Down
4 changes: 4 additions & 0 deletions docs/v1/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1412,12 +1412,16 @@ components:
tx_id:
type: string
description: Transaction ID to correlate upcoming authorization response.
profile_auth_state_ttl:
type: integer
description: Profile specific Auth state TTL.
wallet_initiated_flow:
$ref: ./common.yaml#/components/schemas/WalletInitiatedFlowData
required:
- authorization_request
- authorization_endpoint
- tx_id
- profile_auth_state_ttl
OAuthParameters:
title: OAuthParameters
x-tags:
Expand Down
17 changes: 17 additions & 0 deletions pkg/profile/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ type Issuer struct {
WebHook string `json:"webHook,omitempty"`
CredentialMetaData *CredentialMetaData `json:"credentialMetadata"`
Checks IssuanceChecks `json:"checks"`
DataConfig IssuerDataConfig `json:"dataConfig"`
}

// IssuerDataConfig stores profile specific transient data configuration.
type IssuerDataConfig struct {
ClaimDataTTL int32
OIDC4CITransactionDataTTL int32
OIDC4CIAuthStateTTL int32
OIDC4CIAckDataTTL int32
}

type CredentialMetaData struct {
Expand Down Expand Up @@ -207,6 +216,14 @@ type Verifier struct {
SigningDID *SigningDID `json:"signingDID,omitempty"`
PresentationDefinitions []*presexch.PresentationDefinition `json:"presentationDefinitions,omitempty"`
WebHook string `json:"webHook,omitempty"`
DataConfig VerifierDataConfig `json:"dataConfig"`
}

// VerifierDataConfig stores profile specific transient data configuration.
type VerifierDataConfig struct {
OIDC4VPNonceStoreDataTTL int32
OIDC4VPTransactionDataTTL int32
OIDC4VPReceivedClaimsDataTTL int32
}

// OIDC4VPConfig store config for verifier did that used to sign request object in oidc4vp process.
Expand Down
1 change: 1 addition & 0 deletions pkg/restapi/v1/issuer/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ func (c *Controller) prepareClaimDataAuthorizationRequest(
AuthorizationEndpoint: resp.AuthorizationEndpoint,
PushedAuthorizationRequestEndpoint: lo.ToPtr(resp.PushedAuthorizationRequestEndpoint),
TxId: string(resp.TxID),
ProfileAuthStateTtl: int(profile.DataConfig.OIDC4CIAuthStateTTL),
}, nil
}

Expand Down
12 changes: 11 additions & 1 deletion pkg/restapi/v1/issuer/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1383,16 +1383,26 @@ func TestController_PrepareAuthorizationRequest(t *testing.T) {
mockProfileService := NewMockProfileService(gomock.NewController(t))
mockProfileService.EXPECT().GetProfile(profileID, profileVersion).Return(&profileapi.Issuer{
OIDCConfig: &profileapi.OIDCConfig{},
DataConfig: profileapi.IssuerDataConfig{OIDC4CIAuthStateTTL: 10},
}, nil)

c := &Controller{
oidc4ciService: mockOIDC4CIService,
profileSvc: mockProfileService,
}

recorder := httptest.NewRecorder()

req := `{"response_type":"code","op_state":"123","scope":["scope1", "scope2"]}`
ctx := echoContext(withRequestBody([]byte(req)))
ctx := echoContext(withRecorder(recorder), withRequestBody([]byte(req)))
assert.NoError(t, c.PrepareAuthorizationRequest(ctx))

var prepareClaimDataAuthorizationResponse PrepareClaimDataAuthorizationResponse

err := json.NewDecoder(recorder.Body).Decode(&prepareClaimDataAuthorizationResponse)
assert.NoError(t, err)

assert.Equal(t, prepareClaimDataAuthorizationResponse.ProfileAuthStateTtl, 10)
})

t.Run("read body error", func(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions pkg/restapi/v1/issuer/openapi.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pkg/restapi/v1/oidc4ci/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ var logger = log.New("oidc4ci")
type StateStore interface {
SaveAuthorizeState(
ctx context.Context,
profileAuthStateTTL int32,
opState string,
state *oidc4ci.AuthorizeState,
params ...func(insertOptions *oidc4ci.InsertOptions),
) error

GetAuthorizeState(ctx context.Context, opState string) (*oidc4ci.AuthorizeState, error)
Expand Down Expand Up @@ -352,6 +352,7 @@ func (c *Controller) OidcAuthorize(e echo.Context, params OidcAuthorizeParams) e

if err = c.stateStore.SaveAuthorizeState(
ctx,
int32(claimDataAuth.ProfileAuthStateTtl),
lo.FromPtr(params.IssuerState),
&oidc4ci.AuthorizeState{
RedirectURI: ar.GetRedirectURI(),
Expand Down
2 changes: 1 addition & 1 deletion pkg/restapi/v1/oidc4ci/controller_e2e_flows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,9 +541,9 @@ func (s *memoryStateStore) GetAuthorizeState(

func (s *memoryStateStore) SaveAuthorizeState(
_ context.Context,
_ int32,
opState string,
state *oidc4cisrv.AuthorizeState,
_ ...func(insertOptions *oidc4cisrv.InsertOptions),
) error {
s.mu.RLock()
defer s.mu.RUnlock()
Expand Down
15 changes: 8 additions & 7 deletions pkg/restapi/v1/oidc4ci/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ func TestController_OidcAuthorize(t *testing.T) {

b, err := json.Marshal(&issuer.PrepareClaimDataAuthorizationResponse{
AuthorizationRequest: issuer.OAuthParameters{},
ProfileAuthStateTtl: 10,
})
require.NoError(t, err)

Expand All @@ -321,7 +322,7 @@ func TestController_OidcAuthorize(t *testing.T) {
}, nil
})

mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), *params.IssuerState, gomock.Any()).
mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), int32(10), *params.IssuerState, gomock.Any()).
Return(nil)
},
check: func(t *testing.T, rec *httptest.ResponseRecorder, err error) {
Expand Down Expand Up @@ -384,7 +385,7 @@ func TestController_OidcAuthorize(t *testing.T) {
}, nil
})

mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), gomock.Any(), gomock.Any()).
mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), int32(0), gomock.Any(), gomock.Any()).
Return(nil)
},
check: func(t *testing.T, rec *httptest.ResponseRecorder, err error) {
Expand Down Expand Up @@ -445,7 +446,7 @@ func TestController_OidcAuthorize(t *testing.T) {
}, nil
})

mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), *params.IssuerState, gomock.Any()).
mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), int32(0), *params.IssuerState, gomock.Any()).
Return(nil)
},
check: func(t *testing.T, rec *httptest.ResponseRecorder, err error) {
Expand Down Expand Up @@ -512,7 +513,7 @@ func TestController_OidcAuthorize(t *testing.T) {
}, nil
})

mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), "generated-op-state", gomock.Any()).
mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), int32(0), "generated-op-state", gomock.Any()).
Return(nil)
},
check: func(t *testing.T, rec *httptest.ResponseRecorder, err error) {
Expand Down Expand Up @@ -578,7 +579,7 @@ func TestController_OidcAuthorize(t *testing.T) {
},
)

mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), *params.IssuerState, gomock.Any()).Return(nil)
mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), int32(0), *params.IssuerState, gomock.Any()).Return(nil)
},
check: func(t *testing.T, rec *httptest.ResponseRecorder, err error) {
require.NoError(t, err)
Expand Down Expand Up @@ -638,7 +639,7 @@ func TestController_OidcAuthorize(t *testing.T) {
},
)

mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), *params.IssuerState, gomock.Any()).
mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), int32(0), *params.IssuerState, gomock.Any()).
Return(nil)
},
check: func(t *testing.T, rec *httptest.ResponseRecorder, err error) {
Expand Down Expand Up @@ -872,7 +873,7 @@ func TestController_OidcAuthorize(t *testing.T) {
}, nil
})

mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), *params.IssuerState, gomock.Any()).Return(
mockStateStore.EXPECT().SaveAuthorizeState(gomock.Any(), int32(0), *params.IssuerState, gomock.Any()).Return(
errors.New("save state error"))
},
check: func(t *testing.T, rec *httptest.ResponseRecorder, err error) {
Expand Down
4 changes: 0 additions & 4 deletions pkg/service/oidc4ci/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,6 @@ type PrepareCredentialResultData struct {
NotificationID *string
}

type InsertOptions struct {
TTL time.Duration
}

type AuthorizeState struct {
RedirectURI *url.URL `json:"redirect_uri"`
RespondMode string `json:"respond_mode"`
Expand Down
7 changes: 6 additions & 1 deletion pkg/service/oidc4ci/oidc4ci_acknowledgement.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ func (s *AckService) CreateAck(
return nil, nil //nolint:nilnil
}

id, err := s.cfg.AckStore.Create(ctx, ack)
profile, err := s.cfg.ProfileSvc.GetProfile(ack.ProfileID, ack.ProfileVersion)
if err != nil {
return nil, err
}

id, err := s.cfg.AckStore.Create(ctx, profile.DataConfig.OIDC4CIAckDataTTL, ack)
if err != nil {
return nil, err
}
Expand Down
55 changes: 49 additions & 6 deletions pkg/service/oidc4ci/oidc4ci_acknowledgement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,70 @@ func TestCreateAck(t *testing.T) {
})

t.Run("success", func(t *testing.T) {
profileSvc := NewMockProfileService(gomock.NewController(t))
profileSvc.EXPECT().GetProfile("some_issuer", "v1.0").
Return(&profile.Issuer{
DataConfig: profile.IssuerDataConfig{OIDC4CIAckDataTTL: 10},
}, nil)

store := NewMockAckStore(gomock.NewController(t))
srv := oidc4ci.NewAckService(&oidc4ci.AckServiceConfig{
AckStore: store,
ProfileSvc: profileSvc,
AckStore: store,
})

item := &oidc4ci.Ack{}
store.EXPECT().Create(gomock.Any(), item).Return("id", nil)
item := &oidc4ci.Ack{
ProfileID: "some_issuer",
ProfileVersion: "v1.0",
}

store.EXPECT().Create(gomock.Any(), int32(10), item).Return("id", nil)
id, err := srv.CreateAck(context.TODO(), item)

assert.NoError(t, err)
assert.Equal(t, "id", *id)
})

t.Run("profile srv err", func(t *testing.T) {
profileSvc := NewMockProfileService(gomock.NewController(t))
profileSvc.EXPECT().GetProfile("some_issuer", "v1.0").
Return(nil, errors.New("some error"))

store := NewMockAckStore(gomock.NewController(t))
srv := oidc4ci.NewAckService(&oidc4ci.AckServiceConfig{
AckStore: store,
ProfileSvc: profileSvc,
})

item := &oidc4ci.Ack{
ProfileID: "some_issuer",
ProfileVersion: "v1.0",
}

id, err := srv.CreateAck(context.TODO(), item)

assert.Nil(t, id)
assert.ErrorContains(t, err, "some error")
})

t.Run("store err", func(t *testing.T) {
profileSvc := NewMockProfileService(gomock.NewController(t))
profileSvc.EXPECT().GetProfile("some_issuer", "v1.0").
Return(&profile.Issuer{
DataConfig: profile.IssuerDataConfig{OIDC4CIAckDataTTL: 10},
}, nil)

store := NewMockAckStore(gomock.NewController(t))
srv := oidc4ci.NewAckService(&oidc4ci.AckServiceConfig{
AckStore: store,
ProfileSvc: profileSvc,
AckStore: store,
})

item := &oidc4ci.Ack{}
store.EXPECT().Create(gomock.Any(), item).Return("", errors.New("some err"))
item := &oidc4ci.Ack{
ProfileID: "some_issuer",
ProfileVersion: "v1.0",
}
store.EXPECT().Create(gomock.Any(), int32(10), item).Return("", errors.New("some err"))
id, err := srv.CreateAck(context.TODO(), item)

assert.Nil(t, id)
Expand Down
6 changes: 3 additions & 3 deletions pkg/service/oidc4ci/oidc4ci_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ type pinGenerator interface {
type transactionStore interface {
Create(
ctx context.Context,
profileTransactionDataTTL int32,
data *TransactionData,
params ...func(insertOptions *InsertOptions),
) (*Transaction, error)

Get(
Expand All @@ -75,7 +75,7 @@ type transactionStore interface {
}

type claimDataStore interface {
Create(ctx context.Context, data *ClaimData) (string, error)
Create(ctx context.Context, profileTTLSec int32, data *ClaimData) (string, error)
GetAndDelete(ctx context.Context, id string) (*ClaimData, error)
}

Expand Down Expand Up @@ -127,7 +127,7 @@ type trustRegistry interface {
}

type ackStore interface {
Create(ctx context.Context, data *Ack) (string, error)
Create(ctx context.Context, profileAckDataTTL int32, data *Ack) (string, error)
Get(ctx context.Context, id string) (*Ack, error)
Delete(ctx context.Context, id string) error
}
Expand Down
6 changes: 4 additions & 2 deletions pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func (s *Service) InitiateIssuance( // nolint:funlen,gocyclo,gocognit
txData.UserPin = s.pinGenerator.Generate(uuid.NewString())
}

tx, err := s.store.Create(ctx, txData)
tx, err := s.store.Create(ctx, profile.DataConfig.OIDC4CITransactionDataTTL, txData)
if err != nil {
return nil, resterr.NewSystemError(resterr.TransactionStoreComponent, "create",
fmt.Errorf("store tx: %w", err))
Expand Down Expand Up @@ -218,6 +218,7 @@ func (s *Service) newTxCredentialConf(
if isPreAuthFlow {
err = s.applyPreAuthFlowModifications(
ctx,
profile.DataConfig.ClaimDataTTL,
credentialConfiguration,
credentialTemplate,
txCredentialConfiguration,
Expand All @@ -232,6 +233,7 @@ func (s *Service) newTxCredentialConf(

func (s *Service) applyPreAuthFlowModifications(
ctx context.Context,
profileClaimDataTTLSec int32,
req InitiateIssuanceCredentialConfiguration,
credentialTemplate *profileapi.CredentialTemplate,
txCredentialConfiguration *TxCredentialConfiguration,
Expand Down Expand Up @@ -271,7 +273,7 @@ func (s *Service) applyPreAuthFlowModifications(
return errEncrypt
}

claimDataID, claimDataErr := s.claimDataStore.Create(ctx, claimDataEncrypted)
claimDataID, claimDataErr := s.claimDataStore.Create(ctx, profileClaimDataTTLSec, claimDataEncrypted)
if claimDataErr != nil {
return resterr.NewSystemError(resterr.ClaimDataStoreComponent, "create",
fmt.Errorf("store claim data: %w", claimDataErr))
Expand Down
Loading
Loading