nuts-foundation/nuts-consent-logic

View on GitHub
api/api.go

Summary

Maintainability
A
1 hr
Test Coverage
/*
 *  Nuts consent logic holds the logic for consent creation
 *  Copyright (C) 2019 Nuts community
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package api

import (
    "fmt"
    "net/http"
    "strings"
    "time"

    "github.com/labstack/echo/v4"
    "github.com/nuts-foundation/nuts-consent-logic/pkg"
    core "github.com/nuts-foundation/nuts-go-core"
)

// Wrapper provides the implementation of the generated ServerInterface
type Wrapper struct {
    Cl *pkg.ConsentLogic
}

// NutsConsentLogicCreateConsent Creates the consent FHIR resource, validate it and sends it to the consent-bridge.
func (wrapper Wrapper) CreateOrUpdateConsent(ctx echo.Context) error {
    createConsentApiRequest := &CreateConsentRequest{}
    if err := ctx.Bind(createConsentApiRequest); err != nil {
        ctx.Logger().Error("Could not unmarshal json body:", err)
        return err
    }

    // Validate all required fields
    if createConsentApiRequest.Custodian == "" {
        return echo.NewHTTPError(http.StatusBadRequest, "the consent requires a custodian")
    }

    if createConsentApiRequest.Subject == "" {
        return echo.NewHTTPError(http.StatusBadRequest, "the consent requires a subject")
    }

    if createConsentApiRequest.Actor == "" {
        return echo.NewHTTPError(http.StatusBadRequest, "the consent requires an actor")
    }

    if len(createConsentApiRequest.Records) < 1 {
        return echo.NewHTTPError(http.StatusBadRequest, "the consent requires at least one record")
    }

    nullTime := time.Time{}
    for _, record := range createConsentApiRequest.Records {
        // Validate if each record has a valid period Start:
        if record.Period.Start == nullTime {
            return echo.NewHTTPError(http.StatusBadRequest, "the consent record requires a period.start")
        }

        // Validate if each record has a valid proof
        if record.ConsentProof.Title == "" || record.ConsentProof.ID == "" {
            return echo.NewHTTPError(http.StatusBadRequest, "the consent record requires a valid proof")
        }

        // Validate DataClass
        if len(record.DataClass) < 1 {
            return echo.NewHTTPError(http.StatusBadRequest, "the consent record requires at least one data class")
        }

        for _, dataClass := range record.DataClass {
            if dataClass == "" {
                return echo.NewHTTPError(http.StatusBadRequest, "a data class can not be empty")
            }
            expr := fmt.Sprintf("urn:oid:%s:", core.NutsConsentClassesOID)
            if !strings.Contains(string(dataClass), expr) {
                return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("a data class must start with %s", expr))
            }
        }
    }

    eventUUID, err := wrapper.Cl.StartConsentFlow(apiRequest2Internal(*createConsentApiRequest))
    if err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }
    uuid := eventUUID.String()
    response := JobCreatedResponse{ResultCode: "OK", JobId: &uuid}

    return ctx.JSON(http.StatusAccepted, response)
}

// Convert the public generated data type to the internal type.
// This abstraction makes the app more robust to api changes.
func apiRequest2Internal(apiRequest CreateConsentRequest) *pkg.CreateConsentRequest {
    createConsentRequest := &pkg.CreateConsentRequest{
        Custodian: apiRequest.Custodian.PartyID(),
        Actor:     apiRequest.Actor.PartyID(),
        Subject:   apiRequest.Subject.PartyID(),
    }
    if apiRequest.Performer != nil {
        createConsentRequest.Performer = apiRequest.Performer.PartyID()
    }

    for _, record := range apiRequest.Records {
        newRecord := pkg.Record{
            PreviousRecordhash: record.PreviousRecordHash,
        }

        newRecord.Period = pkg.Period{Start: record.Period.Start, End: record.Period.End}

        consentProof := &pkg.DocumentReference{
            ID:          record.ConsentProof.ID,
            Title:       record.ConsentProof.Title,
            ContentType: record.ConsentProof.ContentType,
            URL:         record.ConsentProof.URL,
            Hash:        record.ConsentProof.Hash,
        }
        newRecord.ConsentProof = consentProof

        for _, dc := range record.DataClass {
            newRecord.DataClass = append(newRecord.DataClass, string(dc))
        }

        createConsentRequest.Records = append(createConsentRequest.Records, newRecord)
    }
    return createConsentRequest
}