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: add activator role #1835

Merged
merged 1 commit into from
Jan 14, 2025
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
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
Loading