From 34d96026561af950a07b7c32d078583a93f7ab67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Gim=C3=A9nez?= Date: Thu, 18 Jul 2024 10:04:03 +0200 Subject: [PATCH] chore: Organize code in shorter files. (#691) * chore: Organize code in shorter files. * feat: Change link to documentation in a log output --- internal/api/agent.go | 43 + internal/api/credentials.go | 522 ++++++++++++ .../{server_test.go => credentials_test.go} | 304 ------- internal/api/identity.go | 217 +++++ internal/api/identity_test.go | 330 ++++++++ internal/api/qrcode.go | 21 + internal/api/server.go | 766 ------------------ 7 files changed, 1133 insertions(+), 1070 deletions(-) create mode 100644 internal/api/agent.go create mode 100644 internal/api/credentials.go rename internal/api/{server_test.go => credentials_test.go} (81%) create mode 100644 internal/api/identity.go create mode 100644 internal/api/identity_test.go create mode 100644 internal/api/qrcode.go diff --git a/internal/api/agent.go b/internal/api/agent.go new file mode 100644 index 000000000..51417e787 --- /dev/null +++ b/internal/api/agent.go @@ -0,0 +1,43 @@ +package api + +import ( + "context" + + "github.com/polygonid/sh-id-platform/internal/core/ports" + "github.com/polygonid/sh-id-platform/internal/log" +) + +// Agent is the controller to fetch credentials from mobile +func (s *Server) Agent(ctx context.Context, request AgentRequestObject) (AgentResponseObject, error) { + if request.Body == nil || *request.Body == "" { + log.Debug(ctx, "agent empty request") + return Agent400JSONResponse{N400JSONResponse{"cannot proceed with an empty request"}}, nil + } + + basicMessage, mediatype, err := s.packageManager.Unpack([]byte(*request.Body)) + if err != nil { + log.Debug(ctx, "agent bad request", "err", err, "body", *request.Body) + return Agent400JSONResponse{N400JSONResponse{"cannot proceed with the given request"}}, nil + } + + req, err := ports.NewAgentRequest(basicMessage) + if err != nil { + log.Error(ctx, "agent parsing request", "err", err) + return Agent400JSONResponse{N400JSONResponse{err.Error()}}, nil + } + + agent, err := s.claimService.Agent(ctx, req, mediatype) + if err != nil { + log.Error(ctx, "agent error", "err", err) + return Agent400JSONResponse{N400JSONResponse{err.Error()}}, nil + } + return Agent200JSONResponse{ + Body: agent.Body, + From: agent.From, + Id: agent.ID, + ThreadID: agent.ThreadID, + To: agent.To, + Typ: string(agent.Typ), + Type: string(agent.Type), + }, nil +} diff --git a/internal/api/credentials.go b/internal/api/credentials.go new file mode 100644 index 000000000..b1de8064c --- /dev/null +++ b/internal/api/credentials.go @@ -0,0 +1,522 @@ +package api + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/google/uuid" + "github.com/iden3/go-iden3-core/v2/w3c" + "github.com/iden3/go-schema-processor/v2/verifiable" + "github.com/iden3/iden3comm/v2/packers" + "github.com/iden3/iden3comm/v2/protocol" + + "github.com/polygonid/sh-id-platform/internal/common" + "github.com/polygonid/sh-id-platform/internal/core/domain" + "github.com/polygonid/sh-id-platform/internal/core/ports" + "github.com/polygonid/sh-id-platform/internal/core/services" + "github.com/polygonid/sh-id-platform/internal/log" + "github.com/polygonid/sh-id-platform/internal/repositories" + "github.com/polygonid/sh-id-platform/pkg/schema" +) + +// CreateCredential is the creation credential controller. It creates a credential and returns the id +func (s *Server) CreateCredential(ctx context.Context, request CreateCredentialRequestObject) (CreateCredentialResponseObject, error) { + ret, err := s.CreateClaim(ctx, CreateClaimRequestObject(request)) + if err != nil { + return CreateCredential500JSONResponse{N500JSONResponse{Message: err.Error()}}, err + } + switch ret := ret.(type) { + case CreateClaim201JSONResponse: + return CreateCredential201JSONResponse(ret), nil + case CreateClaim400JSONResponse: + return CreateCredential400JSONResponse{N400JSONResponse{Message: ret.Message}}, nil + case CreateClaim422JSONResponse: + return CreateCredential422JSONResponse{N422JSONResponse{Message: ret.Message}}, nil + case CreateClaim500JSONResponse: + return CreateCredential500JSONResponse{N500JSONResponse{Message: ret.Message}}, nil + default: + log.Error(ctx, "unexpected return type", "type", fmt.Sprintf("%T", ret)) + return CreateCredential500JSONResponse{N500JSONResponse{Message: fmt.Sprintf("unexpected return type: %T", ret)}}, nil + } +} + +// CreateClaim is claim creation controller +// deprecated - Use CreateCredential instead +func (s *Server) CreateClaim(ctx context.Context, request CreateClaimRequestObject) (CreateClaimResponseObject, error) { + did, err := w3c.ParseDID(request.Identifier) + if err != nil { + return CreateClaim400JSONResponse{N400JSONResponse{Message: err.Error()}}, nil + } + var expiration *time.Time + if request.Body.Expiration != nil { + expiration = common.ToPointer(time.Unix(*request.Body.Expiration, 0)) + } + + claimRequestProofs := ports.ClaimRequestProofs{} + if request.Body.Proofs == nil { + claimRequestProofs.BJJSignatureProof2021 = true + claimRequestProofs.Iden3SparseMerkleTreeProof = true + } else { + for _, proof := range *request.Body.Proofs { + if string(proof) == string(verifiable.BJJSignatureProofType) { + claimRequestProofs.BJJSignatureProof2021 = true + continue + } + if string(proof) == string(verifiable.Iden3SparseMerkleTreeProofType) { + claimRequestProofs.Iden3SparseMerkleTreeProof = true + continue + } + return CreateClaim400JSONResponse{N400JSONResponse{Message: fmt.Sprintf("unsupported proof type: %s", proof)}}, nil + } + } + + resolverPrefix, err := common.ResolverPrefix(did) + if err != nil { + return CreateClaim400JSONResponse{N400JSONResponse{Message: "error parsing did"}}, nil + } + + rhsSettings, err := s.networkResolver.GetRhsSettings(ctx, resolverPrefix) + if err != nil { + return CreateClaim400JSONResponse{N400JSONResponse{Message: "error getting reverse hash service settings"}}, nil + } + + req := ports.NewCreateClaimRequest(did, request.Body.ClaimID, request.Body.CredentialSchema, request.Body.CredentialSubject, expiration, request.Body.Type, request.Body.Version, request.Body.SubjectPosition, request.Body.MerklizedRootPosition, claimRequestProofs, nil, false, rhsSettings.CredentialStatusType, toVerifiableRefreshService(request.Body.RefreshService), request.Body.RevNonce, + toVerifiableDisplayMethod(request.Body.DisplayMethod)) + + resp, err := s.claimService.Save(ctx, req) + if err != nil { + if errors.Is(err, services.ErrLoadingSchema) { + return CreateClaim422JSONResponse{N422JSONResponse{Message: err.Error()}}, nil + } + errs := []error{ + services.ErrJSONLdContext, + services.ErrProcessSchema, + services.ErrMalformedURL, + services.ErrParseClaim, + services.ErrInvalidCredentialSubject, + services.ErrAssigningMTPProof, + services.ErrUnsupportedRefreshServiceType, + services.ErrRefreshServiceLacksExpirationTime, + services.ErrRefreshServiceLacksURL, + services.ErrDisplayMethodLacksURL, + services.ErrUnsupportedDisplayMethodType, + } + for _, e := range errs { + if errors.Is(err, e) { + return CreateClaim400JSONResponse{N400JSONResponse{Message: err.Error()}}, nil + } + } + return CreateClaim500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil + } + return CreateClaim201JSONResponse{Id: resp.ID.String()}, nil +} + +// RevokeCredential is the revocation claim controller +func (s *Server) RevokeCredential(ctx context.Context, request RevokeCredentialRequestObject) (RevokeCredentialResponseObject, error) { + did, err := w3c.ParseDID(request.Identifier) + if err != nil { + log.Warn(ctx, "revoke credential: invalid did", "err", err, "req", request) + return RevokeCredential400JSONResponse{N400JSONResponse{err.Error()}}, nil + } + + if err := s.claimService.Revoke(ctx, *did, uint64(request.Nonce), ""); err != nil { + if errors.Is(err, repositories.ErrClaimDoesNotExist) { + return RevokeCredential404JSONResponse{N404JSONResponse{ + Message: "the credential does not exist", + }}, nil + } + + return RevokeCredential500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil + } + return RevokeCredential202JSONResponse{ + Message: "credential revocation request sent", + }, nil +} + +// RevokeClaim is the revocation claim controller. +// Deprecated - use RevokeCredential instead +func (s *Server) RevokeClaim(ctx context.Context, request RevokeClaimRequestObject) (RevokeClaimResponseObject, error) { + did, err := w3c.ParseDID(request.Identifier) + if err != nil { + log.Warn(ctx, "revoke claim invalid did", "err", err, "req", request) + return RevokeClaim400JSONResponse{N400JSONResponse{err.Error()}}, nil + } + + if err := s.claimService.Revoke(ctx, *did, uint64(request.Nonce), ""); err != nil { + if errors.Is(err, repositories.ErrClaimDoesNotExist) { + return RevokeClaim404JSONResponse{N404JSONResponse{ + Message: "the claim does not exist", + }}, nil + } + + return RevokeClaim500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil + } + return RevokeClaim202JSONResponse{ + Message: "claim revocation request sent", + }, nil +} + +// GetRevocationStatusV2 is the controller to get revocation status +func (s *Server) GetRevocationStatusV2(ctx context.Context, request GetRevocationStatusV2RequestObject) (GetRevocationStatusV2ResponseObject, error) { + resp, err := s.GetRevocationStatus(ctx, GetRevocationStatusRequestObject(request)) + if err != nil { + return GetRevocationStatusV2500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil + } + switch ret := resp.(type) { + case GetRevocationStatus200JSONResponse: + return GetRevocationStatusV2200JSONResponse(ret), nil + case GetRevocationStatus500JSONResponse: + return GetRevocationStatusV2500JSONResponse{N500JSONResponse{Message: ret.Message}}, nil + default: + log.Error(ctx, "unexpected return type", "type", fmt.Sprintf("%T", ret)) + return GetRevocationStatusV2500JSONResponse{N500JSONResponse{Message: fmt.Sprintf("unexpected return type: %T", ret)}}, nil + } +} + +// GetRevocationStatus is the controller to get revocation status +// Deprecated - use GetRevocationStatusV2 instead +func (s *Server) GetRevocationStatus(ctx context.Context, request GetRevocationStatusRequestObject) (GetRevocationStatusResponseObject, error) { + issuerDID, err := w3c.ParseDID(request.Identifier) + if err != nil { + return GetRevocationStatus500JSONResponse{N500JSONResponse{ + Message: err.Error(), + }}, nil + } + + rs, err := s.claimService.GetRevocationStatus(ctx, *issuerDID, uint64(request.Nonce)) + if err != nil { + return GetRevocationStatus500JSONResponse{N500JSONResponse{ + Message: err.Error(), + }}, nil + } + + response := GetRevocationStatus200JSONResponse{} + response.Issuer.State = rs.Issuer.State + response.Issuer.RevocationTreeRoot = rs.Issuer.RevocationTreeRoot + response.Issuer.RootOfRoots = rs.Issuer.RootOfRoots + response.Issuer.ClaimsTreeRoot = rs.Issuer.ClaimsTreeRoot + response.Mtp.Existence = rs.MTP.Existence + + if rs.MTP.NodeAux != nil { + key := rs.MTP.NodeAux.Key + decodedKey := key.BigInt().String() + value := rs.MTP.NodeAux.Value + decodedValue := value.BigInt().String() + response.Mtp.NodeAux = &struct { + Key *string `json:"key,omitempty"` + Value *string `json:"value,omitempty"` + }{ + Key: &decodedKey, + Value: &decodedValue, + } + } + + response.Mtp.Existence = rs.MTP.Existence + siblings := make([]string, 0) + for _, s := range rs.MTP.AllSiblings() { + siblings = append(siblings, s.BigInt().String()) + } + response.Mtp.Siblings = &siblings + + return response, err +} + +// GetCredential is the controller to get a credential +func (s *Server) GetCredential(ctx context.Context, request GetCredentialRequestObject) (GetCredentialResponseObject, error) { + resp, err := s.GetClaim(ctx, GetClaimRequestObject(request)) + if err != nil { + return GetCredential500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil + } + switch ret := resp.(type) { + case GetClaim200JSONResponse: + return GetCredential200JSONResponse{ + Context: ret.Context, + CredentialSchema: CredentialSchema{ + Id: ret.CredentialSchema.Id, + Type: ret.CredentialSchema.Type, + }, + CredentialStatus: ret.CredentialStatus, + CredentialSubject: ret.CredentialSubject, + DisplayMethod: ret.DisplayMethod, + ExpirationDate: ret.ExpirationDate, + Id: ret.Id, + IssuanceDate: ret.IssuanceDate, + Issuer: ret.Issuer, + Proof: ret.Proof, + RefreshService: ret.RefreshService, + Type: ret.Type, + }, nil + case GetClaim400JSONResponse: + return GetCredential400JSONResponse{N400JSONResponse{Message: ret.Message}}, nil + case GetClaim404JSONResponse: + return GetCredential404JSONResponse{N404JSONResponse{Message: ret.Message}}, nil + case GetClaim500JSONResponse: + return GetCredential500JSONResponse{N500JSONResponse{Message: ret.Message}}, nil + default: + log.Error(ctx, "unexpected return type", "type", fmt.Sprintf("%T", ret)) + return GetCredential500JSONResponse{N500JSONResponse{Message: fmt.Sprintf("unexpected return type: %T", ret)}}, nil + } +} + +// GetClaim is the controller to get a claim. +// Deprecated - use GetCredential instead +func (s *Server) GetClaim(ctx context.Context, request GetClaimRequestObject) (GetClaimResponseObject, error) { + if request.Identifier == "" { + return GetClaim400JSONResponse{N400JSONResponse{"invalid did, cannot be empty"}}, nil + } + + did, err := w3c.ParseDID(request.Identifier) + if err != nil { + return GetClaim400JSONResponse{N400JSONResponse{"invalid did"}}, nil + } + + if request.Id == "" { + return GetClaim400JSONResponse{N400JSONResponse{"cannot proceed with an empty claim id"}}, nil + } + + clID, err := uuid.Parse(request.Id) + if err != nil { + return GetClaim400JSONResponse{N400JSONResponse{"invalid claim id"}}, nil + } + + claim, err := s.claimService.GetByID(ctx, did, clID) + if err != nil { + if errors.Is(err, services.ErrClaimNotFound) { + return GetClaim404JSONResponse{N404JSONResponse{err.Error()}}, nil + } + return GetClaim500JSONResponse{N500JSONResponse{err.Error()}}, nil + } + + w3c, err := schema.FromClaimModelToW3CCredential(*claim) + if err != nil { + return GetClaim500JSONResponse{N500JSONResponse{"invalid claim format"}}, nil + } + + return GetClaim200JSONResponse(toGetClaim200Response(w3c)), nil +} + +// GetCredentials is the controller to get multiple credentials of a determined identity +func (s *Server) GetCredentials(ctx context.Context, request GetCredentialsRequestObject) (GetCredentialsResponseObject, error) { + resp, err := s.GetClaims(ctx, GetClaimsRequestObject{ + Identifier: request.Identifier, + Params: GetClaimsParams(request.Params), + }) + if err != nil { + return GetCredentials500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil + } + switch ret := resp.(type) { + case GetClaims200JSONResponse: + return GetCredentials200JSONResponse(ret), nil + case GetClaims400JSONResponse: + return GetCredentials400JSONResponse{N400JSONResponse{Message: ret.Message}}, nil + case GetClaims500JSONResponse: + return GetCredentials500JSONResponse{N500JSONResponse{Message: ret.Message}}, nil + default: + log.Error(ctx, "unexpected return type", "type", fmt.Sprintf("%T", ret)) + return GetCredentials500JSONResponse{N500JSONResponse{Message: fmt.Sprintf("unexpected return type: %T", ret)}}, nil + } +} + +// GetClaims is the controller to get multiple claims of a determined identity +// deprecated: use GetCredentials instead +func (s *Server) GetClaims(ctx context.Context, request GetClaimsRequestObject) (GetClaimsResponseObject, error) { + if request.Identifier == "" { + return GetClaims400JSONResponse{N400JSONResponse{"invalid did, cannot be empty"}}, nil + } + + did, err := w3c.ParseDID(request.Identifier) + if err != nil { + return GetClaims400JSONResponse{N400JSONResponse{"invalid did"}}, nil + } + + filter, err := ports.NewClaimsFilter( + request.Params.SchemaHash, + request.Params.SchemaType, + request.Params.Subject, + request.Params.QueryField, + request.Params.QueryValue, + request.Params.Self, + request.Params.Revoked) + if err != nil { + return GetClaims400JSONResponse{N400JSONResponse{err.Error()}}, nil + } + + claims, _, err := s.claimService.GetAll(ctx, *did, filter) + if err != nil && !errors.Is(err, services.ErrClaimNotFound) { + return GetClaims500JSONResponse{N500JSONResponse{"there was an internal error trying to retrieve claims for the requested identifier"}}, nil + } + + w3Claims, err := schema.FromClaimsModelToW3CCredential(claims) + if err != nil { + return GetClaims500JSONResponse{N500JSONResponse{"there was an internal error parsing the claims"}}, nil + } + + return GetClaims200JSONResponse(toGetClaims200Response(w3Claims)), nil +} + +// GetCredentialQrCode returns a GetClaimQrCodeResponseObject that can be used with any QR generator to create a QR and +// scan it with privado.id wallet to accept the claim +func (s *Server) GetCredentialQrCode(ctx context.Context, request GetCredentialQrCodeRequestObject) (GetCredentialQrCodeResponseObject, error) { + resp, err := s.GetClaimQrCode(ctx, GetClaimQrCodeRequestObject(request)) + if err != nil { + return GetCredentialQrCode500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil + } + switch ret := resp.(type) { + case GetClaimQrCode200JSONResponse: + return GetCredentialQrCode200JSONResponse(ret), nil + case GetClaimQrCode400JSONResponse: + return GetCredentialQrCode400JSONResponse{N400JSONResponse{Message: ret.Message}}, nil + case GetClaimQrCode404JSONResponse: + return GetCredentialQrCode404JSONResponse{N404JSONResponse{Message: ret.Message}}, nil + case GetClaimQrCode409JSONResponse: + return GetCredentialQrCode409JSONResponse{N409JSONResponse{Message: ret.Message}}, nil + case GetClaimQrCode500JSONResponse: + return GetCredentialQrCode500JSONResponse{N500JSONResponse{Message: ret.Message}}, nil + default: + log.Error(ctx, "unexpected return type", "type", fmt.Sprintf("%T", ret)) + return GetCredentialQrCode500JSONResponse{N500JSONResponse{Message: fmt.Sprintf("unexpected return type: %T", ret)}}, nil + } +} + +// GetClaimQrCode returns a GetClaimQrCodeResponseObject that can be used with any QR generator to create a QR and +// scan it with polygon wallet to accept the claim +// deprecated - use GetCredentialQrCode instead +func (s *Server) GetClaimQrCode(ctx context.Context, request GetClaimQrCodeRequestObject) (GetClaimQrCodeResponseObject, error) { + if request.Identifier == "" { + return GetClaimQrCode400JSONResponse{N400JSONResponse{"invalid did, cannot be empty"}}, nil + } + + did, err := w3c.ParseDID(request.Identifier) + if err != nil { + return GetClaimQrCode400JSONResponse{N400JSONResponse{"invalid did"}}, nil + } + + if request.Id == "" { + return GetClaimQrCode400JSONResponse{N400JSONResponse{"cannot proceed with an empty claim id"}}, nil + } + + claimID, err := uuid.Parse(request.Id) + if err != nil { + return GetClaimQrCode400JSONResponse{N400JSONResponse{"invalid claim id"}}, nil + } + + claim, err := s.claimService.GetByID(ctx, did, claimID) + if err != nil { + if errors.Is(err, services.ErrClaimNotFound) { + return GetClaimQrCode404JSONResponse{N404JSONResponse{err.Error()}}, nil + } + return GetClaimQrCode500JSONResponse{N500JSONResponse{err.Error()}}, nil + } + + if !claim.ValidProof() { + return GetClaimQrCode409JSONResponse{N409JSONResponse{"State must be published before fetching MTP type credential"}}, nil + } + + return toGetClaimQrCode200JSONResponse(claim, s.cfg.ServerUrl), nil +} + +func toVerifiableRefreshService(s *RefreshService) *verifiable.RefreshService { + if s == nil { + return nil + } + return &verifiable.RefreshService{ + ID: s.Id, + Type: verifiable.RefreshServiceType(s.Type), + } +} + +func toVerifiableDisplayMethod(s *DisplayMethod) *verifiable.DisplayMethod { + if s == nil { + return nil + } + return &verifiable.DisplayMethod{ + ID: s.Id, + Type: verifiable.DisplayMethodType(s.Type), + } +} + +func toGetClaims200Response(claims []*verifiable.W3CCredential) GetClaimsResponse { + response := make(GetClaims200JSONResponse, len(claims)) + for i := range claims { + response[i] = toGetClaim200Response(claims[i]) + } + + return response +} + +func toGetClaim200Response(claim *verifiable.W3CCredential) GetClaimResponse { + var claimExpiration, claimIssuanceDate *TimeUTC + if claim.Expiration != nil { + claimExpiration = common.ToPointer(TimeUTC(*claim.Expiration)) + } + if claim.IssuanceDate != nil { + claimIssuanceDate = common.ToPointer(TimeUTC(*claim.IssuanceDate)) + } + + var refreshService *RefreshService + if claim.RefreshService != nil { + refreshService = &RefreshService{ + Id: claim.RefreshService.ID, + Type: RefreshServiceType(claim.RefreshService.Type), + } + } + + var displayMethod *DisplayMethod + if claim.DisplayMethod != nil { + displayMethod = &DisplayMethod{ + Id: claim.DisplayMethod.ID, + Type: DisplayMethodType(claim.DisplayMethod.Type), + } + } + + return GetClaimResponse{ + Context: claim.Context, + CredentialSchema: CredentialSchema{ + claim.CredentialSchema.ID, + claim.CredentialSchema.Type, + }, + CredentialStatus: claim.CredentialStatus, + CredentialSubject: claim.CredentialSubject, + ExpirationDate: claimExpiration, + Id: claim.ID, + IssuanceDate: claimIssuanceDate, + Issuer: claim.Issuer, + Proof: claim.Proof, + Type: claim.Type, + RefreshService: refreshService, + DisplayMethod: displayMethod, + } +} + +func toGetClaimQrCode200JSONResponse(claim *domain.Claim, hostURL string) GetClaimQrCode200JSONResponse { + id := uuid.New() + return GetClaimQrCode200JSONResponse{ + Body: struct { + Credentials []struct { + Description string `json:"description"` + Id string `json:"id"` + } `json:"credentials"` + Url string `json:"url"` + }{ + Credentials: []struct { + Description string `json:"description"` + Id string `json:"id"` + }{ + { + Description: claim.SchemaType, + Id: claim.ID.String(), + }, + }, + Url: fmt.Sprintf("%s/v1/agent", strings.TrimSuffix(hostURL, "/")), + }, + From: claim.Issuer, + Id: id.String(), + Thid: id.String(), + To: claim.OtherIdentifier, + Typ: string(packers.MediaTypePlainMessage), + Type: string(protocol.CredentialOfferMessageType), + } +} diff --git a/internal/api/server_test.go b/internal/api/credentials_test.go similarity index 81% rename from internal/api/server_test.go rename to internal/api/credentials_test.go index e90762061..b6d15c915 100644 --- a/internal/api/server_test.go +++ b/internal/api/credentials_test.go @@ -12,7 +12,6 @@ import ( "time" "github.com/google/uuid" - core "github.com/iden3/go-iden3-core/v2" "github.com/iden3/go-iden3-core/v2/w3c" "github.com/iden3/go-schema-processor/v2/verifiable" "github.com/iden3/iden3comm/v2" @@ -36,223 +35,6 @@ import ( "github.com/polygonid/sh-id-platform/pkg/reverse_hash" ) -func TestServer_CreateIdentity(t *testing.T) { - const ( - method = "polygonid" - blockchain = "polygon" - network = "amoy" - BJJ = "BJJ" - ETH = "ETH" - ) - identityRepo := repositories.NewIdentity() - claimsRepo := repositories.NewClaims() - identityStateRepo := repositories.NewIdentityState() - mtRepo := repositories.NewIdentityMerkleTreeRepository() - mtService := services.NewIdentityMerkleTrees(mtRepo) - revocationRepository := repositories.NewRevocation() - connectionsRepository := repositories.NewConnections() - - reader := helpers.CreateFile(t) - - networkResolver, err := networkPkg.NewResolver(context.Background(), cfg, keyStore, reader) - require.NoError(t, err) - - rhsFactory := reverse_hash.NewFactory(*networkResolver, reverse_hash.DefaultRHSTimeOut) - revocationStatusResolver := revocation_status.NewRevocationStatusResolver(*networkResolver) - identityService := services.NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver) - - mediaTypeManager := services.NewMediaTypeManager( - map[iden3comm.ProtocolMessage][]string{ - protocol.CredentialFetchRequestMessageType: {string(packers.MediaTypeZKPMessage)}, - protocol.RevocationStatusRequestMessageType: {"*"}, - }, - true, - ) - claimsService := services.NewClaim(claimsRepo, identityService, nil, mtService, identityStateRepo, schemaLoader, storage, cfg.ServerUrl, pubsub.NewMock(), ipfsGatewayURL, revocationStatusResolver, mediaTypeManager) - accountService := services.NewAccountService(*networkResolver) - server := NewServer(&cfg, identityService, accountService, claimsService, nil, NewPublisherMock(), NewPackageManagerMock(), *networkResolver, nil) - handler := getHandler(context.Background(), server) - - type expected struct { - httpCode int - message *string - } - type testConfig struct { - name string - auth func() (string, string) - input CreateIdentityRequest - expected expected - } - - for _, tc := range []testConfig{ - { - name: "No auth header", - auth: authWrong, - expected: expected{ - httpCode: http.StatusUnauthorized, - }, - }, - { - name: "should create a BJJ identity for amoy network", - auth: authOk, - input: CreateIdentityRequest{ - DidMetadata: struct { - Blockchain string `json:"blockchain"` - Method string `json:"method"` - Network string `json:"network"` - Type CreateIdentityRequestDidMetadataType `json:"type"` - }{Blockchain: blockchain, Method: method, Network: string(core.Amoy), Type: BJJ}, - }, - expected: expected{ - httpCode: 201, - message: nil, - }, - }, - { - name: "should create a ETH identity for amoy network", - auth: authOk, - input: CreateIdentityRequest{ - DidMetadata: struct { - Blockchain string `json:"blockchain"` - Method string `json:"method"` - Network string `json:"network"` - Type CreateIdentityRequestDidMetadataType `json:"type"` - }{Blockchain: blockchain, Method: method, Network: string(core.Amoy), Type: ETH}, - }, - expected: expected{ - httpCode: 201, - message: nil, - }, - }, - { - name: "should create a BJJ identity", - auth: authOk, - input: CreateIdentityRequest{ - DidMetadata: struct { - Blockchain string `json:"blockchain"` - Method string `json:"method"` - Network string `json:"network"` - Type CreateIdentityRequestDidMetadataType `json:"type"` - }{Blockchain: blockchain, Method: method, Network: network, Type: BJJ}, - }, - expected: expected{ - httpCode: 201, - message: nil, - }, - }, - { - name: "should create a ETH identity", - auth: authOk, - input: CreateIdentityRequest{ - DidMetadata: struct { - Blockchain string `json:"blockchain"` - Method string `json:"method"` - Network string `json:"network"` - Type CreateIdentityRequestDidMetadataType `json:"type"` - }{Blockchain: blockchain, Method: method, Network: network, Type: ETH}, - }, - expected: expected{ - httpCode: 201, - message: nil, - }, - }, - { - name: "should return an error wrong network", - auth: authOk, - input: CreateIdentityRequest{ - DidMetadata: struct { - Blockchain string `json:"blockchain"` - Method string `json:"method"` - Network string `json:"network"` - Type CreateIdentityRequestDidMetadataType `json:"type"` - }{Blockchain: blockchain, Method: method, Network: "mynetwork", Type: BJJ}, - }, - expected: expected{ - httpCode: 400, - message: common.ToPointer("error getting reverse hash service settings: rhsSettings not found for polygon:mynetwork"), - }, - }, - { - name: "should return an error wrong method", - auth: authOk, - input: CreateIdentityRequest{ - DidMetadata: struct { - Blockchain string `json:"blockchain"` - Method string `json:"method"` - Network string `json:"network"` - Type CreateIdentityRequestDidMetadataType `json:"type"` - }{Blockchain: blockchain, Method: "my method", Network: network, Type: BJJ}, - }, - expected: expected{ - httpCode: 400, - message: common.ToPointer("cannot create identity: can't add genesis claims to tree: wrong DID Metadata"), - }, - }, - { - name: "should return an error wrong blockchain", - auth: authOk, - input: CreateIdentityRequest{ - DidMetadata: struct { - Blockchain string `json:"blockchain"` - Method string `json:"method"` - Network string `json:"network"` - Type CreateIdentityRequestDidMetadataType `json:"type"` - }{Blockchain: "my blockchain", Method: method, Network: network, Type: BJJ}, - }, - expected: expected{ - httpCode: 400, - message: common.ToPointer("error getting reverse hash service settings: rhsSettings not found for my blockchain:amoy"), - }, - }, - { - name: "should return an error wrong type", - auth: authOk, - input: CreateIdentityRequest{ - DidMetadata: struct { - Blockchain string `json:"blockchain"` - Method string `json:"method"` - Network string `json:"network"` - Type CreateIdentityRequestDidMetadataType `json:"type"` - }{Blockchain: "my blockchain", Method: method, Network: network, Type: "a wrong type"}, - }, - expected: expected{ - httpCode: 400, - message: common.ToPointer("Type must be BJJ or ETH"), - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - rr := httptest.NewRecorder() - req, err := http.NewRequest("POST", "/v1/identities", tests.JSONBody(t, tc.input)) - req.SetBasicAuth(tc.auth()) - require.NoError(t, err) - handler.ServeHTTP(rr, req) - require.Equal(t, tc.expected.httpCode, rr.Code) - switch tc.expected.httpCode { - case http.StatusCreated: - var response CreateIdentityResponse - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) - require.NotNil(t, response.Identifier) - assert.Contains(t, *response.Identifier, tc.input.DidMetadata.Network) - assert.NotNil(t, response.State.CreatedAt) - assert.NotNil(t, response.State.ModifiedAt) - assert.NotNil(t, response.State.State) - assert.NotNil(t, response.State.Status) - if tc.input.DidMetadata.Type == BJJ { - assert.NotNil(t, *response.State.ClaimsTreeRoot) - } - if tc.input.DidMetadata.Type == ETH { - assert.NotNil(t, *response.Address) - } - case http.StatusBadRequest: - var response CreateIdentity400JSONResponse - assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) - assert.Equal(t, *tc.expected.message, response.Message) - } - }) - } -} - func TestServer_RevokeClaim(t *testing.T) { identityRepo := repositories.NewIdentity() claimsRepo := repositories.NewClaims() @@ -730,92 +512,6 @@ func TestServer_CreateCredential(t *testing.T) { } } -func TestServer_GetIdentities(t *testing.T) { - ctx := context.Background() - identityRepo := repositories.NewIdentity() - claimsRepo := repositories.NewClaims() - identityStateRepo := repositories.NewIdentityState() - mtRepo := repositories.NewIdentityMerkleTreeRepository() - mtService := services.NewIdentityMerkleTrees(mtRepo) - revocationRepository := repositories.NewRevocation() - connectionsRepository := repositories.NewConnections() - - reader := helpers.CreateFile(t) - networkResolver, err := networkPkg.NewResolver(ctx, cfg, keyStore, reader) - require.NoError(t, err) - - rhsFactory := reverse_hash.NewFactory(*networkResolver, reverse_hash.DefaultRHSTimeOut) - revocationStatusResolver := revocation_status.NewRevocationStatusResolver(*networkResolver) - identityService := services.NewIdentity(&KMSMock{}, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver) - - mediaTypeManager := services.NewMediaTypeManager( - map[iden3comm.ProtocolMessage][]string{ - protocol.CredentialFetchRequestMessageType: {string(packers.MediaTypeZKPMessage)}, - protocol.RevocationStatusRequestMessageType: {"*"}, - }, - true, - ) - - claimsService := services.NewClaim(claimsRepo, identityService, nil, mtService, identityStateRepo, schemaLoader, storage, cfg.ServerUrl, pubsub.NewMock(), ipfsGatewayURL, revocationStatusResolver, mediaTypeManager) - accountService := services.NewAccountService(*networkResolver) - server := NewServer(&cfg, identityService, accountService, claimsService, nil, NewPublisherMock(), NewPackageManagerMock(), *networkResolver, nil) - handler := getHandler(context.Background(), server) - - idStr1 := "did:polygonid:polygon:mumbai:2qE1ZT16aqEWhh9mX9aqM2pe2ZwV995dTkReeKwCaQ" - idStr2 := "did:polygonid:polygon:mumbai:2qMHFTHn2SC3XkBEJrR4eH4Yk8jRGg5bzYYG1ZGECa" - identity1 := &domain.Identity{ - Identifier: idStr1, - } - identity2 := &domain.Identity{ - Identifier: idStr2, - } - fixture := tests.NewFixture(storage) - fixture.CreateIdentity(t, identity1) - fixture.CreateIdentity(t, identity2) - - type expected struct { - httpCode int - } - type testConfig struct { - name string - auth func() (string, string) - expected expected - } - - for _, tc := range []testConfig{ - { - name: "No auth header", - auth: authWrong, - expected: expected{ - httpCode: http.StatusUnauthorized, - }, - }, - { - name: "should return all the entities", - auth: authOk, - expected: expected{ - httpCode: 200, - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - rr := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/v1/identities", nil) - req.SetBasicAuth(tc.auth()) - require.NoError(t, err) - handler.ServeHTTP(rr, req) - - require.Equal(t, tc.expected.httpCode, rr.Code) - if tc.expected.httpCode == http.StatusOK { - var response GetIdentities200JSONResponse - assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) - assert.Equal(t, tc.expected.httpCode, rr.Code) - assert.True(t, len(response) >= 2) - } - }) - } -} - func TestServer_GetCredentialQrCode(t *testing.T) { ctx := context.Background() identityRepo := repositories.NewIdentity() diff --git a/internal/api/identity.go b/internal/api/identity.go new file mode 100644 index 000000000..770bc1aa2 --- /dev/null +++ b/internal/api/identity.go @@ -0,0 +1,217 @@ +package api + +import ( + "context" + "errors" + "fmt" + + core "github.com/iden3/go-iden3-core/v2" + "github.com/iden3/go-iden3-core/v2/w3c" + + "github.com/polygonid/sh-id-platform/internal/common" + "github.com/polygonid/sh-id-platform/internal/core/ports" + "github.com/polygonid/sh-id-platform/internal/core/services" + "github.com/polygonid/sh-id-platform/internal/gateways" + "github.com/polygonid/sh-id-platform/internal/kms" + "github.com/polygonid/sh-id-platform/internal/log" +) + +// CreateIdentity is created identity controller +func (s *Server) CreateIdentity(ctx context.Context, request CreateIdentityRequestObject) (CreateIdentityResponseObject, error) { + method := request.Body.DidMetadata.Method + blockchain := request.Body.DidMetadata.Blockchain + network := request.Body.DidMetadata.Network + keyType := request.Body.DidMetadata.Type + + if keyType != "BJJ" && keyType != "ETH" { + return CreateIdentity400JSONResponse{ + N400JSONResponse{ + Message: "Type must be BJJ or ETH", + }, + }, nil + } + + rhsSettings, err := s.networkResolver.GetRhsSettingsForBlockchainAndNetwork(ctx, blockchain, network) + if err != nil { + return CreateIdentity400JSONResponse{N400JSONResponse{Message: fmt.Sprintf("error getting reverse hash service settings: %s", err.Error())}}, nil + } + + identity, err := s.identityService.Create(ctx, s.cfg.ServerUrl, &ports.DIDCreationOptions{ + Method: core.DIDMethod(method), + Network: core.NetworkID(network), + Blockchain: core.Blockchain(blockchain), + KeyType: kms.KeyType(keyType), + AuthBJJCredentialStatus: rhsSettings.CredentialStatusType, + }) + if err != nil { + if errors.Is(err, services.ErrWrongDIDMetada) { + return CreateIdentity400JSONResponse{ + N400JSONResponse{ + Message: err.Error(), + }, + }, nil + } + + if errors.Is(err, kms.ErrPermissionDenied) { + var message string + if s.cfg.VaultUserPassAuthEnabled { + message = "Issuer Node cannot connect with Vault. Please check the value of ISSUER_VAULT_USERPASS_AUTH_PASSWORD variable." + } else { + message = `Issuer Node cannot connect with Vault. Please check the value of ISSUER_KEY_STORE_TOKEN variable.` + } + + log.Info(ctx, message+". More information in this link: https://docs.privado.id/docs/issuer/vault-auth/") + return CreateIdentity403JSONResponse{ + N403JSONResponse{ + Message: message, + }, + }, nil + } + + return nil, err + } + + return CreateIdentity201JSONResponse{ + Identifier: &identity.Identifier, + State: &IdentityState{ + BlockNumber: identity.State.BlockNumber, + BlockTimestamp: identity.State.BlockTimestamp, + ClaimsTreeRoot: identity.State.ClaimsTreeRoot, + CreatedAt: TimeUTC(identity.State.CreatedAt), + ModifiedAt: TimeUTC(identity.State.ModifiedAt), + PreviousState: identity.State.PreviousState, + RevocationTreeRoot: identity.State.RevocationTreeRoot, + RootOfRoots: identity.State.RootOfRoots, + State: identity.State.State, + Status: string(identity.State.Status), + TxID: identity.State.TxID, + }, + Address: identity.Address, + }, nil +} + +// GetIdentities is the controller to get identities +func (s *Server) GetIdentities(ctx context.Context, request GetIdentitiesRequestObject) (GetIdentitiesResponseObject, error) { + var response GetIdentities200JSONResponse + var err error + response, err = s.identityService.Get(ctx) + if err != nil { + return GetIdentities500JSONResponse{N500JSONResponse{ + Message: err.Error(), + }}, nil + } + + return response, nil +} + +// GetIdentityDetails is the controller to get identity details +func (s *Server) GetIdentityDetails(ctx context.Context, request GetIdentityDetailsRequestObject) (GetIdentityDetailsResponseObject, error) { + userDID, err := w3c.ParseDID(request.Identifier) + if err != nil { + log.Error(ctx, "get identity details. Parsing did", "err", err) + return GetIdentityDetails400JSONResponse{ + N400JSONResponse{ + Message: "invalid did", + }, + }, err + } + + identity, err := s.identityService.GetByDID(ctx, *userDID) + if err != nil { + log.Error(ctx, "get identity details. Getting identity", "err", err) + return GetIdentityDetails500JSONResponse{ + N500JSONResponse{ + Message: err.Error(), + }, + }, err + } + + if identity.KeyType == string(kms.KeyTypeEthereum) { + did, err := w3c.ParseDID(identity.Identifier) + if err != nil { + log.Error(ctx, "get identity details. Parsing did", "err", err) + return GetIdentityDetails400JSONResponse{N400JSONResponse{Message: "invalid did"}}, nil + } + balance, err := s.accountService.GetBalanceByDID(ctx, did) + if err != nil { + log.Error(ctx, "get identity details. Getting balance", "err", err) + return GetIdentityDetails500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil + } + identity.Balance = balance + } + + response := GetIdentityDetails200JSONResponse{ + Identifier: &identity.Identifier, + State: &IdentityState{ + BlockNumber: identity.State.BlockNumber, + BlockTimestamp: identity.State.BlockTimestamp, + ClaimsTreeRoot: identity.State.ClaimsTreeRoot, + CreatedAt: TimeUTC(identity.State.CreatedAt), + ModifiedAt: TimeUTC(identity.State.ModifiedAt), + PreviousState: identity.State.PreviousState, + RevocationTreeRoot: identity.State.RevocationTreeRoot, + RootOfRoots: identity.State.RootOfRoots, + State: identity.State.State, + Status: string(identity.State.Status), + TxID: identity.State.TxID, + }, + } + + if identity.Address != nil && *identity.Address != "" { + response.Address = identity.Address + } + + if identity.Balance != nil { + response.Balance = common.ToPointer(identity.Balance.String()) + } + + return response, nil +} + +// PublishIdentityState - publish identity state on chain +func (s *Server) PublishIdentityState(ctx context.Context, request PublishIdentityStateRequestObject) (PublishIdentityStateResponseObject, error) { + did, err := w3c.ParseDID(request.Identifier) + if err != nil { + return PublishIdentityState400JSONResponse{N400JSONResponse{"invalid did"}}, nil + } + + publishedState, err := s.publisherGateway.PublishState(ctx, did) + if err != nil { + if errors.Is(err, gateways.ErrNoStatesToProcess) || errors.Is(err, gateways.ErrStateIsBeingProcessed) { + return PublishIdentityState200JSONResponse{Message: err.Error()}, nil + } + return PublishIdentityState500JSONResponse{N500JSONResponse{err.Error()}}, nil + } + + return PublishIdentityState202JSONResponse{ + ClaimsTreeRoot: publishedState.ClaimsTreeRoot, + RevocationTreeRoot: publishedState.RevocationTreeRoot, + RootOfRoots: publishedState.RootOfRoots, + State: publishedState.State, + TxID: publishedState.TxID, + }, nil +} + +// RetryPublishState - retry to publish the current state if it failed previously. +func (s *Server) RetryPublishState(ctx context.Context, request RetryPublishStateRequestObject) (RetryPublishStateResponseObject, error) { + did, err := w3c.ParseDID(request.Identifier) + if err != nil { + return RetryPublishState400JSONResponse{N400JSONResponse{"invalid did"}}, nil + } + + publishedState, err := s.publisherGateway.RetryPublishState(ctx, did) + if err != nil { + log.Error(ctx, "error retrying the publishing the state", "err", err) + if errors.Is(err, gateways.ErrStateIsBeingProcessed) || errors.Is(err, gateways.ErrNoFailedStatesToProcess) { + return RetryPublishState400JSONResponse{N400JSONResponse{Message: err.Error()}}, nil + } + return RetryPublishState500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil + } + return RetryPublishState202JSONResponse{ + ClaimsTreeRoot: publishedState.ClaimsTreeRoot, + RevocationTreeRoot: publishedState.RevocationTreeRoot, + RootOfRoots: publishedState.RootOfRoots, + State: publishedState.State, + TxID: publishedState.TxID, + }, nil +} diff --git a/internal/api/identity_test.go b/internal/api/identity_test.go new file mode 100644 index 000000000..87cf1cc7e --- /dev/null +++ b/internal/api/identity_test.go @@ -0,0 +1,330 @@ +package api + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + core "github.com/iden3/go-iden3-core/v2" + "github.com/iden3/iden3comm/v2" + "github.com/iden3/iden3comm/v2/packers" + "github.com/iden3/iden3comm/v2/protocol" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/polygonid/sh-id-platform/internal/common" + "github.com/polygonid/sh-id-platform/internal/core/domain" + "github.com/polygonid/sh-id-platform/internal/core/services" + "github.com/polygonid/sh-id-platform/internal/db/tests" + "github.com/polygonid/sh-id-platform/internal/repositories" + "github.com/polygonid/sh-id-platform/pkg/credentials/revocation_status" + "github.com/polygonid/sh-id-platform/pkg/helpers" + networkPkg "github.com/polygonid/sh-id-platform/pkg/network" + "github.com/polygonid/sh-id-platform/pkg/pubsub" + "github.com/polygonid/sh-id-platform/pkg/reverse_hash" +) + +func TestServer_CreateIdentity(t *testing.T) { + const ( + method = "polygonid" + blockchain = "polygon" + network = "amoy" + BJJ = "BJJ" + ETH = "ETH" + ) + identityRepo := repositories.NewIdentity() + claimsRepo := repositories.NewClaims() + identityStateRepo := repositories.NewIdentityState() + mtRepo := repositories.NewIdentityMerkleTreeRepository() + mtService := services.NewIdentityMerkleTrees(mtRepo) + revocationRepository := repositories.NewRevocation() + connectionsRepository := repositories.NewConnections() + + reader := helpers.CreateFile(t) + + networkResolver, err := networkPkg.NewResolver(context.Background(), cfg, keyStore, reader) + require.NoError(t, err) + + rhsFactory := reverse_hash.NewFactory(*networkResolver, reverse_hash.DefaultRHSTimeOut) + revocationStatusResolver := revocation_status.NewRevocationStatusResolver(*networkResolver) + identityService := services.NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver) + + mediaTypeManager := services.NewMediaTypeManager( + map[iden3comm.ProtocolMessage][]string{ + protocol.CredentialFetchRequestMessageType: {string(packers.MediaTypeZKPMessage)}, + protocol.RevocationStatusRequestMessageType: {"*"}, + }, + true, + ) + claimsService := services.NewClaim(claimsRepo, identityService, nil, mtService, identityStateRepo, schemaLoader, storage, cfg.ServerUrl, pubsub.NewMock(), ipfsGatewayURL, revocationStatusResolver, mediaTypeManager) + accountService := services.NewAccountService(*networkResolver) + server := NewServer(&cfg, identityService, accountService, claimsService, nil, NewPublisherMock(), NewPackageManagerMock(), *networkResolver, nil) + handler := getHandler(context.Background(), server) + + type expected struct { + httpCode int + message *string + } + type testConfig struct { + name string + auth func() (string, string) + input CreateIdentityRequest + expected expected + } + + for _, tc := range []testConfig{ + { + name: "No auth header", + auth: authWrong, + expected: expected{ + httpCode: http.StatusUnauthorized, + }, + }, + { + name: "should create a BJJ identity for amoy network", + auth: authOk, + input: CreateIdentityRequest{ + DidMetadata: struct { + Blockchain string `json:"blockchain"` + Method string `json:"method"` + Network string `json:"network"` + Type CreateIdentityRequestDidMetadataType `json:"type"` + }{Blockchain: blockchain, Method: method, Network: string(core.Amoy), Type: BJJ}, + }, + expected: expected{ + httpCode: 201, + message: nil, + }, + }, + { + name: "should create a ETH identity for amoy network", + auth: authOk, + input: CreateIdentityRequest{ + DidMetadata: struct { + Blockchain string `json:"blockchain"` + Method string `json:"method"` + Network string `json:"network"` + Type CreateIdentityRequestDidMetadataType `json:"type"` + }{Blockchain: blockchain, Method: method, Network: string(core.Amoy), Type: ETH}, + }, + expected: expected{ + httpCode: 201, + message: nil, + }, + }, + { + name: "should create a BJJ identity", + auth: authOk, + input: CreateIdentityRequest{ + DidMetadata: struct { + Blockchain string `json:"blockchain"` + Method string `json:"method"` + Network string `json:"network"` + Type CreateIdentityRequestDidMetadataType `json:"type"` + }{Blockchain: blockchain, Method: method, Network: network, Type: BJJ}, + }, + expected: expected{ + httpCode: 201, + message: nil, + }, + }, + { + name: "should create a ETH identity", + auth: authOk, + input: CreateIdentityRequest{ + DidMetadata: struct { + Blockchain string `json:"blockchain"` + Method string `json:"method"` + Network string `json:"network"` + Type CreateIdentityRequestDidMetadataType `json:"type"` + }{Blockchain: blockchain, Method: method, Network: network, Type: ETH}, + }, + expected: expected{ + httpCode: 201, + message: nil, + }, + }, + { + name: "should return an error wrong network", + auth: authOk, + input: CreateIdentityRequest{ + DidMetadata: struct { + Blockchain string `json:"blockchain"` + Method string `json:"method"` + Network string `json:"network"` + Type CreateIdentityRequestDidMetadataType `json:"type"` + }{Blockchain: blockchain, Method: method, Network: "mynetwork", Type: BJJ}, + }, + expected: expected{ + httpCode: 400, + message: common.ToPointer("error getting reverse hash service settings: rhsSettings not found for polygon:mynetwork"), + }, + }, + { + name: "should return an error wrong method", + auth: authOk, + input: CreateIdentityRequest{ + DidMetadata: struct { + Blockchain string `json:"blockchain"` + Method string `json:"method"` + Network string `json:"network"` + Type CreateIdentityRequestDidMetadataType `json:"type"` + }{Blockchain: blockchain, Method: "my method", Network: network, Type: BJJ}, + }, + expected: expected{ + httpCode: 400, + message: common.ToPointer("cannot create identity: can't add genesis claims to tree: wrong DID Metadata"), + }, + }, + { + name: "should return an error wrong blockchain", + auth: authOk, + input: CreateIdentityRequest{ + DidMetadata: struct { + Blockchain string `json:"blockchain"` + Method string `json:"method"` + Network string `json:"network"` + Type CreateIdentityRequestDidMetadataType `json:"type"` + }{Blockchain: "my blockchain", Method: method, Network: network, Type: BJJ}, + }, + expected: expected{ + httpCode: 400, + message: common.ToPointer("error getting reverse hash service settings: rhsSettings not found for my blockchain:amoy"), + }, + }, + { + name: "should return an error wrong type", + auth: authOk, + input: CreateIdentityRequest{ + DidMetadata: struct { + Blockchain string `json:"blockchain"` + Method string `json:"method"` + Network string `json:"network"` + Type CreateIdentityRequestDidMetadataType `json:"type"` + }{Blockchain: "my blockchain", Method: method, Network: network, Type: "a wrong type"}, + }, + expected: expected{ + httpCode: 400, + message: common.ToPointer("Type must be BJJ or ETH"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + rr := httptest.NewRecorder() + req, err := http.NewRequest("POST", "/v1/identities", tests.JSONBody(t, tc.input)) + req.SetBasicAuth(tc.auth()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + require.Equal(t, tc.expected.httpCode, rr.Code) + switch tc.expected.httpCode { + case http.StatusCreated: + var response CreateIdentityResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + require.NotNil(t, response.Identifier) + assert.Contains(t, *response.Identifier, tc.input.DidMetadata.Network) + assert.NotNil(t, response.State.CreatedAt) + assert.NotNil(t, response.State.ModifiedAt) + assert.NotNil(t, response.State.State) + assert.NotNil(t, response.State.Status) + if tc.input.DidMetadata.Type == BJJ { + assert.NotNil(t, *response.State.ClaimsTreeRoot) + } + if tc.input.DidMetadata.Type == ETH { + assert.NotNil(t, *response.Address) + } + case http.StatusBadRequest: + var response CreateIdentity400JSONResponse + assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.Equal(t, *tc.expected.message, response.Message) + } + }) + } +} + +func TestServer_GetIdentities(t *testing.T) { + ctx := context.Background() + identityRepo := repositories.NewIdentity() + claimsRepo := repositories.NewClaims() + identityStateRepo := repositories.NewIdentityState() + mtRepo := repositories.NewIdentityMerkleTreeRepository() + mtService := services.NewIdentityMerkleTrees(mtRepo) + revocationRepository := repositories.NewRevocation() + connectionsRepository := repositories.NewConnections() + + reader := helpers.CreateFile(t) + networkResolver, err := networkPkg.NewResolver(ctx, cfg, keyStore, reader) + require.NoError(t, err) + + rhsFactory := reverse_hash.NewFactory(*networkResolver, reverse_hash.DefaultRHSTimeOut) + revocationStatusResolver := revocation_status.NewRevocationStatusResolver(*networkResolver) + identityService := services.NewIdentity(&KMSMock{}, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver) + + mediaTypeManager := services.NewMediaTypeManager( + map[iden3comm.ProtocolMessage][]string{ + protocol.CredentialFetchRequestMessageType: {string(packers.MediaTypeZKPMessage)}, + protocol.RevocationStatusRequestMessageType: {"*"}, + }, + true, + ) + + claimsService := services.NewClaim(claimsRepo, identityService, nil, mtService, identityStateRepo, schemaLoader, storage, cfg.ServerUrl, pubsub.NewMock(), ipfsGatewayURL, revocationStatusResolver, mediaTypeManager) + accountService := services.NewAccountService(*networkResolver) + server := NewServer(&cfg, identityService, accountService, claimsService, nil, NewPublisherMock(), NewPackageManagerMock(), *networkResolver, nil) + handler := getHandler(context.Background(), server) + + idStr1 := "did:polygonid:polygon:mumbai:2qE1ZT16aqEWhh9mX9aqM2pe2ZwV995dTkReeKwCaQ" + idStr2 := "did:polygonid:polygon:mumbai:2qMHFTHn2SC3XkBEJrR4eH4Yk8jRGg5bzYYG1ZGECa" + identity1 := &domain.Identity{ + Identifier: idStr1, + } + identity2 := &domain.Identity{ + Identifier: idStr2, + } + fixture := tests.NewFixture(storage) + fixture.CreateIdentity(t, identity1) + fixture.CreateIdentity(t, identity2) + + type expected struct { + httpCode int + } + type testConfig struct { + name string + auth func() (string, string) + expected expected + } + + for _, tc := range []testConfig{ + { + name: "No auth header", + auth: authWrong, + expected: expected{ + httpCode: http.StatusUnauthorized, + }, + }, + { + name: "should return all the entities", + auth: authOk, + expected: expected{ + httpCode: 200, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/v1/identities", nil) + req.SetBasicAuth(tc.auth()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + + require.Equal(t, tc.expected.httpCode, rr.Code) + if tc.expected.httpCode == http.StatusOK { + var response GetIdentities200JSONResponse + assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.Equal(t, tc.expected.httpCode, rr.Code) + assert.True(t, len(response) >= 2) + } + }) + } +} diff --git a/internal/api/qrcode.go b/internal/api/qrcode.go new file mode 100644 index 000000000..ef7f8931a --- /dev/null +++ b/internal/api/qrcode.go @@ -0,0 +1,21 @@ +package api + +import ( + "context" + + "github.com/polygonid/sh-id-platform/internal/log" +) + +// GetQrFromStore is the controller to get qr bodies +func (s *Server) GetQrFromStore(ctx context.Context, request GetQrFromStoreRequestObject) (GetQrFromStoreResponseObject, error) { + if request.Params.Id == nil { + log.Warn(ctx, "qr store. Missing id parameter") + return GetQrFromStore400JSONResponse{N400JSONResponse{"id is required"}}, nil + } + body, err := s.qrService.Find(ctx, *request.Params.Id) + if err != nil { + log.Error(ctx, "qr store. Finding qr", "err", err, "id", *request.Params.Id) + return GetQrFromStore500JSONResponse{N500JSONResponse{"error looking for qr body"}}, nil + } + return NewQrContentResponse(body), nil +} diff --git a/internal/api/server.go b/internal/api/server.go index 2e87878dd..0bff92746 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -2,34 +2,16 @@ package api import ( "context" - "errors" - "fmt" "net/http" "os" - "strings" - "time" "github.com/go-chi/chi/v5" - "github.com/google/uuid" - core "github.com/iden3/go-iden3-core/v2" - "github.com/iden3/go-iden3-core/v2/w3c" - "github.com/iden3/go-schema-processor/v2/verifiable" "github.com/iden3/iden3comm/v2" - "github.com/iden3/iden3comm/v2/packers" - "github.com/iden3/iden3comm/v2/protocol" - "github.com/polygonid/sh-id-platform/internal/common" "github.com/polygonid/sh-id-platform/internal/config" - "github.com/polygonid/sh-id-platform/internal/core/domain" "github.com/polygonid/sh-id-platform/internal/core/ports" - "github.com/polygonid/sh-id-platform/internal/core/services" - "github.com/polygonid/sh-id-platform/internal/gateways" "github.com/polygonid/sh-id-platform/internal/health" - "github.com/polygonid/sh-id-platform/internal/kms" - "github.com/polygonid/sh-id-platform/internal/log" - "github.com/polygonid/sh-id-platform/internal/repositories" "github.com/polygonid/sh-id-platform/pkg/network" - "github.com/polygonid/sh-id-platform/pkg/schema" ) // Server implements StrictServerInterface and holds the implementation of all API controllers @@ -83,651 +65,6 @@ func (s *Server) GetYaml(_ context.Context, _ GetYamlRequestObject) (GetYamlResp return nil, nil } -// CreateIdentity is created identity controller -func (s *Server) CreateIdentity(ctx context.Context, request CreateIdentityRequestObject) (CreateIdentityResponseObject, error) { - method := request.Body.DidMetadata.Method - blockchain := request.Body.DidMetadata.Blockchain - network := request.Body.DidMetadata.Network - keyType := request.Body.DidMetadata.Type - - if keyType != "BJJ" && keyType != "ETH" { - return CreateIdentity400JSONResponse{ - N400JSONResponse{ - Message: "Type must be BJJ or ETH", - }, - }, nil - } - - rhsSettings, err := s.networkResolver.GetRhsSettingsForBlockchainAndNetwork(ctx, blockchain, network) - if err != nil { - return CreateIdentity400JSONResponse{N400JSONResponse{Message: fmt.Sprintf("error getting reverse hash service settings: %s", err.Error())}}, nil - } - - identity, err := s.identityService.Create(ctx, s.cfg.ServerUrl, &ports.DIDCreationOptions{ - Method: core.DIDMethod(method), - Network: core.NetworkID(network), - Blockchain: core.Blockchain(blockchain), - KeyType: kms.KeyType(keyType), - AuthBJJCredentialStatus: rhsSettings.CredentialStatusType, - }) - if err != nil { - if errors.Is(err, services.ErrWrongDIDMetada) { - return CreateIdentity400JSONResponse{ - N400JSONResponse{ - Message: err.Error(), - }, - }, nil - } - - if errors.Is(err, kms.ErrPermissionDenied) { - var message string - if s.cfg.VaultUserPassAuthEnabled { - message = "Issuer Node cannot connect with Vault. Please check the value of ISSUER_VAULT_USERPASS_AUTH_PASSWORD variable." - } else { - message = `Issuer Node cannot connect with Vault. Please check the value of ISSUER_KEY_STORE_TOKEN variable.` - } - - log.Info(ctx, message+". More information in this link: https://devs.polygonid.com/docs/issuer/vault-auth") - return CreateIdentity403JSONResponse{ - N403JSONResponse{ - Message: message, - }, - }, nil - } - - return nil, err - } - - return CreateIdentity201JSONResponse{ - Identifier: &identity.Identifier, - State: &IdentityState{ - BlockNumber: identity.State.BlockNumber, - BlockTimestamp: identity.State.BlockTimestamp, - ClaimsTreeRoot: identity.State.ClaimsTreeRoot, - CreatedAt: TimeUTC(identity.State.CreatedAt), - ModifiedAt: TimeUTC(identity.State.ModifiedAt), - PreviousState: identity.State.PreviousState, - RevocationTreeRoot: identity.State.RevocationTreeRoot, - RootOfRoots: identity.State.RootOfRoots, - State: identity.State.State, - Status: string(identity.State.Status), - TxID: identity.State.TxID, - }, - Address: identity.Address, - }, nil -} - -// CreateCredential is the creation credential controller. It creates a credential and returns the id -func (s *Server) CreateCredential(ctx context.Context, request CreateCredentialRequestObject) (CreateCredentialResponseObject, error) { - ret, err := s.CreateClaim(ctx, CreateClaimRequestObject(request)) - if err != nil { - return CreateCredential500JSONResponse{N500JSONResponse{Message: err.Error()}}, err - } - switch ret := ret.(type) { - case CreateClaim201JSONResponse: - return CreateCredential201JSONResponse(ret), nil - case CreateClaim400JSONResponse: - return CreateCredential400JSONResponse{N400JSONResponse{Message: ret.Message}}, nil - case CreateClaim422JSONResponse: - return CreateCredential422JSONResponse{N422JSONResponse{Message: ret.Message}}, nil - case CreateClaim500JSONResponse: - return CreateCredential500JSONResponse{N500JSONResponse{Message: ret.Message}}, nil - default: - log.Error(ctx, "unexpected return type", "type", fmt.Sprintf("%T", ret)) - return CreateCredential500JSONResponse{N500JSONResponse{Message: fmt.Sprintf("unexpected return type: %T", ret)}}, nil - } -} - -// CreateClaim is claim creation controller -// deprecated - Use CreateCredential instead -func (s *Server) CreateClaim(ctx context.Context, request CreateClaimRequestObject) (CreateClaimResponseObject, error) { - did, err := w3c.ParseDID(request.Identifier) - if err != nil { - return CreateClaim400JSONResponse{N400JSONResponse{Message: err.Error()}}, nil - } - var expiration *time.Time - if request.Body.Expiration != nil { - expiration = common.ToPointer(time.Unix(*request.Body.Expiration, 0)) - } - - claimRequestProofs := ports.ClaimRequestProofs{} - if request.Body.Proofs == nil { - claimRequestProofs.BJJSignatureProof2021 = true - claimRequestProofs.Iden3SparseMerkleTreeProof = true - } else { - for _, proof := range *request.Body.Proofs { - if string(proof) == string(verifiable.BJJSignatureProofType) { - claimRequestProofs.BJJSignatureProof2021 = true - continue - } - if string(proof) == string(verifiable.Iden3SparseMerkleTreeProofType) { - claimRequestProofs.Iden3SparseMerkleTreeProof = true - continue - } - return CreateClaim400JSONResponse{N400JSONResponse{Message: fmt.Sprintf("unsupported proof type: %s", proof)}}, nil - } - } - - resolverPrefix, err := common.ResolverPrefix(did) - if err != nil { - return CreateClaim400JSONResponse{N400JSONResponse{Message: "error parsing did"}}, nil - } - - rhsSettings, err := s.networkResolver.GetRhsSettings(ctx, resolverPrefix) - if err != nil { - return CreateClaim400JSONResponse{N400JSONResponse{Message: "error getting reverse hash service settings"}}, nil - } - - req := ports.NewCreateClaimRequest(did, request.Body.ClaimID, request.Body.CredentialSchema, request.Body.CredentialSubject, expiration, request.Body.Type, request.Body.Version, request.Body.SubjectPosition, request.Body.MerklizedRootPosition, claimRequestProofs, nil, false, rhsSettings.CredentialStatusType, toVerifiableRefreshService(request.Body.RefreshService), request.Body.RevNonce, - toVerifiableDisplayMethod(request.Body.DisplayMethod)) - - resp, err := s.claimService.Save(ctx, req) - if err != nil { - if errors.Is(err, services.ErrLoadingSchema) { - return CreateClaim422JSONResponse{N422JSONResponse{Message: err.Error()}}, nil - } - errs := []error{ - services.ErrJSONLdContext, - services.ErrProcessSchema, - services.ErrMalformedURL, - services.ErrParseClaim, - services.ErrInvalidCredentialSubject, - services.ErrAssigningMTPProof, - services.ErrUnsupportedRefreshServiceType, - services.ErrRefreshServiceLacksExpirationTime, - services.ErrRefreshServiceLacksURL, - services.ErrDisplayMethodLacksURL, - services.ErrUnsupportedDisplayMethodType, - } - for _, e := range errs { - if errors.Is(err, e) { - return CreateClaim400JSONResponse{N400JSONResponse{Message: err.Error()}}, nil - } - } - return CreateClaim500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil - } - return CreateClaim201JSONResponse{Id: resp.ID.String()}, nil -} - -// RevokeCredential is the revocation claim controller -func (s *Server) RevokeCredential(ctx context.Context, request RevokeCredentialRequestObject) (RevokeCredentialResponseObject, error) { - did, err := w3c.ParseDID(request.Identifier) - if err != nil { - log.Warn(ctx, "revoke credential: invalid did", "err", err, "req", request) - return RevokeCredential400JSONResponse{N400JSONResponse{err.Error()}}, nil - } - - if err := s.claimService.Revoke(ctx, *did, uint64(request.Nonce), ""); err != nil { - if errors.Is(err, repositories.ErrClaimDoesNotExist) { - return RevokeCredential404JSONResponse{N404JSONResponse{ - Message: "the credential does not exist", - }}, nil - } - - return RevokeCredential500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil - } - return RevokeCredential202JSONResponse{ - Message: "credential revocation request sent", - }, nil -} - -// RevokeClaim is the revocation claim controller. -// Deprecated - use RevokeCredential instead -func (s *Server) RevokeClaim(ctx context.Context, request RevokeClaimRequestObject) (RevokeClaimResponseObject, error) { - did, err := w3c.ParseDID(request.Identifier) - if err != nil { - log.Warn(ctx, "revoke claim invalid did", "err", err, "req", request) - return RevokeClaim400JSONResponse{N400JSONResponse{err.Error()}}, nil - } - - if err := s.claimService.Revoke(ctx, *did, uint64(request.Nonce), ""); err != nil { - if errors.Is(err, repositories.ErrClaimDoesNotExist) { - return RevokeClaim404JSONResponse{N404JSONResponse{ - Message: "the claim does not exist", - }}, nil - } - - return RevokeClaim500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil - } - return RevokeClaim202JSONResponse{ - Message: "claim revocation request sent", - }, nil -} - -// GetRevocationStatusV2 is the controller to get revocation status -func (s *Server) GetRevocationStatusV2(ctx context.Context, request GetRevocationStatusV2RequestObject) (GetRevocationStatusV2ResponseObject, error) { - resp, err := s.GetRevocationStatus(ctx, GetRevocationStatusRequestObject(request)) - if err != nil { - return GetRevocationStatusV2500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil - } - switch ret := resp.(type) { - case GetRevocationStatus200JSONResponse: - return GetRevocationStatusV2200JSONResponse(ret), nil - case GetRevocationStatus500JSONResponse: - return GetRevocationStatusV2500JSONResponse{N500JSONResponse{Message: ret.Message}}, nil - default: - log.Error(ctx, "unexpected return type", "type", fmt.Sprintf("%T", ret)) - return GetRevocationStatusV2500JSONResponse{N500JSONResponse{Message: fmt.Sprintf("unexpected return type: %T", ret)}}, nil - } -} - -// GetRevocationStatus is the controller to get revocation status -// Deprecated - use GetRevocationStatusV2 instead -func (s *Server) GetRevocationStatus(ctx context.Context, request GetRevocationStatusRequestObject) (GetRevocationStatusResponseObject, error) { - issuerDID, err := w3c.ParseDID(request.Identifier) - if err != nil { - return GetRevocationStatus500JSONResponse{N500JSONResponse{ - Message: err.Error(), - }}, nil - } - - rs, err := s.claimService.GetRevocationStatus(ctx, *issuerDID, uint64(request.Nonce)) - if err != nil { - return GetRevocationStatus500JSONResponse{N500JSONResponse{ - Message: err.Error(), - }}, nil - } - - response := GetRevocationStatus200JSONResponse{} - response.Issuer.State = rs.Issuer.State - response.Issuer.RevocationTreeRoot = rs.Issuer.RevocationTreeRoot - response.Issuer.RootOfRoots = rs.Issuer.RootOfRoots - response.Issuer.ClaimsTreeRoot = rs.Issuer.ClaimsTreeRoot - response.Mtp.Existence = rs.MTP.Existence - - if rs.MTP.NodeAux != nil { - key := rs.MTP.NodeAux.Key - decodedKey := key.BigInt().String() - value := rs.MTP.NodeAux.Value - decodedValue := value.BigInt().String() - response.Mtp.NodeAux = &struct { - Key *string `json:"key,omitempty"` - Value *string `json:"value,omitempty"` - }{ - Key: &decodedKey, - Value: &decodedValue, - } - } - - response.Mtp.Existence = rs.MTP.Existence - siblings := make([]string, 0) - for _, s := range rs.MTP.AllSiblings() { - siblings = append(siblings, s.BigInt().String()) - } - response.Mtp.Siblings = &siblings - - return response, err -} - -// GetCredential is the controller to get a credential -func (s *Server) GetCredential(ctx context.Context, request GetCredentialRequestObject) (GetCredentialResponseObject, error) { - resp, err := s.GetClaim(ctx, GetClaimRequestObject(request)) - if err != nil { - return GetCredential500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil - } - switch ret := resp.(type) { - case GetClaim200JSONResponse: - return GetCredential200JSONResponse{ - Context: ret.Context, - CredentialSchema: CredentialSchema{ - Id: ret.CredentialSchema.Id, - Type: ret.CredentialSchema.Type, - }, - CredentialStatus: ret.CredentialStatus, - CredentialSubject: ret.CredentialSubject, - DisplayMethod: ret.DisplayMethod, - ExpirationDate: ret.ExpirationDate, - Id: ret.Id, - IssuanceDate: ret.IssuanceDate, - Issuer: ret.Issuer, - Proof: ret.Proof, - RefreshService: ret.RefreshService, - Type: ret.Type, - }, nil - case GetClaim400JSONResponse: - return GetCredential400JSONResponse{N400JSONResponse{Message: ret.Message}}, nil - case GetClaim404JSONResponse: - return GetCredential404JSONResponse{N404JSONResponse{Message: ret.Message}}, nil - case GetClaim500JSONResponse: - return GetCredential500JSONResponse{N500JSONResponse{Message: ret.Message}}, nil - default: - log.Error(ctx, "unexpected return type", "type", fmt.Sprintf("%T", ret)) - return GetCredential500JSONResponse{N500JSONResponse{Message: fmt.Sprintf("unexpected return type: %T", ret)}}, nil - } -} - -// GetClaim is the controller to get a claim. -// Deprecated - use GetCredential instead -func (s *Server) GetClaim(ctx context.Context, request GetClaimRequestObject) (GetClaimResponseObject, error) { - if request.Identifier == "" { - return GetClaim400JSONResponse{N400JSONResponse{"invalid did, cannot be empty"}}, nil - } - - did, err := w3c.ParseDID(request.Identifier) - if err != nil { - return GetClaim400JSONResponse{N400JSONResponse{"invalid did"}}, nil - } - - if request.Id == "" { - return GetClaim400JSONResponse{N400JSONResponse{"cannot proceed with an empty claim id"}}, nil - } - - clID, err := uuid.Parse(request.Id) - if err != nil { - return GetClaim400JSONResponse{N400JSONResponse{"invalid claim id"}}, nil - } - - claim, err := s.claimService.GetByID(ctx, did, clID) - if err != nil { - if errors.Is(err, services.ErrClaimNotFound) { - return GetClaim404JSONResponse{N404JSONResponse{err.Error()}}, nil - } - return GetClaim500JSONResponse{N500JSONResponse{err.Error()}}, nil - } - - w3c, err := schema.FromClaimModelToW3CCredential(*claim) - if err != nil { - return GetClaim500JSONResponse{N500JSONResponse{"invalid claim format"}}, nil - } - - return GetClaim200JSONResponse(toGetClaim200Response(w3c)), nil -} - -// GetCredentials is the controller to get multiple credentials of a determined identity -func (s *Server) GetCredentials(ctx context.Context, request GetCredentialsRequestObject) (GetCredentialsResponseObject, error) { - resp, err := s.GetClaims(ctx, GetClaimsRequestObject{ - Identifier: request.Identifier, - Params: GetClaimsParams(request.Params), - }) - if err != nil { - return GetCredentials500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil - } - switch ret := resp.(type) { - case GetClaims200JSONResponse: - return GetCredentials200JSONResponse(ret), nil - case GetClaims400JSONResponse: - return GetCredentials400JSONResponse{N400JSONResponse{Message: ret.Message}}, nil - case GetClaims500JSONResponse: - return GetCredentials500JSONResponse{N500JSONResponse{Message: ret.Message}}, nil - default: - log.Error(ctx, "unexpected return type", "type", fmt.Sprintf("%T", ret)) - return GetCredentials500JSONResponse{N500JSONResponse{Message: fmt.Sprintf("unexpected return type: %T", ret)}}, nil - } -} - -// GetClaims is the controller to get multiple claims of a determined identity -// deprecated: use GetCredentials instead -func (s *Server) GetClaims(ctx context.Context, request GetClaimsRequestObject) (GetClaimsResponseObject, error) { - if request.Identifier == "" { - return GetClaims400JSONResponse{N400JSONResponse{"invalid did, cannot be empty"}}, nil - } - - did, err := w3c.ParseDID(request.Identifier) - if err != nil { - return GetClaims400JSONResponse{N400JSONResponse{"invalid did"}}, nil - } - - filter, err := ports.NewClaimsFilter( - request.Params.SchemaHash, - request.Params.SchemaType, - request.Params.Subject, - request.Params.QueryField, - request.Params.QueryValue, - request.Params.Self, - request.Params.Revoked) - if err != nil { - return GetClaims400JSONResponse{N400JSONResponse{err.Error()}}, nil - } - - claims, _, err := s.claimService.GetAll(ctx, *did, filter) - if err != nil && !errors.Is(err, services.ErrClaimNotFound) { - return GetClaims500JSONResponse{N500JSONResponse{"there was an internal error trying to retrieve claims for the requested identifier"}}, nil - } - - w3Claims, err := schema.FromClaimsModelToW3CCredential(claims) - if err != nil { - return GetClaims500JSONResponse{N500JSONResponse{"there was an internal error parsing the claims"}}, nil - } - - return GetClaims200JSONResponse(toGetClaims200Response(w3Claims)), nil -} - -// GetCredentialQrCode returns a GetClaimQrCodeResponseObject that can be used with any QR generator to create a QR and -// scan it with privado.id wallet to accept the claim -func (s *Server) GetCredentialQrCode(ctx context.Context, request GetCredentialQrCodeRequestObject) (GetCredentialQrCodeResponseObject, error) { - resp, err := s.GetClaimQrCode(ctx, GetClaimQrCodeRequestObject(request)) - if err != nil { - return GetCredentialQrCode500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil - } - switch ret := resp.(type) { - case GetClaimQrCode200JSONResponse: - return GetCredentialQrCode200JSONResponse(ret), nil - case GetClaimQrCode400JSONResponse: - return GetCredentialQrCode400JSONResponse{N400JSONResponse{Message: ret.Message}}, nil - case GetClaimQrCode404JSONResponse: - return GetCredentialQrCode404JSONResponse{N404JSONResponse{Message: ret.Message}}, nil - case GetClaimQrCode409JSONResponse: - return GetCredentialQrCode409JSONResponse{N409JSONResponse{Message: ret.Message}}, nil - case GetClaimQrCode500JSONResponse: - return GetCredentialQrCode500JSONResponse{N500JSONResponse{Message: ret.Message}}, nil - default: - log.Error(ctx, "unexpected return type", "type", fmt.Sprintf("%T", ret)) - return GetCredentialQrCode500JSONResponse{N500JSONResponse{Message: fmt.Sprintf("unexpected return type: %T", ret)}}, nil - } -} - -// GetClaimQrCode returns a GetClaimQrCodeResponseObject that can be used with any QR generator to create a QR and -// scan it with polygon wallet to accept the claim -// deprecated - use GetCredentialQrCode instead -func (s *Server) GetClaimQrCode(ctx context.Context, request GetClaimQrCodeRequestObject) (GetClaimQrCodeResponseObject, error) { - if request.Identifier == "" { - return GetClaimQrCode400JSONResponse{N400JSONResponse{"invalid did, cannot be empty"}}, nil - } - - did, err := w3c.ParseDID(request.Identifier) - if err != nil { - return GetClaimQrCode400JSONResponse{N400JSONResponse{"invalid did"}}, nil - } - - if request.Id == "" { - return GetClaimQrCode400JSONResponse{N400JSONResponse{"cannot proceed with an empty claim id"}}, nil - } - - claimID, err := uuid.Parse(request.Id) - if err != nil { - return GetClaimQrCode400JSONResponse{N400JSONResponse{"invalid claim id"}}, nil - } - - claim, err := s.claimService.GetByID(ctx, did, claimID) - if err != nil { - if errors.Is(err, services.ErrClaimNotFound) { - return GetClaimQrCode404JSONResponse{N404JSONResponse{err.Error()}}, nil - } - return GetClaimQrCode500JSONResponse{N500JSONResponse{err.Error()}}, nil - } - - if !claim.ValidProof() { - return GetClaimQrCode409JSONResponse{N409JSONResponse{"State must be published before fetching MTP type credential"}}, nil - } - - return toGetClaimQrCode200JSONResponse(claim, s.cfg.ServerUrl), nil -} - -// GetIdentities is the controller to get identities -func (s *Server) GetIdentities(ctx context.Context, request GetIdentitiesRequestObject) (GetIdentitiesResponseObject, error) { - var response GetIdentities200JSONResponse - var err error - response, err = s.identityService.Get(ctx) - if err != nil { - return GetIdentities500JSONResponse{N500JSONResponse{ - Message: err.Error(), - }}, nil - } - - return response, nil -} - -// Agent is the controller to fetch credentials from mobile -func (s *Server) Agent(ctx context.Context, request AgentRequestObject) (AgentResponseObject, error) { - if request.Body == nil || *request.Body == "" { - log.Debug(ctx, "agent empty request") - return Agent400JSONResponse{N400JSONResponse{"cannot proceed with an empty request"}}, nil - } - - basicMessage, mediatype, err := s.packageManager.Unpack([]byte(*request.Body)) - if err != nil { - log.Debug(ctx, "agent bad request", "err", err, "body", *request.Body) - return Agent400JSONResponse{N400JSONResponse{"cannot proceed with the given request"}}, nil - } - - req, err := ports.NewAgentRequest(basicMessage) - if err != nil { - log.Error(ctx, "agent parsing request", "err", err) - return Agent400JSONResponse{N400JSONResponse{err.Error()}}, nil - } - - agent, err := s.claimService.Agent(ctx, req, mediatype) - if err != nil { - log.Error(ctx, "agent error", "err", err) - return Agent400JSONResponse{N400JSONResponse{err.Error()}}, nil - } - return Agent200JSONResponse{ - Body: agent.Body, - From: agent.From, - Id: agent.ID, - ThreadID: agent.ThreadID, - To: agent.To, - Typ: string(agent.Typ), - Type: string(agent.Type), - }, nil -} - -// PublishIdentityState - publish identity state on chain -func (s *Server) PublishIdentityState(ctx context.Context, request PublishIdentityStateRequestObject) (PublishIdentityStateResponseObject, error) { - did, err := w3c.ParseDID(request.Identifier) - if err != nil { - return PublishIdentityState400JSONResponse{N400JSONResponse{"invalid did"}}, nil - } - - publishedState, err := s.publisherGateway.PublishState(ctx, did) - if err != nil { - if errors.Is(err, gateways.ErrNoStatesToProcess) || errors.Is(err, gateways.ErrStateIsBeingProcessed) { - return PublishIdentityState200JSONResponse{Message: err.Error()}, nil - } - return PublishIdentityState500JSONResponse{N500JSONResponse{err.Error()}}, nil - } - - return PublishIdentityState202JSONResponse{ - ClaimsTreeRoot: publishedState.ClaimsTreeRoot, - RevocationTreeRoot: publishedState.RevocationTreeRoot, - RootOfRoots: publishedState.RootOfRoots, - State: publishedState.State, - TxID: publishedState.TxID, - }, nil -} - -// RetryPublishState - retry to publish the current state if it failed previously. -func (s *Server) RetryPublishState(ctx context.Context, request RetryPublishStateRequestObject) (RetryPublishStateResponseObject, error) { - did, err := w3c.ParseDID(request.Identifier) - if err != nil { - return RetryPublishState400JSONResponse{N400JSONResponse{"invalid did"}}, nil - } - - publishedState, err := s.publisherGateway.RetryPublishState(ctx, did) - if err != nil { - log.Error(ctx, "error retrying the publishing the state", "err", err) - if errors.Is(err, gateways.ErrStateIsBeingProcessed) || errors.Is(err, gateways.ErrNoFailedStatesToProcess) { - return RetryPublishState400JSONResponse{N400JSONResponse{Message: err.Error()}}, nil - } - return RetryPublishState500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil - } - return RetryPublishState202JSONResponse{ - ClaimsTreeRoot: publishedState.ClaimsTreeRoot, - RevocationTreeRoot: publishedState.RevocationTreeRoot, - RootOfRoots: publishedState.RootOfRoots, - State: publishedState.State, - TxID: publishedState.TxID, - }, nil -} - -// GetQrFromStore is the controller to get qr bodies -func (s *Server) GetQrFromStore(ctx context.Context, request GetQrFromStoreRequestObject) (GetQrFromStoreResponseObject, error) { - if request.Params.Id == nil { - log.Warn(ctx, "qr store. Missing id parameter") - return GetQrFromStore400JSONResponse{N400JSONResponse{"id is required"}}, nil - } - body, err := s.qrService.Find(ctx, *request.Params.Id) - if err != nil { - log.Error(ctx, "qr store. Finding qr", "err", err, "id", *request.Params.Id) - return GetQrFromStore500JSONResponse{N500JSONResponse{"error looking for qr body"}}, nil - } - return NewQrContentResponse(body), nil -} - -// GetIdentityDetails is the controller to get identity details -func (s *Server) GetIdentityDetails(ctx context.Context, request GetIdentityDetailsRequestObject) (GetIdentityDetailsResponseObject, error) { - userDID, err := w3c.ParseDID(request.Identifier) - if err != nil { - log.Error(ctx, "get identity details. Parsing did", "err", err) - return GetIdentityDetails400JSONResponse{ - N400JSONResponse{ - Message: "invalid did", - }, - }, err - } - - identity, err := s.identityService.GetByDID(ctx, *userDID) - if err != nil { - log.Error(ctx, "get identity details. Getting identity", "err", err) - return GetIdentityDetails500JSONResponse{ - N500JSONResponse{ - Message: err.Error(), - }, - }, err - } - - if identity.KeyType == string(kms.KeyTypeEthereum) { - did, err := w3c.ParseDID(identity.Identifier) - if err != nil { - log.Error(ctx, "get identity details. Parsing did", "err", err) - return GetIdentityDetails400JSONResponse{N400JSONResponse{Message: "invalid did"}}, nil - } - balance, err := s.accountService.GetBalanceByDID(ctx, did) - if err != nil { - log.Error(ctx, "get identity details. Getting balance", "err", err) - return GetIdentityDetails500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil - } - identity.Balance = balance - } - - response := GetIdentityDetails200JSONResponse{ - Identifier: &identity.Identifier, - State: &IdentityState{ - BlockNumber: identity.State.BlockNumber, - BlockTimestamp: identity.State.BlockTimestamp, - ClaimsTreeRoot: identity.State.ClaimsTreeRoot, - CreatedAt: TimeUTC(identity.State.CreatedAt), - ModifiedAt: TimeUTC(identity.State.ModifiedAt), - PreviousState: identity.State.PreviousState, - RevocationTreeRoot: identity.State.RevocationTreeRoot, - RootOfRoots: identity.State.RootOfRoots, - State: identity.State.State, - Status: string(identity.State.Status), - TxID: identity.State.TxID, - }, - } - - if identity.Address != nil && *identity.Address != "" { - response.Address = identity.Address - } - - if identity.Balance != nil { - response.Balance = common.ToPointer(identity.Balance.String()) - } - - return response, nil -} - // RegisterStatic add method to the mux that are not documented in the API. func RegisterStatic(mux *chi.Mux) { mux.Get("/", documentation) @@ -735,109 +72,6 @@ func RegisterStatic(mux *chi.Mux) { mux.Get("/favicon.ico", favicon) } -func toVerifiableRefreshService(s *RefreshService) *verifiable.RefreshService { - if s == nil { - return nil - } - return &verifiable.RefreshService{ - ID: s.Id, - Type: verifiable.RefreshServiceType(s.Type), - } -} - -func toVerifiableDisplayMethod(s *DisplayMethod) *verifiable.DisplayMethod { - if s == nil { - return nil - } - return &verifiable.DisplayMethod{ - ID: s.Id, - Type: verifiable.DisplayMethodType(s.Type), - } -} - -func toGetClaims200Response(claims []*verifiable.W3CCredential) GetClaimsResponse { - response := make(GetClaims200JSONResponse, len(claims)) - for i := range claims { - response[i] = toGetClaim200Response(claims[i]) - } - - return response -} - -func toGetClaim200Response(claim *verifiable.W3CCredential) GetClaimResponse { - var claimExpiration, claimIssuanceDate *TimeUTC - if claim.Expiration != nil { - claimExpiration = common.ToPointer(TimeUTC(*claim.Expiration)) - } - if claim.IssuanceDate != nil { - claimIssuanceDate = common.ToPointer(TimeUTC(*claim.IssuanceDate)) - } - - var refreshService *RefreshService - if claim.RefreshService != nil { - refreshService = &RefreshService{ - Id: claim.RefreshService.ID, - Type: RefreshServiceType(claim.RefreshService.Type), - } - } - - var displayMethod *DisplayMethod - if claim.DisplayMethod != nil { - displayMethod = &DisplayMethod{ - Id: claim.DisplayMethod.ID, - Type: DisplayMethodType(claim.DisplayMethod.Type), - } - } - - return GetClaimResponse{ - Context: claim.Context, - CredentialSchema: CredentialSchema{ - claim.CredentialSchema.ID, - claim.CredentialSchema.Type, - }, - CredentialStatus: claim.CredentialStatus, - CredentialSubject: claim.CredentialSubject, - ExpirationDate: claimExpiration, - Id: claim.ID, - IssuanceDate: claimIssuanceDate, - Issuer: claim.Issuer, - Proof: claim.Proof, - Type: claim.Type, - RefreshService: refreshService, - DisplayMethod: displayMethod, - } -} - -func toGetClaimQrCode200JSONResponse(claim *domain.Claim, hostURL string) GetClaimQrCode200JSONResponse { - id := uuid.New() - return GetClaimQrCode200JSONResponse{ - Body: struct { - Credentials []struct { - Description string `json:"description"` - Id string `json:"id"` - } `json:"credentials"` - Url string `json:"url"` - }{ - Credentials: []struct { - Description string `json:"description"` - Id string `json:"id"` - }{ - { - Description: claim.SchemaType, - Id: claim.ID.String(), - }, - }, - Url: fmt.Sprintf("%s/v1/agent", strings.TrimSuffix(hostURL, "/")), - }, - From: claim.Issuer, - Id: id.String(), - Thid: id.String(), - To: claim.OtherIdentifier, - Typ: string(packers.MediaTypePlainMessage), - Type: string(protocol.CredentialOfferMessageType), - } -} - func documentation(w http.ResponseWriter, _ *http.Request) { writeFile("api/spec.html", "text/html; charset=UTF-8", w) }