diff --git a/src/common/utils.go b/src/common/utils.go index 81036af..f229cfd 100644 --- a/src/common/utils.go +++ b/src/common/utils.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "github.com/bb-consent/api/src/config" "github.com/microcosm-cc/bluemonday" ) @@ -221,3 +222,11 @@ func CalculateSHA1(data string) (string, error) { return hashHex, err } + +// Http response +func ReturnHTTPResponse(resp interface{}, w http.ResponseWriter) { + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/config/constants.go b/src/config/constants.go index 5b3a248..1f59a15 100644 --- a/src/config/constants.go +++ b/src/config/constants.go @@ -28,6 +28,7 @@ const ( IdpId = "idpId" ApiKeyId = "apiKeyId" IndividualHeaderKey = "X-ConsentBB-IndividualId" + RevisionId = "revisionId" ) // Schemas @@ -58,3 +59,9 @@ const ( Audit = "audit" Onboard = "onboard" ) + +// Data agreement record state +const ( + Unsigned = "unsigned" + Signed = "signed" +) diff --git a/src/database/db.go b/src/database/db.go index f867a76..5af5af9 100644 --- a/src/database/db.go +++ b/src/database/db.go @@ -157,6 +157,21 @@ func Init(config *config.Configuration) error { return err } + err = initCollection("dataAgreementRecords", []string{"id"}, true) + if err != nil { + return err + } + + err = initCollection("signatures", []string{"id"}, true) + if err != nil { + return err + } + + err = initCollection("dataAgreementRecordsHistories", []string{"id"}, true) + if err != nil { + return err + } + return nil } diff --git a/src/v2/dataagreement_record/dataagreement_record.go b/src/v2/dataagreement_record/dataagreement_record.go new file mode 100644 index 0000000..94af708 --- /dev/null +++ b/src/v2/dataagreement_record/dataagreement_record.go @@ -0,0 +1,63 @@ +package dataagreementrecord + +import ( + "net/http" + "strings" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type DataAgreementRecord struct { + Id primitive.ObjectID `json:"id" bson:"_id,omitempty"` + DataAgreementId string `json:"dataAgreementId"` + DataAgreementRevisionId string `json:"dataAgreementRevisionId"` + DataAgreementRevisionHash string `json:"dataAgreementRevisionHash"` + DataAttributes []DataAttributeForDataAgreementRecord `json:"dataAttributes"` + IndividualId string `json:"individualId"` + OptIn bool `json:"optIn"` + State string `json:"state" valid:"required"` + SignatureId string `json:"signatureId"` + OrganisationId string `json:"-"` + IsDeleted bool `json:"-"` +} + +type DataAttributeForDataAgreementRecord struct { + DataAttributeId string `json:"id"` + DataAttributeRevisionId string `json:"dataAttributeRevisionId"` + DataAttributeRevisionHash string `json:"dataAttributeRevisionHash"` + OptIn bool `json:"optIn"` +} + +// DataAgreementRecordError is an error enumeration for create consent record API. +type DataAgreementRecordError int + +const ( + // IndividualIDIsMissingError indicates that the consent record query params is missing. + IndividualIdIsMissingError DataAgreementRecordError = iota + DataAgreementIdIsMissingError + RevisionIdIsMissingError +) + +// Error returns the string representation of the error. +func (e DataAgreementRecordError) Error() string { + switch e { + case IndividualIdIsMissingError: + return "Query param individualId is missing!" + case DataAgreementIdIsMissingError: + return "Query param dataAgreementId is missing!" + case RevisionIdIsMissingError: + return "Query param revisionId is missing!" + default: + return "Unknown error!" + } +} + +// ParseQueryParams +func ParseQueryParams(r *http.Request, paramName string, errorType DataAgreementRecordError) (paramValue string, err error) { + query := r.URL.Query() + values, ok := query[paramName] + if ok && len(strings.TrimSpace(values[0])) > 0 { + return values[0], nil + } + return "", errorType +} diff --git a/src/v2/dataagreement_record/db.go b/src/v2/dataagreement_record/db.go new file mode 100644 index 0000000..73638ea --- /dev/null +++ b/src/v2/dataagreement_record/db.go @@ -0,0 +1,91 @@ +package dataagreementrecord + +import ( + "context" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/database" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +func Collection() *mongo.Collection { + return database.DB.Client.Database(database.DB.Name).Collection("dataAgreementRecords") +} + +type DataAgreementRecordRepository struct { + DefaultFilter bson.M +} + +// Init +func (darRepo *DataAgreementRecordRepository) Init(organisationId string) { + darRepo.DefaultFilter = bson.M{"organisationid": organisationId, "isdeleted": false} +} + +// Add Adds the data agreement record to the db +func (darRepo *DataAgreementRecordRepository) Add(dataAgreementRecord DataAgreementRecord) (DataAgreementRecord, error) { + + _, err := Collection().InsertOne(context.TODO(), dataAgreementRecord) + if err != nil { + return DataAgreementRecord{}, err + } + + return dataAgreementRecord, nil +} + +// Get Gets a single data agreement record +func (darRepo *DataAgreementRecordRepository) Get(dataAgreementRecordID string) (DataAgreementRecord, error) { + dataAgreementRecordId, err := primitive.ObjectIDFromHex(dataAgreementRecordID) + if err != nil { + return DataAgreementRecord{}, err + } + + filter := common.CombineFilters(darRepo.DefaultFilter, bson.M{"_id": dataAgreementRecordId}) + + var result DataAgreementRecord + err = Collection().FindOne(context.TODO(), filter).Decode(&result) + + return result, err +} + +// Update Updates the data agreement record +func (darRepo *DataAgreementRecordRepository) Update(dataAgreementRecord DataAgreementRecord) (DataAgreementRecord, error) { + + filter := common.CombineFilters(darRepo.DefaultFilter, bson.M{"_id": dataAgreementRecord.Id}) + update := bson.M{"$set": dataAgreementRecord} + + _, err := Collection().UpdateOne(context.TODO(), filter, update) + if err != nil { + return dataAgreementRecord, err + } + return dataAgreementRecord, err +} + +// Get Gets a single data agreement record by data agreement id and individual id +func (darRepo *DataAgreementRecordRepository) GetByDataAgreementIdandIndividualId(dataAgreementId string, individualId string) (DataAgreementRecord, error) { + + filter := common.CombineFilters(darRepo.DefaultFilter, bson.M{"individualid": individualId, "dataagreementid": dataAgreementId}) + + var result DataAgreementRecord + err := Collection().FindOne(context.TODO(), filter).Decode(&result) + + return result, err +} + +// Deletes all the data agreement records of individual +func (darRepo *DataAgreementRecordRepository) DeleteAllRecordsForIndividual(individualId string) error { + + filter := common.CombineFilters(darRepo.DefaultFilter, bson.M{"individualid": individualId}) + + // Update to set IsDeleted to true + update := bson.M{ + "$set": bson.M{ + "isdeleted": true, + }, + } + + _, err := Collection().UpdateMany(context.TODO(), filter, update) + + return err +} diff --git a/src/v2/dataagreement_record_history/dataagreement_record_history.go b/src/v2/dataagreement_record_history/dataagreement_record_history.go new file mode 100644 index 0000000..00f554d --- /dev/null +++ b/src/v2/dataagreement_record_history/dataagreement_record_history.go @@ -0,0 +1,51 @@ +package dataagreementrecordhistory + +import ( + "fmt" + "log" + + "github.com/bb-consent/api/src/org" + "github.com/bb-consent/api/src/v2/dataagreement" +) + +// DataAgreementRecordsHistory +type DataAgreementRecordsHistory struct { + Id string `json:"id" bson:"_id,omitempty"` + OrganisationId string `json:"organisationId"` + DataAgreementId string `json:"dataAgreementId"` + Log string `json:"log"` + Timestamp string `json:"timestamp"` +} + +func DataAgreementRecordHistoryAdd(darH DataAgreementRecordsHistory, optIn bool) error { + o, err := org.Get(darH.OrganisationId) + if err != nil { + return err + } + // Repository + darepo := dataagreement.DataAgreementRepository{} + darepo.Init(darH.OrganisationId) + + dataAgreement, err := darepo.Get(darH.DataAgreementId) + if err != nil { + return err + } + + if optIn { + value := "Allow" + darH.Log = fmt.Sprintf("Updated consent value to <%s> for the purpose <%s> in organization <%s>", + value, dataAgreement.Purpose, o.Name) + } else { + value := "Disallow" + darH.Log = fmt.Sprintf("Updated consent value to <%s> for the purpose <%s> in organization <%s>", + value, dataAgreement.Purpose, o.Name) + } + log.Printf("The log is: %s", darH.Log) + + _, err = Add(darH) + if err != nil { + return err + } + return nil + +} diff --git a/src/v2/dataagreement_record_history/db.go b/src/v2/dataagreement_record_history/db.go new file mode 100644 index 0000000..68b78f6 --- /dev/null +++ b/src/v2/dataagreement_record_history/db.go @@ -0,0 +1,25 @@ +package dataagreementrecordhistory + +import ( + "context" + "time" + + "github.com/bb-consent/api/src/database" + "go.mongodb.org/mongo-driver/mongo" +) + +func Collection() *mongo.Collection { + return database.DB.Client.Database(database.DB.Name).Collection("dataAgreementRecordsHistories") +} + +// Add Adds the Data Agreement Records History to the db +func Add(dataAgreementRecordsHistory DataAgreementRecordsHistory) (DataAgreementRecordsHistory, error) { + + dataAgreementRecordsHistory.Timestamp = time.Now().UTC().Format("2006-01-02T15:04:05Z") + _, err := Collection().InsertOne(context.TODO(), dataAgreementRecordsHistory) + if err != nil { + return DataAgreementRecordsHistory{}, err + } + + return dataAgreementRecordsHistory, nil +} diff --git a/src/v2/handler/service/service_create_blank_signature_object.go b/src/v2/handler/service/service_create_blank_signature_object.go new file mode 100644 index 0000000..c1033c6 --- /dev/null +++ b/src/v2/handler/service/service_create_blank_signature_object.go @@ -0,0 +1,55 @@ +package service + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" + "github.com/bb-consent/api/src/v2/revision" + "github.com/bb-consent/api/src/v2/signature" + "github.com/gorilla/mux" +) + +type createBlankSignatureResp struct { + Signature signature.Signature `json:"signature"` +} + +func ServiceCreateBlankSignature(w http.ResponseWriter, r *http.Request) { + + // Headers + organisationId := common.Sanitize(r.Header.Get(config.OrganizationId)) + + dataAgreementRecordId := common.Sanitize(mux.Vars(r)[config.DataAgreementRecordId]) + + // Repository + darRepo := daRecord.DataAgreementRecordRepository{} + darRepo.Init(organisationId) + + // Get latest revision for data agreement record + daRecordRevision, err := revision.GetLatestByObjectId(dataAgreementRecordId) + if err != nil { + m := fmt.Sprintf("Failed to fetch revision for data agreement record: %v", dataAgreementRecordId) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + // create signature for data agreement record + toBeCreatedSignature, err := signature.CreateSignatureForObject("revision", daRecordRevision.Id.Hex(), false, daRecordRevision, false, signature.Signature{}) + if err != nil { + m := fmt.Sprintf("Failed to create signature for data agreement record: %v", dataAgreementRecordId) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // Creating response + resp := createBlankSignatureResp{ + Signature: toBeCreatedSignature, + } + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/v2/handler/service/service_create_dataagreement_record.go b/src/v2/handler/service/service_create_dataagreement_record.go new file mode 100644 index 0000000..76b7a81 --- /dev/null +++ b/src/v2/handler/service/service_create_dataagreement_record.go @@ -0,0 +1,161 @@ +package service + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" + daRecordHistory "github.com/bb-consent/api/src/v2/dataagreement_record_history" + "github.com/bb-consent/api/src/v2/dataattribute" + "github.com/bb-consent/api/src/v2/revision" + "github.com/gorilla/mux" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +// getDataAttributesWithRevisionForCreateDataAgreementRecord +func getDataAttributesWithRevisionForCreateDataAgreementRecord(dataAttributes []dataattribute.DataAttribute) ([]daRecord.DataAttributeForDataAgreementRecord, error) { + var dataAttributesWithRevision []daRecord.DataAttributeForDataAgreementRecord + + for _, da := range dataAttributes { + var dataAttributeWithRevision daRecord.DataAttributeForDataAgreementRecord + + dataAttributeWithRevision.DataAttributeId = da.Id.Hex() + revisionForDataAttibute, err := revision.GetLatestByDataAttributeId(da.Id.Hex()) + if err != nil { + return dataAttributesWithRevision, err + } + dataAttributeWithRevision.DataAttributeRevisionId = revisionForDataAttibute.Id.Hex() + dataAttributeWithRevision.DataAttributeRevisionHash = revisionForDataAttibute.SerializedHash + dataAttributeWithRevision.OptIn = true + + dataAttributesWithRevision = append(dataAttributesWithRevision, dataAttributeWithRevision) + } + return dataAttributesWithRevision, nil +} + +// createDataAgreementRecord +func createDataAgreementRecord(dataAgreementId string, rev revision.Revision, individualId string, dataAttributesWithRevision []daRecord.DataAttributeForDataAgreementRecord) daRecord.DataAgreementRecord { + var newDaRecord daRecord.DataAgreementRecord + + newDaRecord.Id = primitive.NewObjectID() + newDaRecord.DataAgreementId = dataAgreementId + newDaRecord.DataAgreementRevisionHash = rev.SerializedHash + newDaRecord.DataAgreementRevisionId = rev.Id.Hex() + newDaRecord.DataAttributes = dataAttributesWithRevision + newDaRecord.IndividualId = individualId + newDaRecord.OptIn = true + newDaRecord.State = config.Unsigned + + return newDaRecord +} + +type createDataAgreementRecordResp struct { + DataAgreementRecord daRecord.DataAgreementRecord `json:"dataAgreementRecord"` + Revision revision.Revision `json:"revision"` +} + +// ServiceCreateDataAgreementRecord +func ServiceCreateDataAgreementRecord(w http.ResponseWriter, r *http.Request) { + // Headers + organisationId := common.Sanitize(r.Header.Get(config.OrganizationId)) + individualId := common.Sanitize(r.Header.Get(config.IndividualHeaderKey)) + + dataAgreementId := common.Sanitize(mux.Vars(r)[config.DataAgreementId]) + + revisionId, err := daRecord.ParseQueryParams(r, config.RevisionId, daRecord.RevisionIdIsMissingError) + revisionId = common.Sanitize(revisionId) + var rev revision.Revision + + // If revision id is missing, fetch latest revision + if err != nil && errors.Is(err, daRecord.RevisionIdIsMissingError) { + rev, err = revision.GetLatestByObjectId(dataAgreementId) + if err != nil { + m := fmt.Sprintf("Failed to fetch revision for data agreement: %v", dataAgreementId) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + } else { + // fetch revision based on id and schema name + rev, err = revision.GetByRevisionIdAndSchema(revisionId, config.DataAgreement) + if err != nil { + m := fmt.Sprintf("Failed to fetch revision: %v", revisionId) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + } + + // Repository + dataAttributeRepo := dataattribute.DataAttributeRepository{} + dataAttributeRepo.Init(organisationId) + + // Fetch data attributes of data agreement from db + dataAttributes, err := dataAttributeRepo.GetDataAttributesByDataAgreementId(dataAgreementId) + if err != nil { + m := fmt.Sprintf("Failed to fetch data attributes for data agreement: %v", dataAgreementId) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // Fetch revisions for data attributes + dataAttributesWithRevision, err := getDataAttributesWithRevisionForCreateDataAgreementRecord(dataAttributes) + if err != nil { + m := "Failed to fetch revisions for data attributes" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // create new data agreement record + newDaRecord := createDataAgreementRecord(dataAgreementId, rev, individualId, dataAttributesWithRevision) + newDaRecord.OrganisationId = organisationId + + // Create new revision + newRevision, err := revision.CreateRevisionForDataAgreementRecord(newDaRecord, individualId) + if err != nil { + m := "Failed to create revision for new data agreement record" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + // Repository + darRepo := daRecord.DataAgreementRecordRepository{} + darRepo.Init(organisationId) + + savedDaRecord, err := darRepo.Add(newDaRecord) + if err != nil { + m := "Failed to create new data agreement record" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // Save the revision to db + savedRevision, err := revision.Add(newRevision) + if err != nil { + m := fmt.Sprintf("Failed to create new revision: %v", newRevision.Id) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + // Add data agreement record history + darH := daRecordHistory.DataAgreementRecordsHistory{} + darH.DataAgreementId = dataAgreementId + darH.OrganisationId = organisationId + err = daRecordHistory.DataAgreementRecordHistoryAdd(darH, savedDaRecord.OptIn) + if err != nil { + m := "Failed to add data agreement record history" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // response + resp := createDataAgreementRecordResp{ + DataAgreementRecord: savedDaRecord, + Revision: savedRevision, + } + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/v2/handler/service/service_create_draft_consent_record.go b/src/v2/handler/service/service_create_draft_consent_record.go new file mode 100644 index 0000000..2da7c03 --- /dev/null +++ b/src/v2/handler/service/service_create_draft_consent_record.go @@ -0,0 +1,127 @@ +package service + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" + "github.com/bb-consent/api/src/v2/dataattribute" + "github.com/bb-consent/api/src/v2/revision" + "github.com/bb-consent/api/src/v2/signature" +) + +// getDataAttributesWithRevision +func getDataAttributesWithRevision(dataAttributes []dataattribute.DataAttribute) ([]daRecord.DataAttributeForDataAgreementRecord, error) { + var dataAttributesWithRevision []daRecord.DataAttributeForDataAgreementRecord + + for _, da := range dataAttributes { + var dataAttributeWithRevision daRecord.DataAttributeForDataAgreementRecord + + dataAttributeWithRevision.DataAttributeId = da.Id.Hex() + revisionForDataAttibute, err := revision.GetLatestByDataAttributeId(da.Id.Hex()) + if err != nil { + return dataAttributesWithRevision, err + } + dataAttributeWithRevision.DataAttributeRevisionId = revisionForDataAttibute.Id.Hex() + dataAttributeWithRevision.DataAttributeRevisionHash = revisionForDataAttibute.SerializedHash + dataAttributeWithRevision.OptIn = true + + dataAttributesWithRevision = append(dataAttributesWithRevision, dataAttributeWithRevision) + } + return dataAttributesWithRevision, nil +} + +// createDraftDataAgreementRecord +func createDraftDataAgreementRecord(dataAgreementId string, rev revision.Revision, individualId string, dataAttributesWithRevision []daRecord.DataAttributeForDataAgreementRecord) daRecord.DataAgreementRecord { + var newDaRecord daRecord.DataAgreementRecord + + newDaRecord.DataAgreementId = dataAgreementId + newDaRecord.DataAgreementRevisionHash = rev.SerializedHash + newDaRecord.DataAgreementRevisionId = rev.Id.Hex() + newDaRecord.DataAttributes = dataAttributesWithRevision + newDaRecord.IndividualId = individualId + newDaRecord.OptIn = true + newDaRecord.State = config.Unsigned + + return newDaRecord +} + +type draftDataAgreementRecordResp struct { + DataAgreementRecord daRecord.DataAgreementRecord `json:"dataAgreementRecord"` + Signature signature.Signature `json:"signature"` +} + +func ServiceCreateDraftConsentRecord(w http.ResponseWriter, r *http.Request) { + // Headers + organisationId := common.Sanitize(r.Header.Get(config.OrganizationId)) + individualId := common.Sanitize(r.Header.Get(config.IndividualHeaderKey)) + + // Parse query params + dataAgreementId, err := daRecord.ParseQueryParams(r, config.DataAgreementId, daRecord.DataAgreementIdIsMissingError) + dataAgreementId = common.Sanitize(dataAgreementId) + if err != nil && errors.Is(err, daRecord.DataAgreementIdIsMissingError) { + m := "Query param dataAgreementId is required" + common.HandleErrorV2(w, http.StatusBadRequest, m, err) + return + } + + revisionId, err := daRecord.ParseQueryParams(r, config.RevisionId, daRecord.RevisionIdIsMissingError) + revisionId = common.Sanitize(revisionId) + var rev revision.Revision + + // If revision id is missing, fetch latest revision + if err != nil && errors.Is(err, daRecord.RevisionIdIsMissingError) { + rev, err = revision.GetLatestByDataAgreementId(dataAgreementId) + if err != nil { + m := fmt.Sprintf("Failed to fetch revision: %v", revisionId) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + } else { + // fetch revision based on id and schema name + rev, err = revision.GetByRevisionIdAndSchema(revisionId, config.DataAgreement) + if err != nil { + m := fmt.Sprintf("Failed to fetch revision: %v", revisionId) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + } + + // Repository + dataAttributeRepo := dataattribute.DataAttributeRepository{} + dataAttributeRepo.Init(organisationId) + + // Fetch data attributes of data agreement from db + dataAttributes, err := dataAttributeRepo.GetDataAttributesByDataAgreementId(dataAgreementId) + if err != nil { + m := fmt.Sprintf("Failed to fetch data attributes for data agreement: %v", dataAgreementId) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // Fecth revisions for data attributes + dataAttributesWithRevision, err := getDataAttributesWithRevision(dataAttributes) + if err != nil { + m := "Failed to fetch revisions for data attributes" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // create new draft data agreement record + newDaRecord := createDraftDataAgreementRecord(dataAgreementId, rev, individualId, dataAttributesWithRevision) + + // response + resp := draftDataAgreementRecordResp{ + DataAgreementRecord: newDaRecord, + Signature: signature.Signature{}, + } + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/v2/handler/service/service_create_paired_dataagreement_record.go.go b/src/v2/handler/service/service_create_paired_dataagreement_record.go.go new file mode 100644 index 0000000..dc5b97c --- /dev/null +++ b/src/v2/handler/service/service_create_paired_dataagreement_record.go.go @@ -0,0 +1,109 @@ +package service + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" + daRecordHistory "github.com/bb-consent/api/src/v2/dataagreement_record_history" + "github.com/bb-consent/api/src/v2/revision" + "github.com/bb-consent/api/src/v2/signature" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type createPairedDataAgreementRecordReq struct { + DataAgreementRecord daRecord.DataAgreementRecord `json:"dataAgreementRecord" valid:"required"` + Signature signature.Signature `json:"signature" valid:"required"` +} + +type createPairedDataAgreementRecordResp struct { + DataAgreementRecord daRecord.DataAgreementRecord `json:"dataAgreementRecord"` + Revision revision.Revision `json:"revision"` + Signature signature.Signature `json:"signature"` +} + +func ServiceCreatePairedDataAgreementRecord(w http.ResponseWriter, r *http.Request) { + // Headers + organisationId := common.Sanitize(r.Header.Get(config.OrganizationId)) + individualId := common.Sanitize(r.Header.Get(config.IndividualHeaderKey)) + + // Request body + var dataAgreementRecordReq createPairedDataAgreementRecordReq + b, _ := io.ReadAll(r.Body) + defer r.Body.Close() + json.Unmarshal(b, &dataAgreementRecordReq) + + dataAgreementRecord := dataAgreementRecordReq.DataAgreementRecord + currentSignature := dataAgreementRecordReq.Signature + + dataAgreementRecord.Id = primitive.NewObjectID() + + newRecordRevision, err := revision.CreateRevisionForDataAgreementRecord(dataAgreementRecord, individualId) + if err != nil { + m := fmt.Sprintf("Failed to create new revision for dataAgreementRecord: %v", dataAgreementRecord.Id.Hex()) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + // create signature for data agreement record + toBeCreatedSignature, err := signature.CreateSignatureForObject("revision", newRecordRevision.Id.Hex(), false, newRecordRevision, true, currentSignature) + if err != nil { + m := fmt.Sprintf("Failed to create signature for data agreement record: %v", dataAgreementRecord.Id.Hex()) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + toBeCreatedSignature.Id = primitive.NewObjectID() + + // Repository + darRepo := daRecord.DataAgreementRecordRepository{} + darRepo.Init(organisationId) + + dataAgreementRecord.SignatureId = toBeCreatedSignature.Id.Hex() + + savedDataAgreementRecord, err := darRepo.Add(dataAgreementRecord) + if err != nil { + m := fmt.Sprintf("Failed to update paired data agreement record: %v", savedDataAgreementRecord.Id) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + savedRevision, err := revision.Add(newRecordRevision) + if err != nil { + m := fmt.Sprintf("Failed to add revision for data agreement record: %v", savedDataAgreementRecord.Id) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + savedSignature, err := signature.Add(toBeCreatedSignature) + if err != nil { + m := fmt.Sprintf("Failed to add signature for data agreement record: %v", savedDataAgreementRecord.Id) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // Add data agreement record history + darH := daRecordHistory.DataAgreementRecordsHistory{} + darH.DataAgreementId = dataAgreementRecord.DataAgreementId + darH.OrganisationId = organisationId + err = daRecordHistory.DataAgreementRecordHistoryAdd(darH, savedDataAgreementRecord.OptIn) + if err != nil { + m := "Failed to add data agreement record history" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + // response + resp := createPairedDataAgreementRecordResp{ + DataAgreementRecord: savedDataAgreementRecord, + Revision: savedRevision, + Signature: savedSignature, + } + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) + +} diff --git a/src/v2/handler/service/service_delete_individual_dataagreement_records.go b/src/v2/handler/service/service_delete_individual_dataagreement_records.go new file mode 100644 index 0000000..06f1ced --- /dev/null +++ b/src/v2/handler/service/service_delete_individual_dataagreement_records.go @@ -0,0 +1,31 @@ +package service + +import ( + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" +) + +func ServiceDeleteIndividualDataAgreementRecords(w http.ResponseWriter, r *http.Request) { + + // Headers + organisationId := common.Sanitize(r.Header.Get(config.OrganizationId)) + individualId := common.Sanitize(r.Header.Get(config.IndividualHeaderKey)) + + // Repository + darRepo := daRecord.DataAgreementRecordRepository{} + darRepo.Init(organisationId) + + err := darRepo.DeleteAllRecordsForIndividual(individualId) + if err != nil { + m := "Failed to delete data agreement records for individual" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + +} diff --git a/src/v2/handler/service/service_fetch_data_agreement_records.go b/src/v2/handler/service/service_fetch_data_agreement_records.go new file mode 100644 index 0000000..ffc49d5 --- /dev/null +++ b/src/v2/handler/service/service_fetch_data_agreement_records.go @@ -0,0 +1,64 @@ +package service + +import ( + "context" + "errors" + "log" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" + "github.com/bb-consent/api/src/v2/paginate" +) + +type fetchDataAgreementRecordsResp struct { + DataAgreementRecords interface{} `json:"dataAgreementRecords"` + Pagination paginate.Pagination `json:"pagination"` +} + +func ServiceFetchDataAgreementRecords(w http.ResponseWriter, r *http.Request) { + + // Headers + organisationId := common.Sanitize(r.Header.Get(config.OrganizationId)) + + // Query params + offset, limit := paginate.ParsePaginationQueryParams(r) + log.Printf("Offset: %v and limit: %v\n", offset, limit) + + // Repository + darRepo := daRecord.DataAgreementRecordRepository{} + darRepo.Init(organisationId) + + // Return all data agreement records + var dataAgreementRecords []daRecord.DataAgreementRecord + query := paginate.PaginateDBObjectsQuery{ + Filter: darRepo.DefaultFilter, + Collection: daRecord.Collection(), + Context: context.Background(), + Limit: limit, + Offset: offset, + } + var resp fetchDataAgreementRecordsResp + result, err := paginate.PaginateDBObjects(query, &dataAgreementRecords) + if err != nil { + if errors.Is(err, paginate.EmptyDBError) { + emptyDataAgreementRecords := make([]interface{}, 0) + resp = fetchDataAgreementRecordsResp{ + DataAgreementRecords: emptyDataAgreementRecords, + Pagination: result.Pagination, + } + common.ReturnHTTPResponse(resp, w) + return + } + m := "Failed to paginate data agreement records" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + + } + resp = fetchDataAgreementRecordsResp{ + DataAgreementRecords: result.Items, + Pagination: result.Pagination, + } + common.ReturnHTTPResponse(resp, w) +} diff --git a/src/v2/handler/service/service_fetch_dataagreement_records_history.go b/src/v2/handler/service/service_fetch_dataagreement_records_history.go new file mode 100644 index 0000000..fb2e865 --- /dev/null +++ b/src/v2/handler/service/service_fetch_dataagreement_records_history.go @@ -0,0 +1,56 @@ +package service + +import ( + "context" + "errors" + "log" + "net/http" + + "github.com/bb-consent/api/src/common" + daRecordHistory "github.com/bb-consent/api/src/v2/dataagreement_record_history" + "github.com/bb-consent/api/src/v2/paginate" + "go.mongodb.org/mongo-driver/bson" +) + +type listDataAgreementRecordHistory struct { + DataAgreementRecordHistory interface{} `json:"dataAgreementRecordHistory"` + Pagination paginate.Pagination `json:"pagination"` +} + +func ServiceFetchRecordsHistory(w http.ResponseWriter, r *http.Request) { + // Query params + offset, limit := paginate.ParsePaginationQueryParams(r) + log.Printf("Offset: %v and limit: %v\n", offset, limit) + + // Return all data agreement record histories + var darH []daRecordHistory.DataAgreementRecordsHistory + query := paginate.PaginateDBObjectsQuery{ + Filter: bson.M{}, + Collection: daRecordHistory.Collection(), + Context: context.Background(), + Limit: limit, + Offset: offset, + } + var resp listDataAgreementRecordHistory + result, err := paginate.PaginateDBObjects(query, &darH) + if err != nil { + if errors.Is(err, paginate.EmptyDBError) { + emptyDarH := make([]interface{}, 0) + resp = listDataAgreementRecordHistory{ + DataAgreementRecordHistory: emptyDarH, + Pagination: result.Pagination, + } + common.ReturnHTTPResponse(resp, w) + return + } + m := "Failed to paginate data agreement record histories" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + + } + resp = listDataAgreementRecordHistory{ + DataAgreementRecordHistory: result.Items, + Pagination: result.Pagination, + } + common.ReturnHTTPResponse(resp, w) +} diff --git a/src/v2/handler/service/service_fetch_individual_dataagreement_records.go b/src/v2/handler/service/service_fetch_individual_dataagreement_records.go new file mode 100644 index 0000000..4175bb8 --- /dev/null +++ b/src/v2/handler/service/service_fetch_individual_dataagreement_records.go @@ -0,0 +1,66 @@ +package service + +import ( + "context" + "errors" + "log" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" + "github.com/bb-consent/api/src/v2/paginate" + "go.mongodb.org/mongo-driver/bson" +) + +type vFetchIndividualDataAgreementRecordsResp struct { + DataAgreementRecords interface{} `json:"dataAgreementRecords"` + Pagination paginate.Pagination `json:"pagination"` +} + +func ServiceFetchIndividualDataAgreementRecords(w http.ResponseWriter, r *http.Request) { + + // Headers + organisationId := common.Sanitize(r.Header.Get(config.OrganizationId)) + individualId := common.Sanitize(r.Header.Get(config.IndividualHeaderKey)) + + // Query params + offset, limit := paginate.ParsePaginationQueryParams(r) + log.Printf("Offset: %v and limit: %v\n", offset, limit) + + // Repository + darRepo := daRecord.DataAgreementRecordRepository{} + darRepo.Init(organisationId) + + // Return all data agreement records + var dataAgreementRecords []daRecord.DataAgreementRecord + query := paginate.PaginateDBObjectsQuery{ + Filter: common.CombineFilters(darRepo.DefaultFilter, bson.M{"individualid": individualId}), + Collection: daRecord.Collection(), + Context: context.Background(), + Limit: limit, + Offset: offset, + } + var resp vFetchIndividualDataAgreementRecordsResp + result, err := paginate.PaginateDBObjects(query, &dataAgreementRecords) + if err != nil { + if errors.Is(err, paginate.EmptyDBError) { + emptyDataAgreementRecords := make([]interface{}, 0) + resp = vFetchIndividualDataAgreementRecordsResp{ + DataAgreementRecords: emptyDataAgreementRecords, + Pagination: result.Pagination, + } + common.ReturnHTTPResponse(resp, w) + return + } + m := "Failed to paginate data agreement records" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + + } + resp = vFetchIndividualDataAgreementRecordsResp{ + DataAgreementRecords: result.Items, + Pagination: result.Pagination, + } + common.ReturnHTTPResponse(resp, w) +} diff --git a/src/v2/handler/service/service_fetch_records_for_data_agreement.go b/src/v2/handler/service/service_fetch_records_for_data_agreement.go new file mode 100644 index 0000000..fe8c023 --- /dev/null +++ b/src/v2/handler/service/service_fetch_records_for_data_agreement.go @@ -0,0 +1,67 @@ +package service + +import ( + "context" + "errors" + "log" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" + "github.com/bb-consent/api/src/v2/paginate" + "github.com/gorilla/mux" + "go.mongodb.org/mongo-driver/bson" +) + +type fetchRecordsForDataAgreementResp struct { + DataAgreementRecords interface{} `json:"dataAgreementRecords"` + Pagination paginate.Pagination `json:"pagination"` +} + +func ServiceFetchRecordsForDataAgreement(w http.ResponseWriter, r *http.Request) { + + // Headers + organisationId := common.Sanitize(r.Header.Get(config.OrganizationId)) + dataAgreementId := common.Sanitize(mux.Vars(r)[config.DataAgreementId]) + + // Query params + offset, limit := paginate.ParsePaginationQueryParams(r) + log.Printf("Offset: %v and limit: %v\n", offset, limit) + + // Repository + darRepo := daRecord.DataAgreementRecordRepository{} + darRepo.Init(organisationId) + + // Return all data agreement records + var dataAgreementRecords []daRecord.DataAgreementRecord + query := paginate.PaginateDBObjectsQuery{ + Filter: common.CombineFilters(darRepo.DefaultFilter, bson.M{"dataagreementid": dataAgreementId}), + Collection: daRecord.Collection(), + Context: context.Background(), + Limit: limit, + Offset: offset, + } + var resp fetchRecordsForDataAgreementResp + result, err := paginate.PaginateDBObjects(query, &dataAgreementRecords) + if err != nil { + if errors.Is(err, paginate.EmptyDBError) { + emptyDataAgreementRecords := make([]interface{}, 0) + resp = fetchRecordsForDataAgreementResp{ + DataAgreementRecords: emptyDataAgreementRecords, + Pagination: result.Pagination, + } + common.ReturnHTTPResponse(resp, w) + return + } + m := "Failed to paginate data agreement records" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + + } + resp = fetchRecordsForDataAgreementResp{ + DataAgreementRecords: result.Items, + Pagination: result.Pagination, + } + common.ReturnHTTPResponse(resp, w) +} diff --git a/src/v2/handler/service/service_read_data_agreement_record.go b/src/v2/handler/service/service_read_data_agreement_record.go new file mode 100644 index 0000000..13408c3 --- /dev/null +++ b/src/v2/handler/service/service_read_data_agreement_record.go @@ -0,0 +1,43 @@ +package service + +import ( + "encoding/json" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" + "github.com/gorilla/mux" +) + +type readDataAgreementRecordResp struct { + DataAgreementRecord daRecord.DataAgreementRecord `json:"dataAgreementRecord"` +} + +func ServiceReadDataAgreementRecord(w http.ResponseWriter, r *http.Request) { + + // Headers + organisationId := common.Sanitize(r.Header.Get(config.OrganizationId)) + individualId := common.Sanitize(r.Header.Get(config.IndividualHeaderKey)) + + dataAgreementId := common.Sanitize(mux.Vars(r)[config.DataAgreementId]) + + // Repository + darRepo := daRecord.DataAgreementRecordRepository{} + darRepo.Init(organisationId) + + daRecord, err := darRepo.GetByDataAgreementIdandIndividualId(dataAgreementId, individualId) + if err != nil { + m := "Failed to fetch data agreement record" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + resp := readDataAgreementRecordResp{ + DataAgreementRecord: daRecord, + } + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/v2/handler/service/service_update_dataagreement_record.go b/src/v2/handler/service/service_update_dataagreement_record.go new file mode 100644 index 0000000..9df98a2 --- /dev/null +++ b/src/v2/handler/service/service_update_dataagreement_record.go @@ -0,0 +1,135 @@ +package service + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/asaskevich/govalidator" + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" + daRecordHistory "github.com/bb-consent/api/src/v2/dataagreement_record_history" + "github.com/bb-consent/api/src/v2/revision" + "github.com/gorilla/mux" +) + +// updateOptInOfDataAttributes +func updateOptInOfDataAttributes(daR daRecord.DataAgreementRecord) daRecord.DataAgreementRecord { + var dataAttributes []daRecord.DataAttributeForDataAgreementRecord + for _, da := range daR.DataAttributes { + da.OptIn = daR.OptIn + dataAttributes = append(dataAttributes, da) + } + daR.DataAttributes = dataAttributes + return daR +} + +type updateDataAgreementRecordResp struct { + DataAgreementRecord daRecord.DataAgreementRecord `json:"dataAgreementRecord"` + Revision revision.Revision `json:"revision"` +} +type updateDataAgreementRecordReq struct { + OptIn bool `json:"optIn"` +} + +func ServiceUpdateDataAgreementRecord(w http.ResponseWriter, r *http.Request) { + // Headers + organisationId := common.Sanitize(r.Header.Get(config.OrganizationId)) + individualId := common.Sanitize(r.Header.Get(config.IndividualHeaderKey)) + + dataAgreementRecordId := common.Sanitize(mux.Vars(r)[config.DataAgreementRecordId]) + + // Parse query params + dataAgreementId, err := daRecord.ParseQueryParams(r, config.DataAgreementId, daRecord.DataAgreementIdIsMissingError) + dataAgreementId = common.Sanitize(dataAgreementId) + if err != nil && errors.Is(err, daRecord.DataAgreementIdIsMissingError) { + m := "Query param dataAgreementId is required" + common.HandleErrorV2(w, http.StatusBadRequest, m, err) + return + } + + // Request body + var dataAgreementRecordReq updateDataAgreementRecordReq + b, _ := io.ReadAll(r.Body) + defer r.Body.Close() + json.Unmarshal(b, &dataAgreementRecordReq) + + // validating request payload + valid, err := govalidator.ValidateStruct(dataAgreementRecordReq) + if !valid { + m := fmt.Sprintf("Failed to validate request body: %v", dataAgreementRecordId) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + // Repository + darRepo := daRecord.DataAgreementRecordRepository{} + darRepo.Init(organisationId) + + toBeUpdatedDaRecord, err := darRepo.Get(dataAgreementRecordId) + if err != nil { + m := "Failed to fetch data agreement record" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + if toBeUpdatedDaRecord.OptIn == dataAgreementRecordReq.OptIn { + m := "Data agreement record opt in is same as provided value" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + toBeUpdatedDaRecord.OptIn = dataAgreementRecordReq.OptIn + toBeUpdatedDaRecord = updateOptInOfDataAttributes(toBeUpdatedDaRecord) + + currentDataAgreementRecordRevision, err := revision.GetLatestByObjectId(dataAgreementRecordId) + if err != nil { + m := fmt.Sprintf("Failed to fetch latest revision for data agreement record: %v", dataAgreementRecordId) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // Create new revision + newRevision, err := revision.UpdateRevisionForDataAgreementRecord(toBeUpdatedDaRecord, ¤tDataAgreementRecordRevision, individualId) + if err != nil { + m := "Failed to create revision for new data agreement record" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + savedDaRecord, err := darRepo.Update(toBeUpdatedDaRecord) + if err != nil { + m := "Failed to update new data agreement record" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // Save the revision to db + savedRevision, err := revision.Add(newRevision) + if err != nil { + m := fmt.Sprintf("Failed to create new revision: %v", newRevision.Id) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + // Add data agreement record history + darH := daRecordHistory.DataAgreementRecordsHistory{} + darH.DataAgreementId = savedDaRecord.DataAgreementId + darH.OrganisationId = organisationId + err = daRecordHistory.DataAgreementRecordHistoryAdd(darH, savedDaRecord.OptIn) + if err != nil { + m := "Failed to add data agreement record history" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // response + resp := updateDataAgreementRecordResp{ + DataAgreementRecord: savedDaRecord, + Revision: savedRevision, + } + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/v2/handler/service/service_update_signature_object.go b/src/v2/handler/service/service_update_signature_object.go new file mode 100644 index 0000000..54ff259 --- /dev/null +++ b/src/v2/handler/service/service_update_signature_object.go @@ -0,0 +1,114 @@ +package service + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/asaskevich/govalidator" + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" + "github.com/bb-consent/api/src/v2/signature" + "github.com/gorilla/mux" +) + +// createSignatureFromUpdateSignatureRequestBody +func createSignatureFromUpdateSignatureRequestBody(toBeUpdatedSignatureObject signature.Signature, signatureReq updateSignatureforDataAgreementRecordReq) signature.Signature { + + toBeUpdatedSignatureObject.Payload = signatureReq.Signature.Payload + toBeUpdatedSignatureObject.Signature = signatureReq.Signature.Signature + toBeUpdatedSignatureObject.VerificationMethod = signatureReq.Signature.VerificationMethod + toBeUpdatedSignatureObject.VerificationPayload = signatureReq.Signature.VerificationPayload + toBeUpdatedSignatureObject.VerificationPayloadHash = signatureReq.Signature.VerificationPayloadHash + toBeUpdatedSignatureObject.VerificationArtifact = signatureReq.Signature.VerificationArtifact + toBeUpdatedSignatureObject.VerificationSignedBy = signatureReq.Signature.VerificationSignedBy + toBeUpdatedSignatureObject.VerificationSignedAs = signatureReq.Signature.VerificationSignedAs + toBeUpdatedSignatureObject.VerificationJwsHeader = signatureReq.Signature.VerificationJwsHeader + toBeUpdatedSignatureObject.Timestamp = signatureReq.Signature.Timestamp + toBeUpdatedSignatureObject.SignedWithoutObjectReference = signatureReq.Signature.SignedWithoutObjectReference + toBeUpdatedSignatureObject.ObjectType = signatureReq.Signature.ObjectType + toBeUpdatedSignatureObject.ObjectReference = signatureReq.Signature.ObjectReference + + return toBeUpdatedSignatureObject +} + +type updateSignatureforDataAgreementRecordReq struct { + Signature signature.Signature `json:"signature" valid:"required"` +} + +type updateSignatureforDataAgreementRecordResp struct { + Signature signature.Signature `json:"signature"` +} + +func ServiceUpdateSignatureObject(w http.ResponseWriter, r *http.Request) { + + // Headers + organisationId := common.Sanitize(r.Header.Get(config.OrganizationId)) + + dataAgreementRecordId := common.Sanitize(mux.Vars(r)[config.DataAgreementRecordId]) + + // Request body + var signatureReq updateSignatureforDataAgreementRecordReq + b, _ := io.ReadAll(r.Body) + defer r.Body.Close() + json.Unmarshal(b, &signatureReq) + + // validating request payload + valid, err := govalidator.ValidateStruct(signatureReq) + if !valid { + m := fmt.Sprintf("Failed to validate request body: %v", dataAgreementRecordId) + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // Repository + darRepo := daRecord.DataAgreementRecordRepository{} + darRepo.Init(organisationId) + + toBeUpdatedDaRecord, err := darRepo.Get(dataAgreementRecordId) + if err != nil { + m := "Failed to fetch data agreement record" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + toBeUpdatedSignatureObject, err := signature.Get(toBeUpdatedDaRecord.SignatureId) + if err != nil { + m := "Failed to fetch signature for data agreement record" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // update signaute for data agreement record + toBeUpdatedSignatureObject = createSignatureFromUpdateSignatureRequestBody(toBeUpdatedSignatureObject, signatureReq) + + savedSignature, err := signature.Update(toBeUpdatedSignatureObject) + if err != nil { + m := "Failed to update signature for data agreement record" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + // update the data agreement record state + toBeUpdatedDaRecord.State = config.Signed + + // Save data agreement to db + _, err = darRepo.Update(toBeUpdatedDaRecord) + if err != nil { + m := "Failed to update data agreement record" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + resp := updateSignatureforDataAgreementRecordResp{ + Signature: savedSignature, + } + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) + +} diff --git a/src/v2/handler/service/service_verification_fetch_dataagreement_record.go b/src/v2/handler/service/service_verification_fetch_dataagreement_record.go new file mode 100644 index 0000000..98c9a01 --- /dev/null +++ b/src/v2/handler/service/service_verification_fetch_dataagreement_record.go @@ -0,0 +1,53 @@ +package service + +import ( + "encoding/json" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" + "github.com/bb-consent/api/src/v2/revision" + "github.com/gorilla/mux" +) + +type vFetchDataAgreementRecordResp struct { + DataAgreementRecord daRecord.DataAgreementRecord `json:"dataAgreementRecord"` + Revision revision.Revision `json:"revision"` +} + +func ServiceVerificationFetchDataAgreementRecord(w http.ResponseWriter, r *http.Request) { + + // Headers + organisationId := common.Sanitize(r.Header.Get(config.OrganizationId)) + individualId := common.Sanitize(r.Header.Get(config.IndividualHeaderKey)) + + dataAgreementId := common.Sanitize(mux.Vars(r)[config.DataAgreementId]) + + // Repository + darRepo := daRecord.DataAgreementRecordRepository{} + darRepo.Init(organisationId) + + daRecord, err := darRepo.GetByDataAgreementIdandIndividualId(dataAgreementId, individualId) + if err != nil { + m := "Failed to fetch data agreement record" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + currentRevision, err := revision.GetLatestByObjectId(daRecord.Id.Hex()) + if err != nil { + m := "Failed to fetch revision for data agreement record" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + } + + resp := vFetchDataAgreementRecordResp{ + DataAgreementRecord: daRecord, + Revision: currentRevision, + } + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/v2/handler/service/service_verification_fetch_dataagreement_records.go b/src/v2/handler/service/service_verification_fetch_dataagreement_records.go new file mode 100644 index 0000000..a9e4bfc --- /dev/null +++ b/src/v2/handler/service/service_verification_fetch_dataagreement_records.go @@ -0,0 +1,64 @@ +package service + +import ( + "context" + "errors" + "log" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" + "github.com/bb-consent/api/src/v2/paginate" +) + +type vfetchDataAgreementRecordsResp struct { + DataAgreementRecords interface{} `json:"dataAgreementRecords"` + Pagination paginate.Pagination `json:"pagination"` +} + +func ServiceVerificationFetchDataAgreementRecords(w http.ResponseWriter, r *http.Request) { + + // Headers + organisationId := common.Sanitize(r.Header.Get(config.OrganizationId)) + + // Query params + offset, limit := paginate.ParsePaginationQueryParams(r) + log.Printf("Offset: %v and limit: %v\n", offset, limit) + + // Repository + darRepo := daRecord.DataAgreementRecordRepository{} + darRepo.Init(organisationId) + + // Return all data agreement records + var dataAgreementRecords []daRecord.DataAgreementRecord + query := paginate.PaginateDBObjectsQuery{ + Filter: darRepo.DefaultFilter, + Collection: daRecord.Collection(), + Context: context.Background(), + Limit: limit, + Offset: offset, + } + var resp vfetchDataAgreementRecordsResp + result, err := paginate.PaginateDBObjects(query, &dataAgreementRecords) + if err != nil { + if errors.Is(err, paginate.EmptyDBError) { + emptyDataAgreementRecords := make([]interface{}, 0) + resp = vfetchDataAgreementRecordsResp{ + DataAgreementRecords: emptyDataAgreementRecords, + Pagination: result.Pagination, + } + common.ReturnHTTPResponse(resp, w) + return + } + m := "Failed to paginate data agreement records" + common.HandleErrorV2(w, http.StatusInternalServerError, m, err) + return + + } + resp = vfetchDataAgreementRecordsResp{ + DataAgreementRecords: result.Items, + Pagination: result.Pagination, + } + common.ReturnHTTPResponse(resp, w) +} diff --git a/src/v2/handler/service/servicecreateindividualconsentrecord_handler.go b/src/v2/handler/service/servicecreateindividualconsentrecord_handler.go deleted file mode 100644 index 8859165..0000000 --- a/src/v2/handler/service/servicecreateindividualconsentrecord_handler.go +++ /dev/null @@ -1,420 +0,0 @@ -package service - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net/http" - "strconv" - "strings" - "time" - - "github.com/asaskevich/govalidator" - "github.com/bb-consent/api/src/common" - "github.com/bb-consent/api/src/config" - "github.com/bb-consent/api/src/consent" - "github.com/bb-consent/api/src/consenthistory" - "github.com/bb-consent/api/src/org" - "github.com/bb-consent/api/src/v2/token" - "github.com/bb-consent/api/src/webhooks" - "github.com/gorilla/mux" -) - -type purposeAllUpReq struct { - Consented string `valid:"required"` -} - -type consentsAndPurpose struct { - Purpose org.Purpose - Count ConsentCount - Consents []consentResp -} - -// ConsentCount Counts the total consent attributes and consented ones -type ConsentCount struct { - Total int - Consented int -} -type consentResp struct { - ID string - Description string - Value string - Status consentStatus -} - -type consentStatus struct { - Consented string - TimeStamp time.Time - Days int - Remaining int -} - -type consentHistory struct { - UserID string - ConsentID string - OrgID string - OrgName string - PurposeID string - PurposeAllowAll bool - PurposeName string - AttributeID string - AttributeDescription string - AttributeConsentStatus string -} - -// DataRetentionPolicyResp Data retention policy response struct definition -type DataRetentionPolicyResp struct { - Expiry string -} - -type consentsAndPurposeWithDataRetention struct { - Purpose org.Purpose - Count ConsentCount - Consents []consentResp - DataRetention DataRetentionPolicyResp -} - -// ConsentsRespWithDataRetention Consent response struct definition with data retention for each purpose -type ConsentsRespWithDataRetention struct { - ID string - OrgID string - UserID string - ConsentsAndPurposes []consentsAndPurposeWithDataRetention -} - -// ConsentsResp Consent response struct definition -type ConsentsResp struct { - ID string - OrgID string - UserID string - ConsentsAndPurposes []consentsAndPurpose -} - -func getPurposeFromID(p []org.Purpose, purposeID string) org.Purpose { - for _, e := range p { - if e.ID == purposeID { - return e - } - } - return org.Purpose{} -} - -func createConsentGetResponse(c consent.Consents, o org.Organization) ConsentsResp { - var cResp ConsentsResp - cResp.ID = c.ID.Hex() - cResp.OrgID = c.OrgID - cResp.UserID = c.UserID - - for _, p := range o.Purposes { - // Filtering templates corresponding to the purpose ID - templatesWithPurpose := getTemplateswithPurpose(p.ID, o.Templates) - - // Filtering consents corresponding to purpose ID - cons := getConsentsWithPurpose(p.ID, c) - - conResp := createConsentResponse(templatesWithPurpose, cons, p) - - cResp.ConsentsAndPurposes = append(cResp.ConsentsAndPurposes, conResp) - } - return cResp -} - -func consentHistoryPurposeAdd(ch consentHistory) error { - var c consenthistory.ConsentHistory - - c.ConsentID = ch.ConsentID - c.UserID = ch.UserID - c.OrgID = ch.OrgID - c.PurposeID = ch.PurposeID - - var val = "DisAllow" - if ch.PurposeAllowAll == true { - val = "Allow" - } - c.Log = fmt.Sprintf("Updated consent value to <%s> for the purpose <%s> in organization <%s>", - val, ch.PurposeName, ch.OrgName) - - log.Printf("The log is: %s", c.Log) - _, err := consenthistory.Add(c) - if err != nil { - return err - } - - return nil -} - -func getTemplateswithPurpose(purposeID string, templates []org.Template) []org.Template { - var t []org.Template - for _, template := range templates { - for _, pID := range template.PurposeIDs { - if pID == purposeID { - t = append(t, template) - break - } - } - } - return t -} - -func getConsentsWithPurpose(purposeID string, c consent.Consents) []consent.Consent { - for _, p := range c.Purposes { - if p.ID == purposeID { - return p.Consents - } - } - return []consent.Consent{} -} - -func createConsentResponse(templates []org.Template, consents []consent.Consent, purpose org.Purpose) consentsAndPurpose { - var cp consentsAndPurpose - for _, template := range templates { - var conResp consentResp - conResp.ID = template.ID - conResp.Description = template.Consent - - // Fetching consents matching a Template ID - c := getConsentWithTemplateID(template.ID, consents) - - if (consent.Consent{}) == c { - if purpose.LawfulUsage { - conResp.Status.Consented = common.ConsentStatusAllow - } else { - conResp.Status.Consented = common.ConsentStatusDisAllow - } - } else { - conResp.Status.Consented = c.Status.Consented - conResp.Value = c.Value - if c.Status.Days != 0 { - conResp.Status.Days = c.Status.Days - conResp.Status.Remaining = c.Status.Days - int((time.Now().Sub(c.Status.TimeStamp).Hours())/24) - if conResp.Status.Remaining <= 0 { - conResp.Status.Consented = common.ConsentStatusDisAllow - conResp.Status.Remaining = 0 - } else { - conResp.Status.TimeStamp = c.Status.TimeStamp - } - - } - } - cp.Consents = append(cp.Consents, conResp) - } - cp.Purpose = purpose - cp.Count = getConsentCount(cp) - return cp -} - -func getConsentWithTemplateID(templateID string, consents []consent.Consent) consent.Consent { - for _, c := range consents { - if c.TemplateID == templateID { - return c - } - } - return consent.Consent{} -} - -func getConsentCount(cp consentsAndPurpose) ConsentCount { - var c ConsentCount - var disallowCount = 0 - - for _, p := range cp.Consents { - c.Total++ - if (p.Status.Consented == common.ConsentStatusDisAllow) || (p.Status.Consented == "DisAllow") { - disallowCount++ - } - } - c.Consented = c.Total - disallowCount - return c -} - -func ServiceCreateIndividualConsentRecord(w http.ResponseWriter, r *http.Request) { - - userID := token.GetUserID(r) - purposeID := mux.Vars(r)[config.DataAgreementId] - orgID := r.Header.Get(config.OrganizationId) - - sanitizedUserId := common.Sanitize(userID) - sanitizedOrgId := common.Sanitize(orgID) - - consents, err := consent.GetByUserOrg(sanitizedUserId, sanitizedOrgId) - if err != nil { - m := fmt.Sprintf("Failed to fetch consents user: %v org: %v", userID, orgID) - common.HandleError(w, http.StatusInternalServerError, m, err) - return - } - consentID := consents.ID.Hex() - - var purposeUp purposeAllUpReq - b, _ := ioutil.ReadAll(r.Body) - defer r.Body.Close() - - json.Unmarshal(b, &purposeUp) - - // validating request payload - valid, err := govalidator.ValidateStruct(purposeUp) - if !valid { - common.HandleError(w, http.StatusBadRequest, err.Error(), err) - return - } - - // Validating the purpose consent value - purposeUp.Consented = strings.ToLower(purposeUp.Consented) - switch purposeUp.Consented { - - case strings.ToLower(common.ConsentStatusAskMe): - purposeUp.Consented = common.ConsentStatusAskMe - case strings.ToLower(common.ConsentStatusAllow): - purposeUp.Consented = common.ConsentStatusAllow - case strings.ToLower(common.ConsentStatusDisAllow): - purposeUp.Consented = common.ConsentStatusDisAllow - default: - m := fmt.Sprintf("Please provide a valid value for consent; Failed to update purpose consent: %v for org: %v user: %v", consentID, orgID, userID) - common.HandleError(w, http.StatusNotFound, m, err) - return - } - - o, err := org.Get(orgID) - if err != nil { - m := fmt.Sprintf("Failed to fetch organization for user: %v org: %v", userID, orgID) - common.HandleError(w, http.StatusInternalServerError, m, err) - return - } - - c, err := consent.Get(consentID) - if err != nil { - m := fmt.Sprintf("Failed to fetch consensts by ID: %v for user: %v", consentID, userID) - common.HandleError(w, http.StatusNotFound, m, err) - return - } - - // Combine org and consent details to get unified view - cResp := createConsentGetResponse(c, o) - - var found = 0 - var cp consentsAndPurpose - for _, item := range cResp.ConsentsAndPurposes { - if item.Purpose.ID == purposeID { - cp = item - found++ - } - } - if found == 0 { - //TODO: Handle the case where the purpose ID is non existent - } - - validBasis := cp.Purpose.LawfulBasisOfProcessing == org.ConsentBasis || cp.Purpose.LawfulBasisOfProcessing == org.LegitimateInterestBasis - - if !validBasis { - errorMsg := fmt.Sprintf("Invalid lawfull basis for purpose: %v, org: %v, user: %v", purposeID, orgID, userID) - common.HandleError(w, http.StatusBadRequest, errorMsg, err) - return - } - - //TODO: HAckish, not optimized at all - var cnew consent.Consents - cnew.ID = c.ID - cnew.OrgID = c.OrgID - cnew.UserID = c.UserID - cnew.Purposes = nil - - for _, e := range c.Purposes { - if e.ID != purposeID { - cnew.Purposes = append(cnew.Purposes, e) - } - } - - var purposeConsentStatus = false - if purposeUp.Consented == common.ConsentStatusAllow { - purposeConsentStatus = true - } - - var purpose consent.Purpose - purpose.ID = purposeID - purpose.AllowAll = purposeConsentStatus - - for _, e := range cp.Consents { - var conNew consent.Consent - conNew.TemplateID = e.ID - conNew.Value = e.Value - conNew.Status.Consented = purposeUp.Consented - conNew.Status.Days = 0 - - purpose.Consents = append(purpose.Consents, conNew) - } - - cnew.Purposes = append(cnew.Purposes, purpose) - c, err = consent.UpdatePurposes(cnew) - - if err != nil { - m := fmt.Sprintf("Failed to update consent:%v for org: %v user: %v", cnew, orgID, userID) - common.HandleError(w, http.StatusInternalServerError, m, err) - return - } - - cResp = createConsentGetResponse(c, o) - - var ch consentHistory - ch.UserID = userID - ch.OrgID = orgID - ch.OrgName = o.Name - ch.PurposeID = purposeID - ch.PurposeName = getPurposeFromID(o.Purposes, purposeID).Name - ch.ConsentID = c.ID.Hex() - ch.PurposeAllowAll = false - ch.PurposeAllowAll = purposeConsentStatus - - purpose.AllowAll = purposeConsentStatus - err = consentHistoryPurposeAdd(ch) - if err != nil { - m := fmt.Sprintf("Failed to update log for consent: %v for org: %v user: %v ", consentID, orgID, userID) - common.HandleError(w, http.StatusInternalServerError, m, err) - return - } - - var cRespWithDataRetention ConsentsRespWithDataRetention - cRespWithDataRetention.OrgID = cResp.OrgID - cRespWithDataRetention.UserID = cResp.UserID - cRespWithDataRetention.ID = cResp.ID - - for i, _ := range cResp.ConsentsAndPurposes { - var tempConsentsAndPurposeWithDataRetention consentsAndPurposeWithDataRetention - tempConsentsAndPurposeWithDataRetention.Consents = cResp.ConsentsAndPurposes[i].Consents - tempConsentsAndPurposeWithDataRetention.Count = cResp.ConsentsAndPurposes[i].Count - tempConsentsAndPurposeWithDataRetention.Purpose = cResp.ConsentsAndPurposes[i].Purpose - - if o.DataRetention.Enabled { - - // Check if purpose is allowed - if cResp.ConsentsAndPurposes[i].Count.Consented > 0 { - latestConsentHistory, err := consenthistory.GetLatestByUserOrgPurposeID(sanitizedUserId, sanitizedOrgId, cResp.ConsentsAndPurposes[i].Purpose.ID) - if err != nil { - cRespWithDataRetention.ConsentsAndPurposes = append(cRespWithDataRetention.ConsentsAndPurposes, tempConsentsAndPurposeWithDataRetention) - continue - } - - tempConsentsAndPurposeWithDataRetention.DataRetention.Expiry = latestConsentHistory.ID.Timestamp().Add(time.Second * time.Duration(o.DataRetention.RetentionPeriod)).UTC().String() - } - } - - cRespWithDataRetention.ConsentsAndPurposes = append(cRespWithDataRetention.ConsentsAndPurposes, tempConsentsAndPurposeWithDataRetention) - - } - - // Trigger webhooks - var consentedAttributes []string - for _, pConsent := range purpose.Consents { - consentedAttributes = append(consentedAttributes, pConsent.TemplateID) - } - - webhookEventTypeID := webhooks.EventTypeConsentDisAllowed - if purposeUp.Consented == common.ConsentStatusAllow { - webhookEventTypeID = webhooks.EventTypeConsentAllowed - } - - go webhooks.TriggerConsentWebhookEvent(userID, purposeID, consentID, orgID, webhooks.EventTypes[webhookEventTypeID], strconv.FormatInt(time.Now().UTC().Unix(), 10), 0, consentedAttributes) - - response, _ := json.Marshal(cRespWithDataRetention) - w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) - w.Write(response) - -} diff --git a/src/v2/handler/service/servicelistindividualrecordlist_handler.go b/src/v2/handler/service/servicelistindividualrecordlist_handler.go deleted file mode 100644 index 0fc389e..0000000 --- a/src/v2/handler/service/servicelistindividualrecordlist_handler.go +++ /dev/null @@ -1,49 +0,0 @@ -package service - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/bb-consent/api/src/common" - "github.com/bb-consent/api/src/config" - "github.com/bb-consent/api/src/consent" - "github.com/bb-consent/api/src/org" - "github.com/bb-consent/api/src/v2/token" -) - -func ServiceListIndividualRecordList(w http.ResponseWriter, r *http.Request) { - userID := token.GetUserID(r) - orgID := r.Header.Get(config.OrganizationId) - - sanitizedUserId := common.Sanitize(userID) - sanitizedOrgId := common.Sanitize(orgID) - - consents, err := consent.GetByUserOrg(sanitizedUserId, sanitizedOrgId) - if err != nil { - m := fmt.Sprintf("Failed to fetch consents user: %v org: %v", userID, orgID) - common.HandleError(w, http.StatusInternalServerError, m, err) - return - } - consentID := consents.ID.Hex() - - o, err := org.Get(orgID) - if err != nil { - m := fmt.Sprintf("Failed to fetch organization for user: %v org: %v", userID, orgID) - common.HandleError(w, http.StatusInternalServerError, m, err) - return - } - - consent, err := consent.Get(consentID) - if err != nil { - m := fmt.Sprintf("Failed to fetch consensts by ID: %v \n", consentID) - common.HandleError(w, http.StatusNotFound, m, err) - return - } - - c := createConsentGetResponse(consent, o) - response, _ := json.Marshal(c) - w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) - w.Write(response) - -} diff --git a/src/v2/handler/service/servicereadindividualrecordread_handler.go b/src/v2/handler/service/servicereadindividualrecordread_handler.go deleted file mode 100644 index f37bb82..0000000 --- a/src/v2/handler/service/servicereadindividualrecordread_handler.go +++ /dev/null @@ -1,110 +0,0 @@ -package service - -import ( - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/bb-consent/api/src/common" - "github.com/bb-consent/api/src/config" - "github.com/bb-consent/api/src/consent" - "github.com/bb-consent/api/src/consenthistory" - "github.com/bb-consent/api/src/org" - "github.com/bb-consent/api/src/v2/token" - "github.com/gorilla/mux" -) - -type consentPurposeResp struct { - ID string - ConsentID string - OrgID string - UserID string - Consents consentsAndPurpose - DataRetention DataRetentionPolicyResp -} - -func ServiceReadIndividualRecordRead(w http.ResponseWriter, r *http.Request) { - - userID := token.GetUserID(r) - purposeID := mux.Vars(r)[config.DataAgreementId] - orgID := r.Header.Get(config.OrganizationId) - - sanitizedUserId := common.Sanitize(userID) - sanitizedOrgId := common.Sanitize(orgID) - sanitizedPurposeId := common.Sanitize(purposeID) - - consents, err := consent.GetByUserOrg(sanitizedUserId, sanitizedOrgId) - if err != nil { - m := fmt.Sprintf("Failed to fetch consents user: %v org: %v", userID, orgID) - common.HandleError(w, http.StatusInternalServerError, m, err) - return - } - consentID := consents.ID.Hex() - - o, err := org.Get(orgID) - if err != nil { - m := fmt.Sprintf("Failed to fetch organization for user: %v org: %v", userID, orgID) - common.HandleError(w, http.StatusInternalServerError, m, err) - return - } - - consent, err := consent.Get(consentID) - if err != nil { - m := fmt.Sprintf("Failed to fetch consensts by ID: %v for user: %v", consentID, userID) - common.HandleError(w, http.StatusNotFound, m, err) - return - } - - c := createConsentGetResponse(consent, o) - - var found = 0 - var cp consentsAndPurpose - for _, item := range c.ConsentsAndPurposes { - if item.Purpose.ID == purposeID { - cp = item - found++ - } - } - if found == 0 { - //TODO: Handle the case where the purpose ID is non existent - } - - var cpResp consentPurposeResp - cpResp.ID = purposeID - cpResp.ConsentID = consentID - cpResp.OrgID = orgID - cpResp.UserID = userID - cpResp.Consents = cp - - // Data retention expiry - if o.DataRetention.Enabled { - - // Check if atleast one attribute consent is allowed - isPurposeAllowed := false - for _, attributeConsent := range cpResp.Consents.Consents { - if attributeConsent.Status.Consented == common.ConsentStatusAllow { - isPurposeAllowed = true - break - } - } - - if isPurposeAllowed { - - latestConsentHistory, err := consenthistory.GetLatestByUserOrgPurposeID(sanitizedUserId, sanitizedOrgId, sanitizedPurposeId) - if err != nil { - response, _ := json.Marshal(cpResp) - w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) - w.Write(response) - return - } - - cpResp.DataRetention.Expiry = latestConsentHistory.ID.Timestamp().Add(time.Second * time.Duration(o.DataRetention.RetentionPeriod)).UTC().String() - } - } - - response, _ := json.Marshal(cpResp) - w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) - w.Write(response) - -} diff --git a/src/v2/handler/service/serviceupdateindividualconsentrecord_handler.go b/src/v2/handler/service/serviceupdateindividualconsentrecord_handler.go deleted file mode 100644 index 2c76bda..0000000 --- a/src/v2/handler/service/serviceupdateindividualconsentrecord_handler.go +++ /dev/null @@ -1,14 +0,0 @@ -package service - -import ( - "net/http" - - "github.com/bb-consent/api/src/config" -) - -func ServiceUpdateIndividualConsentRecord(w http.ResponseWriter, r *http.Request) { - - w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) - w.WriteHeader(http.StatusOK) - -} diff --git a/src/v2/handler/service/serviceverificationagreementconsentrecordread_handler.go b/src/v2/handler/service/serviceverificationagreementconsentrecordread_handler.go deleted file mode 100644 index 2ac9768..0000000 --- a/src/v2/handler/service/serviceverificationagreementconsentrecordread_handler.go +++ /dev/null @@ -1,14 +0,0 @@ -package service - -import ( - "net/http" - - "github.com/bb-consent/api/src/config" -) - -func ServiceVerificationAgreementConsentRecordRead(w http.ResponseWriter, r *http.Request) { - - w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) - w.WriteHeader(http.StatusOK) - -} diff --git a/src/v2/handler/service/serviceverificationagreementlist_handler.go b/src/v2/handler/service/serviceverificationagreementlist_handler.go deleted file mode 100644 index be47c4c..0000000 --- a/src/v2/handler/service/serviceverificationagreementlist_handler.go +++ /dev/null @@ -1,14 +0,0 @@ -package service - -import ( - "net/http" - - "github.com/bb-consent/api/src/config" -) - -func ServiceVerificationAgreementList(w http.ResponseWriter, r *http.Request) { - - w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) - w.WriteHeader(http.StatusOK) - -} diff --git a/src/v2/handler/service/serviceverificationconsentrecordlist_handler.go b/src/v2/handler/service/serviceverificationconsentrecordlist_handler.go deleted file mode 100644 index 416f203..0000000 --- a/src/v2/handler/service/serviceverificationconsentrecordlist_handler.go +++ /dev/null @@ -1,14 +0,0 @@ -package service - -import ( - "net/http" - - "github.com/bb-consent/api/src/config" -) - -func ServiceVerificationConsentRecordList(w http.ResponseWriter, r *http.Request) { - - w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) - w.WriteHeader(http.StatusOK) - -} diff --git a/src/v2/http_path/routes.go b/src/v2/http_path/routes.go index 9ff4fe0..84d0fd6 100644 --- a/src/v2/http_path/routes.go +++ b/src/v2/http_path/routes.go @@ -92,15 +92,25 @@ func SetRoutes(r *mux.Router, e *casbin.Enforcer) { r.Handle(ServiceListDataAttributesForDataAgreement, m.Chain(serviceHandler.ServiceListDataAttributesForDataAgreement, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET") // Verification mechanisms - r.Handle(ServiceVerificationAgreementList, m.Chain(serviceHandler.ServiceVerificationAgreementList, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET") - r.Handle(ServiceVerificationAgreementConsentRecordRead, m.Chain(serviceHandler.ServiceVerificationAgreementConsentRecordRead, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET") - r.Handle(ServiceVerificationConsentRecordList, m.Chain(serviceHandler.ServiceVerificationConsentRecordList, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET") + + r.Handle(ServiceVerificationFetchAllDataAgreementRecords, m.Chain(serviceHandler.ServiceFetchDataAgreementRecords, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET") + r.Handle(ServiceVerificationFetchDataAgreementRecord, m.Chain(serviceHandler.ServiceVerificationFetchDataAgreementRecord, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET") + r.Handle(ServiceVerificationFetchDataAgreementRecords, m.Chain(serviceHandler.ServiceVerificationFetchDataAgreementRecords, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET") // Recording consent - r.Handle(ServiceCreateIndividualConsentRecord, m.Chain(serviceHandler.ServiceCreateIndividualConsentRecord, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("POST") - r.Handle(ServiceUpdateIndividualConsentRecord, m.Chain(serviceHandler.ServiceCreateIndividualConsentRecord, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("PUT") - r.Handle(ServiceListIndividualRecordList, m.Chain(serviceHandler.ServiceListIndividualRecordList, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET") - r.Handle(ServiceReadIndividualRecordRead, m.Chain(serviceHandler.ServiceReadIndividualRecordRead, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET") + r.Handle(ServiceCreateDraftConsentRecord, m.Chain(serviceHandler.ServiceCreateDraftConsentRecord, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("POST") + r.Handle(ServiceCreateDataAgreementRecord, m.Chain(serviceHandler.ServiceCreateDataAgreementRecord, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("POST") + r.Handle(ServiceUpdateDataAgreementRecord, m.Chain(serviceHandler.ServiceUpdateDataAgreementRecord, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("PUT") + r.Handle(ServiceDeleteIndividualDataAgreementRecords, m.Chain(serviceHandler.ServiceDeleteIndividualDataAgreementRecords, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("PUT") + r.Handle(ServiceCreatePairedDataAgreementRecord, m.Chain(serviceHandler.ServiceCreatePairedDataAgreementRecord, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("POST") + r.Handle(ServiceUpdateSignatureObject, m.Chain(serviceHandler.ServiceUpdateSignatureObject, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("PUT") + r.Handle(ServiceCreateBlankSignature, m.Chain(serviceHandler.ServiceCreateBlankSignature, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("POST") + + r.Handle(ServiceReadDataAgreementRecord, m.Chain(serviceHandler.ServiceReadDataAgreementRecord, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET") + r.Handle(ServiceFetchIndividualDataAgreementRecords, m.Chain(serviceHandler.ServiceFetchIndividualDataAgreementRecords, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET") + r.Handle(ServiceFetchRecordsForDataAgreement, m.Chain(serviceHandler.ServiceFetchRecordsForDataAgreement, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET") + + r.Handle(ServiceFetchRecordsHistory, m.Chain(serviceHandler.ServiceFetchRecordsHistory, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET") // Audit api(s) diff --git a/src/v2/http_path/service_paths.go b/src/v2/http_path/service_paths.go index 1cc7fd1..df99c2d 100644 --- a/src/v2/http_path/service_paths.go +++ b/src/v2/http_path/service_paths.go @@ -11,15 +11,25 @@ const ServiceReadPolicy = "/v2/service/policy/{policyId}" const ServiceListDataAttributesForDataAgreement = "/v2/service/data-agreement/{dataAgreementId}/data-attributes" // Verification mechanisms -const ServiceVerificationAgreementList = "/v2/service/verification/data-agreements/" -const ServiceVerificationAgreementConsentRecordRead = "/v2/service/verification/data-agreement/" -const ServiceVerificationConsentRecordList = "/v2/service/verification/records" +const ServiceVerificationFetchAllDataAgreementRecords = "/v2/service/verification/data-agreements" +const ServiceVerificationFetchDataAgreementRecord = "/v2/service/verification/data-agreement/{dataAgreementId}" +const ServiceVerificationFetchDataAgreementRecords = "/v2/service/verification/data-agreement-records" // Recording consent -const ServiceCreateIndividualConsentRecord = "/v2/service/individual/data-agreement/{dataAgreementId}/record" -const ServiceUpdateIndividualConsentRecord = "/v2/service/individual/data-agreement/{dataAgreementId}" -const ServiceListIndividualRecordList = "/v2/service/individual/record/data-agreement/" -const ServiceReadIndividualRecordRead = "/v2/service/individual/record/data-agreement/{dataAgreementId}/" +const ServiceCreateDraftConsentRecord = "/v2/service/individual/record/data-agreement-record/draft" +const ServiceCreateDataAgreementRecord = "/v2/service/individual/record/data-agreement/{dataAgreementId}" +const ServiceReadDataAgreementRecord = "/v2/service/individual/record/data-agreement/{dataAgreementId}" +const ServiceUpdateDataAgreementRecord = "/v2/service/individual/record/data-agreement-record/{dataAgreementRecordId}" +const ServiceDeleteIndividualDataAgreementRecords = "/v2/service/individual/record/data-agreement-record" +const ServiceCreatePairedDataAgreementRecord = "/v2/service/individual/record/data-agreement-record" + +const ServiceCreateBlankSignature = "/v2/service/individual/record/data-agreement-record/{dataAgreementRecordId}/signature" +const ServiceUpdateSignatureObject = "/v2/service/individual/record/data-agreement-record/{dataAgreementRecordId}/signature" + +const ServiceFetchIndividualDataAgreementRecords = "/v2/service/individual/record/data-agreement-record" +const ServiceFetchRecordsForDataAgreement = "/v2/service/individual/record/data-agreement/{dataAgreementId}/all" + +const ServiceFetchRecordsHistory = "/v2/service/individual/record/data-agreement-record/history" // Idp -const ServiceReadIdp = "/service/idp/open-id/{idpId}" +const ServiceReadIdp = "/v2/service/idp/open-id/{idpId}" diff --git a/src/v2/middleware/authenticate.go b/src/v2/middleware/authenticate.go index a4d38d1..defb889 100644 --- a/src/v2/middleware/authenticate.go +++ b/src/v2/middleware/authenticate.go @@ -121,7 +121,7 @@ func verifyTokenAndIdentifyRole(accessToken string, r *http.Request) error { if len(user.Roles) > 0 { token.SetUserToRequestContext(r, user.ID.Hex(), rbac.ROLE_ADMIN) } else { - token.SetUserToRequestContext(r, user.ID.Hex(), rbac.ROLE_ADMIN) + token.SetUserToRequestContext(r, user.ID.Hex(), rbac.ROLE_USER) } return nil diff --git a/src/v2/revision/db.go b/src/v2/revision/db.go index dac6df7..118a2f4 100644 --- a/src/v2/revision/db.go +++ b/src/v2/revision/db.go @@ -146,3 +146,33 @@ func ListAllByDataAttributeId(dataAttributeId string) ([]Revision, error) { return results, err } + +// GetByRevisionIdAndSchema gets revision by id and schema +func GetByRevisionIdAndSchema(revisionID string, schemaName string) (Revision, error) { + var result Revision + + revisionId, err := primitive.ObjectIDFromHex(revisionID) + if err != nil { + return result, err + } + + err = Collection().FindOne(context.TODO(), bson.M{"_id": revisionId, "schemaname": schemaName}).Decode(&result) + if err != nil { + return result, err + } + + return result, err +} + +// Get Gets revision by object id +func GetLatestByObjectId(objectId string) (Revision, error) { + + var result Revision + opts := options.FindOne().SetSort(bson.M{"timestamp": -1}) + err := Collection().FindOne(context.TODO(), bson.M{"objectid": objectId}, opts).Decode(&result) + if err != nil { + return Revision{}, err + } + + return result, err +} diff --git a/src/v2/revision/revisions.go b/src/v2/revision/revisions.go index 69e3d18..88bad23 100644 --- a/src/v2/revision/revisions.go +++ b/src/v2/revision/revisions.go @@ -8,6 +8,7 @@ import ( "github.com/bb-consent/api/src/config" "github.com/bb-consent/api/src/policy" "github.com/bb-consent/api/src/v2/dataagreement" + daRecord "github.com/bb-consent/api/src/v2/dataagreement_record" "github.com/bb-consent/api/src/v2/dataattribute" "go.mongodb.org/mongo-driver/bson/primitive" ) @@ -374,3 +375,61 @@ func RecreateDataAttributeFromRevision(revision Revision) (dataattribute.DataAtt return da, nil } + +type dataAgreementRecordForObjectData struct { + Id primitive.ObjectID `json:"id" bson:"_id,omitempty"` + DataAgreementId string `json:"dataAgreementId"` + DataAgreementRevisionId string `json:"dataAgreementRevisionId"` + DataAgreementRevisionHash string `json:"dataAgreementRevisionHash"` + DataAttributes []daRecord.DataAttributeForDataAgreementRecord `json:"dataAttributes"` + IndividualId string `json:"individualId"` + OptIn bool `json:"optIn"` + State string `json:"state" valid:"required"` + SignatureId string `json:"signatureId"` +} + +// CreateRevisionForDataAgreementRecord +func CreateRevisionForDataAgreementRecord(newDataAgreementRecord daRecord.DataAgreementRecord, orgAdminId string) (Revision, error) { + // Object data + objectData := dataAgreementRecordForObjectData{ + Id: newDataAgreementRecord.Id, + DataAgreementId: newDataAgreementRecord.DataAgreementId, + DataAgreementRevisionId: newDataAgreementRecord.DataAgreementRevisionId, + DataAgreementRevisionHash: newDataAgreementRecord.DataAgreementRevisionHash, + DataAttributes: newDataAgreementRecord.DataAttributes, + IndividualId: newDataAgreementRecord.IndividualId, + OptIn: newDataAgreementRecord.OptIn, + State: newDataAgreementRecord.State, + SignatureId: newDataAgreementRecord.SignatureId, + } + + // Create revision + revision := Revision{} + revision.Init(objectData.Id.Hex(), orgAdminId, config.DataAgreementRecord) + err := revision.CreateRevision(objectData) + + return revision, err +} + +// UpdateRevisionForDataAgreementRecord +func UpdateRevisionForDataAgreementRecord(updatedDataAgreementRecord daRecord.DataAgreementRecord, previousRevision *Revision, orgAdminId string) (Revision, error) { + // Object data + objectData := dataAgreementRecordForObjectData{ + Id: updatedDataAgreementRecord.Id, + DataAgreementId: updatedDataAgreementRecord.DataAgreementId, + DataAgreementRevisionId: updatedDataAgreementRecord.DataAgreementRevisionId, + DataAgreementRevisionHash: updatedDataAgreementRecord.DataAgreementRevisionHash, + DataAttributes: updatedDataAgreementRecord.DataAttributes, + IndividualId: updatedDataAgreementRecord.IndividualId, + OptIn: updatedDataAgreementRecord.OptIn, + State: updatedDataAgreementRecord.State, + SignatureId: updatedDataAgreementRecord.SignatureId, + } + + // Update revision + revision := Revision{} + revision.Init(objectData.Id.Hex(), orgAdminId, config.DataAgreementRecord) + err := revision.UpdateRevision(previousRevision, objectData) + + return revision, err +} diff --git a/src/v2/signature/db.go b/src/v2/signature/db.go new file mode 100644 index 0000000..9da565d --- /dev/null +++ b/src/v2/signature/db.go @@ -0,0 +1,53 @@ +package signature + +import ( + "context" + + "github.com/bb-consent/api/src/database" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +func Collection() *mongo.Collection { + return database.DB.Client.Database(database.DB.Name).Collection("signatures") +} + +// Add Adds the signature to the db +func Add(signature Signature) (Signature, error) { + + _, err := Collection().InsertOne(context.TODO(), signature) + if err != nil { + return Signature{}, err + } + + return signature, nil +} + +// Get Gets a signature by given id +func Get(signatureID string) (Signature, error) { + signatureId, err := primitive.ObjectIDFromHex(signatureID) + if err != nil { + return Signature{}, err + } + + filter := bson.M{"_id": signatureId} + + var result Signature + err = Collection().FindOne(context.TODO(), filter).Decode(&result) + + return result, err +} + +// Update Updates the signature +func Update(signature Signature) (Signature, error) { + + filter := bson.M{"_id": signature.Id} + update := bson.M{"$set": signature} + + _, err := Collection().UpdateOne(context.TODO(), filter, update) + if err != nil { + return signature, err + } + return signature, nil +} diff --git a/src/v2/signature/signature.go b/src/v2/signature/signature.go new file mode 100644 index 0000000..145ee3a --- /dev/null +++ b/src/v2/signature/signature.go @@ -0,0 +1,72 @@ +package signature + +import ( + "encoding/json" + + "github.com/bb-consent/api/src/common" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type Signature struct { + Id primitive.ObjectID `json:"id" bson:"_id,omitempty"` + Payload string `json:"payload"` + Signature string `json:"signature"` + VerificationMethod string `json:"verificationMethod"` + VerificationPayload string `json:"verificationPayload"` + VerificationPayloadHash string `json:"verificationPayloadHash"` + VerificationArtifact string `json:"verificationArtifact"` + VerificationSignedBy string `json:"verificationSignedBy"` + VerificationSignedAs string `json:"verificationSignedAs"` + VerificationJwsHeader string `json:"verificationJwsHeader"` + Timestamp string `json:"timestamp"` + SignedWithoutObjectReference bool `json:"signedWithoutObjectReference"` + ObjectType string `json:"objectType"` + ObjectReference string `json:"objectReference"` +} + +// Init +func (s *Signature) Init(ObjectType string, ObjectReference string, SignedWithoutObjectReference bool) { + + s.SignedWithoutObjectReference = SignedWithoutObjectReference + s.ObjectType = ObjectType + s.ObjectReference = ObjectReference +} + +// CreateSignature +func (s *Signature) CreateSignature(VerificationPayload interface{}, IsPayload bool) error { + + // Verification Payload + verificationPayloadSerialised, err := json.Marshal(VerificationPayload) + if err != nil { + return err + } + s.VerificationPayload = string(verificationPayloadSerialised) + + // Serialised hash using SHA-1 + s.VerificationPayloadHash, err = common.CalculateSHA1(string(verificationPayloadSerialised)) + if err != nil { + return err + } + + if IsPayload { + // Payload + payload, err := json.Marshal(s) + if err != nil { + return err + } + s.Payload = string(payload) + } + + return nil + +} + +// CreateSignatureForPolicy +func CreateSignatureForObject(ObjectType string, ObjectReference string, SignedWithoutObjectReference bool, VerificationPayload interface{}, IsPayload bool, signature Signature) (Signature, error) { + + // Create signature + signature.Init(ObjectType, ObjectReference, SignedWithoutObjectReference) + err := signature.CreateSignature(VerificationPayload, IsPayload) + + return signature, err +} diff --git a/src/v2/token/token.go b/src/v2/token/token.go index f8f52d8..74b2ef9 100644 --- a/src/v2/token/token.go +++ b/src/v2/token/token.go @@ -191,4 +191,10 @@ func GetUserRole(r *http.Request) string { func SetUserToRequestContext(r *http.Request, userID string, userRole string) { context.Set(r, userIDKey, userID) context.Set(r, UserRoleKey, userRole) + + // Set individual to request header if not present + if _, exists := r.Header[http.CanonicalHeaderKey(config.IndividualHeaderKey)]; !exists { + r.Header.Set(config.IndividualHeaderKey, userID) + } + }