Skip to content
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ clean:
#==============================================================================

install-mockery:
go install github.com/vektra/mockery/v2@latest
go install github.com/vektra/mockery/v2@v2.53.7

install-gotestsum:
go install gotest.tools/gotestsum@latest
Expand Down
16 changes: 16 additions & 0 deletions agent/common/identityhub/identityhub.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"path"

"github.com/eclipse-cfm/cfm/agent/common"
"github.com/eclipse-cfm/cfm/common/token"
Expand Down Expand Up @@ -152,6 +154,20 @@ func (a HttpIdentityAPIClient) RequestCredentials(ctx context.Context, participa
return location, nil
}

// HolderPIDFromLocation extracts the holderPid that IdentityHub assigned to a credential request from the Location
// header returned on its creation, which ends with ".../credentials/request/{holderPid}".
func HolderPIDFromLocation(location string) (string, error) {
u, err := url.Parse(location)
if err != nil {
return "", fmt.Errorf("invalid credential request location '%s': %w", location, err)
}
holderPID := path.Base(u.Path)
if holderPID == "" || holderPID == "." || holderPID == "/" || holderPID == "request" {
return "", fmt.Errorf("credential request location '%s' does not contain a holderPid", location)
}
return holderPID, nil
}

func (a HttpIdentityAPIClient) GetCredentialRequestState(ctx context.Context, participantContextID string, credentialRequestID string) (string, error) {
accessToken, err := a.TokenProvider.GetToken(ctx, ScopeCredentialsRead, participantContextID)
if err != nil {
Expand Down
17 changes: 17 additions & 0 deletions agent/common/identityhub/identityhub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,20 @@ func TestHttpIdentityAPIClient_DeleteParticipantContext_NotFound(t *testing.T) {
err := client.DeleteParticipantContext(t.Context(), "test")
require.ErrorContains(t, err, "participant context test not found")
}

func TestHolderPIDFromLocation(t *testing.T) {
holderPID, err := HolderPIDFromLocation("v1/participants/participant-1/credentials/request/holder-123")
require.NoError(t, err)
require.Equal(t, "holder-123", holderPID)

holderPID, err = HolderPIDFromLocation("http://identityhub/api/identity/v1/participants/participant-1/credentials/request/holder-123")
require.NoError(t, err)
require.Equal(t, "holder-123", holderPID)
}

func TestHolderPIDFromLocation_Invalid(t *testing.T) {
for _, location := range []string{"", "/", "v1/participants/participant-1/credentials/request", "http://[::1"} {
_, err := HolderPIDFromLocation(location)
require.Error(t, err, location)
}
}
1 change: 0 additions & 1 deletion agent/common/identityhub/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,5 @@ type CredentialType struct {

type CredentialRequest struct {
IssuerDID string `json:"issuerDid" validate:"required"`
HolderPID string `json:"holderPid" validate:"required"`
Credentials []CredentialType `json:"credentials" validate:"required"`
}
10 changes: 7 additions & 3 deletions agent/common/identityhub/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ func TestParticipantManifest_Serialization(t *testing.T) {
func TestCredentialRequest_Serialization(t *testing.T) {
cr := CredentialRequest{
IssuerDID: "did:example:issuer",
HolderPID: "holder-123",
Credentials: []CredentialType{
{
Format: "ldp",
Expand All @@ -73,12 +72,17 @@ func TestCredentialRequest_Serialization(t *testing.T) {
}

func TestCredentialRequest_Deserialization(t *testing.T) {
jsonString := `{"issuerDid": "did:example:issuer", "holderPid": "holder-123", "credentials": [{"format": "ldp", "type": "VerifiableCredential", "id": "cred-1"}]}`
jsonString := `{"issuerDid": "did:example:issuer", "credentials": [{"format": "ldp", "type": "VerifiableCredential", "id": "cred-1"}]}`

request := CredentialRequest{}
err := json.Unmarshal([]byte(jsonString), &request)
require.NoError(t, err)
require.Equal(t, "did:example:issuer", request.IssuerDID)
require.Equal(t, "holder-123", request.HolderPID)
require.Len(t, request.Credentials, 1)
}

func TestCredentialRequest_Serialization_OmitsHolderPid(t *testing.T) {
data, err := json.Marshal(CredentialRequest{IssuerDID: "did:example:issuer"})
require.NoError(t, err)
require.NotContains(t, string(data), "holderPid")
}
9 changes: 5 additions & 4 deletions agent/orchestration/onboarding/activity/activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"github.com/eclipse-cfm/cfm/common/model"
"github.com/eclipse-cfm/cfm/common/system"
"github.com/eclipse-cfm/cfm/pmanager/api"
"github.com/google/uuid"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
Expand Down Expand Up @@ -167,17 +166,19 @@ func (p OnboardingActivityProcessor) processNewRequest(ctx api.ActivityContext,
return api.ActivityResult{Result: api.ActivityResultFatalError, Error: fmt.Errorf("multiple issuers not supported yet")}
}

holderPid := uuid.New().String()
cr := identityhub.CredentialRequest{
IssuerDID: issuer,
HolderPID: holderPid,
Credentials: credentials,
}
// make credential request
// make credential request, IdentityHub assigns the holderPid and returns it at the end of the location
location, err := p.IdentityApiClient.RequestCredentials(ctx.Context(), credentialRequest.ParticipantContextID, cr)
if err != nil {
return api.ActivityResult{Result: api.ActivityResultFatalError, Error: fmt.Errorf("error requesting credentials: %w", err)}
}
holderPid, err := identityhub.HolderPIDFromLocation(location)
if err != nil {
return api.ActivityResult{Result: api.ActivityResultFatalError, Error: fmt.Errorf("error requesting credentials: %w", err)}
}
p.Monitor.Infof("Credentials request for participant '%s' and credentials '%s' submitted successfully, credential is at %s", credentialRequest.ParticipantContextID, credentialsTypes, location)
ctx.SetValue("participantContextId", credentialRequest.ParticipantContextID)
ctx.SetValue("holderPid", holderPid)
Expand Down
2 changes: 1 addition & 1 deletion agent/orchestration/onboarding/activity/activity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestOnboardingActivityProcessor_ProcessDeploy_WhenNewRequest(t *testing.T)

assert.Equal(t, ih.expectedURL, activityContext.Values()["credentialRequest"])
assert.Equal(t, "test-participant", activityContext.Values()["participantContextId"])
assert.Contains(t, activityContext.Values(), "holderPid")
assert.Equal(t, "123", activityContext.Values()["holderPid"])
}

func TestOnboardingActivityProcessor_ProcessDeploy_WhenNewRequestError(t *testing.T) {
Expand Down
Loading