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
2 changes: 1 addition & 1 deletion doc/docs.go

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions doc/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1961,8 +1961,9 @@ paths:
get:
consumes:
- application/json
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.
parameters:
- description: The issuer id
example: did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp
Expand Down
33 changes: 25 additions & 8 deletions pkg/server/router/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,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 @@ -413,30 +413,47 @@ 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.listCredentials(c)
return
}
if issuer != nil {
cr.getCredentialsByIssuer(c, *issuer)
cr.listCredentialsByIssuer(c, *issuer)
return
}
if subject != nil {
cr.getCredentialsBySubject(c, *subject)
cr.listCredentialsBySubject(c, *subject)
return
}
if schema != nil {
cr.getCredentialsBySchema(c, *schema)
cr.listCredentialsBySchema(c, *schema)
return
}

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

func (cr CredentialRouter) getCredentialsByIssuer(c *gin.Context, issuer string) {
func (cr CredentialRouter) listCredentials(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)
}

func (cr CredentialRouter) listCredentialsByIssuer(c *gin.Context, issuer string) {
gotCredentials, err := cr.service.ListCredentialsByIssuer(c, credential.ListCredentialByIssuerRequest{Issuer: issuer})
if err != nil {
errMsg := fmt.Sprintf("could not get credentials for issuer: %s", util.SanitizeLog(issuer))
Expand All @@ -449,7 +466,7 @@ func (cr CredentialRouter) getCredentialsByIssuer(c *gin.Context, issuer string)
return
}

func (cr CredentialRouter) getCredentialsBySubject(c *gin.Context, subject string) {
func (cr CredentialRouter) listCredentialsBySubject(c *gin.Context, subject string) {
gotCredentials, err := cr.service.ListCredentialsBySubject(c, credential.ListCredentialBySubjectRequest{Subject: subject})
if err != nil {
errMsg := fmt.Sprintf("could not get credentials for subject: %s", util.SanitizeLog(subject))
Expand All @@ -461,7 +478,7 @@ func (cr CredentialRouter) getCredentialsBySubject(c *gin.Context, subject strin
framework.Respond(c, resp, http.StatusOK)
}

func (cr CredentialRouter) getCredentialsBySchema(c *gin.Context, schema string) {
func (cr CredentialRouter) listCredentialsBySchema(c *gin.Context, schema string) {
gotCredentials, err := cr.service.ListCredentialsBySchema(c, credential.ListCredentialBySchemaRequest{Schema: schema})
if err != nil {
errMsg := fmt.Sprintf("could not get credentials for schema: %s", util.SanitizeLog(schema))
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 @@ -372,6 +372,63 @@ func TestCredentialAPI(t *testing.T) {
assert.Equal(tt, resp.Credential.CredentialSchema.ID, getCredsResp.Credentials[0].Credential.CredentialSchema.ID)
})

t.Run("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
28 changes: 26 additions & 2 deletions pkg/service/credential/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,34 @@ 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.ListCredentials(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))

gotCreds, err := s.storage.GetCredentialsByIssuer(ctx, request.Issuer)
gotCreds, err := s.storage.ListCredentialsByIssuer(ctx, request.Issuer)
if err != nil {
return nil, sdkutil.LoggingErrorMsgf(err, "could not list credential(s) for issuer: %s", request.Issuer)
}
Expand All @@ -356,7 +380,7 @@ func (s Service) ListCredentialsByIssuer(ctx context.Context, request ListCreden
func (s Service) ListCredentialsBySubject(ctx context.Context, request ListCredentialBySubjectRequest) (*ListCredentialsResponse, error) {
logrus.Debugf("listing credential(s) for subject: %s", util.SanitizeLog(request.Subject))

gotCreds, err := s.storage.GetCredentialsBySubject(ctx, request.Subject)
gotCreds, err := s.storage.ListCredentialsBySubject(ctx, request.Subject)
if err != nil {
return nil, sdkutil.LoggingErrorMsgf(err, "could not list credential(s) for subject: %s", request.Subject)
}
Expand Down
52 changes: 41 additions & 11 deletions pkg/service/credential/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,11 @@ func (cs *Storage) GetStatusListCredential(ctx context.Context, id string) (*Sto
}

if len(storedCreds) == 0 {
logrus.Warnf("no credentials able to be retrieved for id: %s", id)
logrus.Infof("no credentials able to be retrieved for id: %s", id)
}

if len(storedCreds) > 1 {
logrus.Warnf("there should only be status list credential per <issuer,schema,statuspurpose> tripple, bad state")
logrus.Error("there should only be status list credential per <issuer,schema,statuspurpose> tripple, bad state")
}

return &storedCreds[0], nil
Expand Down Expand Up @@ -346,14 +346,44 @@ func (cs *Storage) getCredential(ctx context.Context, id string, namespace strin
return &stored, nil
}

// ListCredentials 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) ListCredentials(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.Info("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.

// GetCredentialsByIssuer gets all credentials stored with a prefix key containing the issuer value
// ListCredentialsByIssuer gets all credentials stored with a prefix key containing the issuer value
// 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) GetCredentialsByIssuer(ctx context.Context, issuer string) ([]StoredCredential, error) {
func (cs *Storage) ListCredentialsByIssuer(ctx context.Context, issuer string) ([]StoredCredential, error) {
keys, err := cs.db.ReadAllKeys(ctx, credentialNamespace)
if err != nil {
return nil, sdkutil.LoggingErrorMsgf(err, "could not read credential storage while searching for creds for issuer: %s", issuer)
Expand Down Expand Up @@ -386,16 +416,16 @@ func (cs *Storage) GetCredentialsByIssuer(ctx context.Context, issuer string) ([
}

if len(storedCreds) == 0 {
logrus.Warnf("no credentials able to be retrieved for issuer: %s", issuerKeys)
logrus.Infof("no credentials able to be retrieved for issuer: %s", issuerKeys)
}

return storedCreds, nil
}

// GetCredentialsBySubject gets all credentials stored with a prefix key containing the subject value
// ListCredentialsBySubject gets all credentials stored with a prefix key containing the subject value
// 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) GetCredentialsBySubject(ctx context.Context, subject string) ([]StoredCredential, error) {
func (cs *Storage) ListCredentialsBySubject(ctx context.Context, subject string) ([]StoredCredential, error) {
keys, err := cs.db.ReadAllKeys(ctx, credentialNamespace)
if err != nil {
return nil, sdkutil.LoggingErrorMsgf(err, "could not read credential storage while searching for creds for subject: %s", subject)
Expand Down Expand Up @@ -429,7 +459,7 @@ func (cs *Storage) GetCredentialsBySubject(ctx context.Context, subject string)
}

if len(storedCreds) == 0 {
logrus.Warnf("no credentials able to be retrieved for subject: %s", subjectKeys)
logrus.Infof("no credentials able to be retrieved for subject: %s", subjectKeys)
}

return storedCreds, nil
Expand Down Expand Up @@ -473,7 +503,7 @@ func (cs *Storage) GetCredentialsBySchema(ctx context.Context, schema string) ([
}

if len(storedCreds) == 0 {
logrus.Warnf("no credentials able to be retrieved for schema: %s", schemaKeys)
logrus.Infof("no credentials able to be retrieved for schema: %s", schemaKeys)
}

return storedCreds, nil
Expand Down Expand Up @@ -523,7 +553,7 @@ func (cs *Storage) GetStatusListCredentialsByIssuerSchemaPurpose(ctx context.Con
}

if len(storedCreds) == 0 {
logrus.Warnf("no credentials able to be retrieved for issuer: %s", issuerSchemaKeys)
logrus.Infof("no credentials able to be retrieved for issuer: %s", issuerSchemaKeys)
}

return storedCreds, nil
Expand Down Expand Up @@ -564,7 +594,7 @@ func (cs *Storage) getCredentialsByIssuerAndSchema(ctx context.Context, issuer s
}

if len(storedCreds) == 0 {
logrus.Warnf("no credentials able to be retrieved for issuer: %s", issuerSchemaKeys)
logrus.Infof("no credentials able to be retrieved for issuer: %s", issuerSchemaKeys)
}

return storedCreds, nil
Expand Down