Skip to content

Commit

Permalink
feat: add activator role
Browse files Browse the repository at this point in the history
Signed-off-by: Misha Sizov <[email protected]>
  • Loading branch information
mishasizov-SK committed Jan 13, 2025
1 parent e38c96b commit b41ee5a
Show file tree
Hide file tree
Showing 16 changed files with 354 additions and 3,411 deletions.
44 changes: 35 additions & 9 deletions component/credentialstatus/credentialstatus_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"fmt"
"io"
"net/http"
"slices"
"strconv"
"strings"

Expand Down Expand Up @@ -41,8 +42,10 @@ import (
)

const (
cslRequestTokenName = "csl"
credentialStatusEventSource = "source://vcs/status" //nolint:gosec
cslRequestTokenName = "csl"
credentialStatusEventSource = "source://vcs/status" //nolint:gosec
credentialStatusClientRoleRevoker = "revoker"
credentialStatusClientRoleActivator = "activator"
)

var logger = log.New("credentialstatus")
Expand Down Expand Up @@ -156,6 +159,15 @@ func (s *Service) UpdateVCStatus(ctx context.Context, params credentialstatus.Up
logfields.WithProfileVersion(params.ProfileVersion),
logfields.WithCredentialID(params.CredentialID))

statusValue, err := strconv.ParseBool(params.DesiredStatus)
if err != nil {
return fmt.Errorf("strconv.ParseBool failed: %w", err)
}

if err = s.checkOAuthClientRole(params.OAuthClientRoles, statusValue); err != nil {
return err
}

profile, err := s.profileService.GetProfile(params.ProfileID, params.ProfileVersion)
if err != nil {
return fmt.Errorf("get profile: %w", err)
Expand All @@ -172,11 +184,6 @@ func (s *Service) UpdateVCStatus(ctx context.Context, params credentialstatus.Up
return fmt.Errorf("vcStatusStore.Get failed: %w", err)
}

statusValue, err := strconv.ParseBool(params.DesiredStatus)
if err != nil {
return fmt.Errorf("strconv.ParseBool failed: %w", err)
}

err = s.updateVCStatus(ctx, typedID, profile.ID, profile.Version, profile.VCConfig.Status.Type, statusValue)
if err != nil {
return fmt.Errorf("updateVCStatus failed: %w", err)
Expand All @@ -187,6 +194,20 @@ func (s *Service) UpdateVCStatus(ctx context.Context, params credentialstatus.Up
return nil
}

func (s *Service) checkOAuthClientRole(oAuthClientRoles []string, statusValue bool) error {
requiredRole := credentialStatusClientRoleActivator

if statusValue {
requiredRole = credentialStatusClientRoleRevoker
}

if !slices.Contains(oAuthClientRoles, requiredRole) {
return resterr.ErrActionForbidden
}

return nil
}

// CreateStatusListEntry creates credentialstatus.StatusListEntry for profileID.
func (s *Service) CreateStatusListEntry(
ctx context.Context,
Expand Down Expand Up @@ -375,8 +396,13 @@ func (s *Service) sendHTTPRequest(req *http.Request, status int, token string) (
}

// updateVCStatus updates StatusListCredential associated with typedID.
func (s *Service) updateVCStatus(ctx context.Context, typedID *verifiable.TypedID, profileID, profileVersion string,
vcStatusType vc.StatusType, status bool) error {
func (s *Service) updateVCStatus(
ctx context.Context,
typedID *verifiable.TypedID,
profileID, profileVersion string,
vcStatusType vc.StatusType,
status bool,
) error {
vcStatusProcessor, err := statustype.GetVCStatusProcessor(vcStatusType)
if err != nil {
return fmt.Errorf("get VC status processor failed: %w", err)
Expand Down
157 changes: 101 additions & 56 deletions component/credentialstatus/credentialstatus_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable"
"github.com/trustbloc/vcs/pkg/event/spi"
profileapi "github.com/trustbloc/vcs/pkg/profile"
"github.com/trustbloc/vcs/pkg/restapi/resterr"
"github.com/trustbloc/vcs/pkg/service/credentialstatus"
"github.com/trustbloc/vcs/pkg/service/credentialstatus/cslservice"
"github.com/trustbloc/vcs/pkg/service/credentialstatus/eventhandler"
Expand Down Expand Up @@ -422,11 +423,12 @@ func TestCredentialStatusList_UpdateVCStatus(t *testing.T) {
require.NoError(t, err)

params := credentialstatus.UpdateVCStatusParams{
ProfileID: profileID,
ProfileVersion: profileVersion,
CredentialID: credID,
DesiredStatus: "true",
StatusType: profile.VCConfig.Status.Type,
OAuthClientRoles: []string{credentialStatusClientRoleRevoker},
ProfileID: profileID,
ProfileVersion: profileVersion,
CredentialID: credID,
DesiredStatus: "true",
StatusType: profile.VCConfig.Status.Type,
}

require.NoError(t, s.UpdateVCStatus(ctx, params))
Expand All @@ -447,109 +449,151 @@ func TestCredentialStatusList_UpdateVCStatus(t *testing.T) {
require.NoError(t, err)
require.True(t, bitSet)
})
t.Run("UpdateVCStatus profileService.GetProfile error", func(t *testing.T) {
t.Run("UpdateVCStatus ParseBool error", func(t *testing.T) {
loader := testutil.DocumentLoader(t)
vcStore := newMockVCStatusStore()
mockProfileSrv := NewMockProfileService(gomock.NewController(t))
mockProfileSrv.EXPECT().GetProfile(profileID, profileVersion).AnyTimes().Return(nil, errors.New("some error"))
mockKMSRegistry := NewMockKMSRegistry(gomock.NewController(t))

s, err := New(&Config{
DocumentLoader: loader,
CSLVCStore: newMockCSLVCStore(),

ProfileService: mockProfileSrv,
KMSRegistry: mockKMSRegistry,
VCStatusStore: vcStore,
Crypto: vccrypto.New(
&vdrmock.VDRegistry{ResolveValue: createDIDDoc("did:test:abc")}, loader),
})
require.NoError(t, err)

err = vcStore.Put(
context.Background(), profileID, profileVersion, credID, &verifiable.TypedID{
Type: string(vc.StatusList2021VCStatus)})
require.NoError(t, err)

params := credentialstatus.UpdateVCStatusParams{
ProfileID: profileID,
ProfileVersion: profileVersion,
CredentialID: credID,
DesiredStatus: "true",
DesiredStatus: "undefined",
StatusType: vc.StatusList2021VCStatus,
}

err = s.UpdateVCStatus(context.Background(), params)
require.Error(t, err)
require.ErrorContains(t, err, "get profile")
require.ErrorContains(t, err, "strconv.ParseBool failed")
})
t.Run("UpdateVCStatus invalid vc status type error", func(t *testing.T) {
t.Run("UpdateVCStatus action forbidden error: revoker tries to activate", func(t *testing.T) {
mockProfileSrv := NewMockProfileService(gomock.NewController(t))
mockProfileSrv.EXPECT().GetProfile(profileID, profileVersion).AnyTimes().Return(getTestProfile(), nil)
s, err := New(&Config{
ProfileService: mockProfileSrv,
})
require.NoError(t, err)

params := credentialstatus.UpdateVCStatusParams{
ProfileID: profileID,
ProfileVersion: profileVersion,
CredentialID: credID,
DesiredStatus: "true",
StatusType: vc.RevocationList2020VCStatus,
OAuthClientRoles: []string{credentialStatusClientRoleRevoker},
ProfileID: profileID,
ProfileVersion: profileVersion,
CredentialID: credID,
DesiredStatus: "false",
StatusType: vc.StatusList2021VCStatus,
}

err = s.UpdateVCStatus(context.Background(), params)
require.Error(t, err)
require.ErrorContains(t, err,
"vc status list version \"RevocationList2020Status\" is not supported by current profile")
require.ErrorIs(t, err, resterr.ErrActionForbidden)
})
t.Run("UpdateVCStatus store.Get error", func(t *testing.T) {
t.Run("UpdateVCStatus action forbidden error: activator tries to revoke", func(t *testing.T) {
mockProfileSrv := NewMockProfileService(gomock.NewController(t))
mockProfileSrv.EXPECT().GetProfile(profileID, profileVersion).AnyTimes().Return(getTestProfile(), nil)
mockKMSRegistry := NewMockKMSRegistry(gomock.NewController(t))
mockKMSRegistry.EXPECT().GetKeyManager(gomock.Any()).AnyTimes().Return(&vcskms.MockKMS{}, nil)
s, err := New(&Config{
ProfileService: mockProfileSrv,
})
require.NoError(t, err)

params := credentialstatus.UpdateVCStatusParams{
OAuthClientRoles: []string{credentialStatusClientRoleActivator},
ProfileID: profileID,
ProfileVersion: profileVersion,
CredentialID: credID,
DesiredStatus: "true",
StatusType: vc.StatusList2021VCStatus,
}

err = s.UpdateVCStatus(context.Background(), params)
require.Error(t, err)
require.ErrorIs(t, err, resterr.ErrActionForbidden)
})

t.Run("UpdateVCStatus profileService.GetProfile error", func(t *testing.T) {
mockProfileSrv := NewMockProfileService(gomock.NewController(t))
mockProfileSrv.EXPECT().GetProfile(profileID, profileVersion).AnyTimes().Return(nil, errors.New("some error"))
s, err := New(&Config{
ProfileService: mockProfileSrv,
KMSRegistry: mockKMSRegistry,
CSLVCStore: newMockCSLVCStore(),
VCStatusStore: newMockVCStatusStore(),
})
require.NoError(t, err)

params := credentialstatus.UpdateVCStatusParams{
ProfileID: profileID,
ProfileVersion: profileVersion,
CredentialID: credID,
DesiredStatus: "true",
StatusType: vc.StatusList2021VCStatus,
OAuthClientRoles: []string{credentialStatusClientRoleRevoker},
ProfileID: profileID,
ProfileVersion: profileVersion,
CredentialID: credID,
DesiredStatus: "true",
StatusType: vc.StatusList2021VCStatus,
}

err = s.UpdateVCStatus(context.Background(), params)
require.Error(t, err)
require.ErrorContains(t, err, "vcStatusStore.Get failed")
require.ErrorContains(t, err, "get profile")
})
t.Run("UpdateVCStatus ParseBool error", func(t *testing.T) {
loader := testutil.DocumentLoader(t)
vcStore := newMockVCStatusStore()
t.Run("UpdateVCStatus invalid vc status type error", func(t *testing.T) {
mockProfileSrv := NewMockProfileService(gomock.NewController(t))
mockProfileSrv.EXPECT().GetProfile(profileID, profileVersion).AnyTimes().Return(getTestProfile(), nil)
s, err := New(&Config{
ProfileService: mockProfileSrv,
})
require.NoError(t, err)

params := credentialstatus.UpdateVCStatusParams{
OAuthClientRoles: []string{credentialStatusClientRoleActivator},
ProfileID: profileID,
ProfileVersion: profileVersion,
CredentialID: credID,
DesiredStatus: "false",
StatusType: vc.RevocationList2020VCStatus,
}

err = s.UpdateVCStatus(context.Background(), params)
require.Error(t, err)
require.ErrorContains(t, err,
"vc status list version \"RevocationList2020Status\" is not supported by current profile")
})
t.Run("UpdateVCStatus store.Get error", func(t *testing.T) {
mockProfileSrv := NewMockProfileService(gomock.NewController(t))
mockProfileSrv.EXPECT().GetProfile(profileID, profileVersion).AnyTimes().Return(getTestProfile(), nil)
mockKMSRegistry := NewMockKMSRegistry(gomock.NewController(t))
mockKMSRegistry.EXPECT().GetKeyManager(gomock.Any()).AnyTimes().Return(&vcskms.MockKMS{}, nil)

s, err := New(&Config{
DocumentLoader: loader,
CSLVCStore: newMockCSLVCStore(),

ProfileService: mockProfileSrv,
KMSRegistry: mockKMSRegistry,
VCStatusStore: vcStore,
Crypto: vccrypto.New(
&vdrmock.VDRegistry{ResolveValue: createDIDDoc("did:test:abc")}, loader),
CSLVCStore: newMockCSLVCStore(),
VCStatusStore: newMockVCStatusStore(),
})
require.NoError(t, err)

err = vcStore.Put(
context.Background(), profileID, profileVersion, credID, &verifiable.TypedID{
Type: string(vc.StatusList2021VCStatus)})
require.NoError(t, err)

params := credentialstatus.UpdateVCStatusParams{
ProfileID: profileID,
ProfileVersion: profileVersion,
CredentialID: credID,
DesiredStatus: "undefined",
StatusType: vc.StatusList2021VCStatus,
OAuthClientRoles: []string{credentialStatusClientRoleRevoker},
ProfileID: profileID,
ProfileVersion: profileVersion,
CredentialID: credID,
DesiredStatus: "true",
StatusType: vc.StatusList2021VCStatus,
}

err = s.UpdateVCStatus(context.Background(), params)
require.Error(t, err)
require.ErrorContains(t, err, "strconv.ParseBool failed")
require.ErrorContains(t, err, "vcStatusStore.Get failed")
})
t.Run("UpdateVCStatus updateVCStatus error", func(t *testing.T) {
loader := testutil.DocumentLoader(t)
Expand All @@ -576,11 +620,12 @@ func TestCredentialStatusList_UpdateVCStatus(t *testing.T) {
require.NoError(t, err)

params := credentialstatus.UpdateVCStatusParams{
ProfileID: profileID,
ProfileVersion: profileVersion,
CredentialID: credID,
DesiredStatus: "true",
StatusType: vc.StatusList2021VCStatus,
OAuthClientRoles: []string{credentialStatusClientRoleRevoker},
ProfileID: profileID,
ProfileVersion: profileVersion,
CredentialID: credID,
DesiredStatus: "true",
StatusType: vc.StatusList2021VCStatus,
}

err = s.UpdateVCStatus(context.Background(), params)
Expand Down
Loading

0 comments on commit b41ee5a

Please sign in to comment.