Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

List Credentials without params #489

Merged
merged 11 commits into from
Jun 6, 2023
22 changes: 20 additions & 2 deletions pkg/server/router/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ type ListCredentialsResponse struct {
// ListCredentials godoc
//
// @Summary List Credentials
// @Description Checks for the presence of a query parameter and calls the associated filtered get method. Only one parameter is allowed to be specified.
// @Description Checks for the presence of an optional query parameter and calls the associated filtered get method. Only one optional parameter is allowed to be specified.
// @Tags CredentialAPI
// @Accept json
// @Produce json
Expand All @@ -418,14 +418,18 @@ func (cr CredentialRouter) ListCredentials(c *gin.Context) {
schema := framework.GetQueryValue(c, SchemaParam)
subject := framework.GetQueryValue(c, SubjectParam)

errMsg := "must use one of the following query parameters: issuer, subject, schema"
errMsg := "must use only one of the following optional query parameters: issuer, subject, schema"

// check if there are multiple parameters set, which is not allowed
if (issuer != nil && subject != nil) || (issuer != nil && schema != nil) || (subject != nil && schema != nil) {
framework.LoggingRespondErrMsg(c, errMsg, http.StatusBadRequest)
return
}

if issuer == nil && schema == nil && subject == nil {
cr.getCredentials(c)
return
}
if issuer != nil {
cr.getCredentialsByIssuer(c, *issuer)
return
Expand All @@ -438,9 +442,23 @@ func (cr CredentialRouter) ListCredentials(c *gin.Context) {
cr.getCredentialsBySchema(c, *schema)
return
}

framework.LoggingRespondErrMsg(c, errMsg, http.StatusBadRequest)
}

func (cr CredentialRouter) getCredentials(c *gin.Context) {
gotCredentials, err := cr.service.ListCredentials(c)
if err != nil {
errMsg := fmt.Sprintf("could not get credentials")
framework.LoggingRespondErrWithMsg(c, err, errMsg, http.StatusInternalServerError)
return
}

resp := ListCredentialsResponse{Credentials: gotCredentials.Credentials}
framework.Respond(c, resp, http.StatusOK)
return
}

func (cr CredentialRouter) getCredentialsByIssuer(c *gin.Context, issuer string) {
gotCredentials, err := cr.service.ListCredentialsByIssuer(c, credential.ListCredentialByIssuerRequest{Issuer: issuer})
if err != nil {
Expand Down
57 changes: 57 additions & 0 deletions pkg/server/server_credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,63 @@ func TestCredentialAPI(t *testing.T) {
assert.Equal(tt, resp.Credential.CredentialSchema.ID, getCredsResp.Credentials[0].Credential.CredentialSchema.ID)
})

t.Run("Test Get Credential No Param", func(tt *testing.T) {
bolt := setupTestDB(tt)
require.NotEmpty(tt, bolt)

keyStoreService := testKeyStoreService(tt, bolt)
didService := testDIDService(tt, bolt, keyStoreService)
schemaService := testSchemaService(tt, bolt, keyStoreService, didService)
credRouter := testCredentialRouter(tt, bolt, keyStoreService, didService, schemaService)

w := httptest.NewRecorder()

issuerDID, err := didService.CreateDIDByMethod(context.Background(), did.CreateDIDRequest{
Method: didsdk.KeyMethod,
KeyType: crypto.Ed25519,
})
assert.NoError(tt, err)
assert.NotEmpty(tt, issuerDID)

createCredRequest := router.CreateCredentialRequest{
Issuer: issuerDID.DID.ID,
IssuerKID: issuerDID.DID.VerificationMethod[0].ID,
Subject: "did:abc:456",
Data: map[string]any{
"firstName": "Jack",
"lastName": "Dorsey",
},
Expiry: time.Now().Add(24 * time.Hour).Format(time.RFC3339),
}
requestValue := newRequestValue(tt, createCredRequest)
req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/credentials", requestValue)
c := newRequestContext(w, req)
credRouter.CreateCredential(c)
assert.True(tt, util.Is2xxResponse(w.Code))

var resp router.CreateCredentialResponse
err = json.NewDecoder(w.Body).Decode(&resp)
assert.NoError(tt, err)
assert.NotEmpty(tt, resp.CredentialJWT)

// reset the http recorder
w = httptest.NewRecorder()

// get credential by issuer id
req = httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/credentials", nil)
c = newRequestContext(w, req)
credRouter.ListCredentials(c)
assert.True(tt, util.Is2xxResponse(w.Code))

var getCredsResp router.ListCredentialsResponse
err = json.NewDecoder(w.Body).Decode(&getCredsResp)
assert.NoError(tt, err)
assert.NotEmpty(tt, getCredsResp)

assert.Len(tt, getCredsResp.Credentials, 1)
assert.Equal(tt, resp.Credential.ID, getCredsResp.Credentials[0].ID)
})

t.Run("Test Get Credential By Issuer", func(tt *testing.T) {
bolt := setupTestDB(tt)
require.NotEmpty(tt, bolt)
Expand Down
24 changes: 24 additions & 0 deletions pkg/service/credential/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,30 @@ func (s Service) GetCredential(ctx context.Context, request GetCredentialRequest
return &response, nil
}

func (s Service) ListCredentials(ctx context.Context) (*ListCredentialsResponse, error) {
logrus.Debugf("listing credential(s) ")

gotCreds, err := s.storage.GetCredentials(ctx)
if err != nil {
return nil, sdkutil.LoggingErrorMsgf(err, "could not list credential(s)")
}

creds := make([]credint.Container, 0, len(gotCreds))
for _, cred := range gotCreds {
container := credint.Container{
ID: cred.CredentialID,
Credential: cred.Credential,
CredentialJWT: cred.CredentialJWT,
Revoked: cred.Revoked,
Suspended: cred.Suspended,
}
creds = append(creds, container)
}

response := ListCredentialsResponse{Credentials: creds}
return &response, nil
}

func (s Service) ListCredentialsByIssuer(ctx context.Context, request ListCredentialByIssuerRequest) (*ListCredentialsResponse, error) {
logrus.Debugf("listing credential(s) for issuer: %s", util.SanitizeLog(request.Issuer))

Expand Down
30 changes: 30 additions & 0 deletions pkg/service/credential/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,36 @@ func (cs *Storage) getCredential(ctx context.Context, id string, namespace strin
return &stored, nil
}

// GetCredentials gets all credentials stored with a prefix key
// The method is greedy, meaning if multiple values are found...and some fail during processing, we will
// return only the successful values and log an error for the failures.
func (cs *Storage) GetCredentials(ctx context.Context) ([]StoredCredential, error) {
keys, err := cs.db.ReadAllKeys(ctx, credentialNamespace)
if err != nil {
return nil, sdkutil.LoggingErrorMsgf(err, "could not read credential storage")
}

var storedCreds []StoredCredential
for _, key := range keys {
credBytes, err := cs.db.Read(ctx, credentialNamespace, key)
if err != nil {
logrus.WithError(err).Errorf("could not read credential with key: %s", key)
} else {
var cred StoredCredential
if err = json.Unmarshal(credBytes, &cred); err != nil {
logrus.WithError(err).Errorf("unmarshalling credential with key: %s", key)
}
storedCreds = append(storedCreds, cred)
}
}

if len(storedCreds) == 0 {
logrus.Warnf("no credentials able to be retrieved")
}

return storedCreds, nil
}

// Note: this is a lazy implementation. Optimizations are to be had by adjusting prefix
// queries, and nested buckets. It is not intended that bolt is run in production, or at any scale,
// so this is not much of a concern.
Expand Down