From 9fc51e5552125cb7f8c9554acece3455f9842aaa Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Wed, 22 May 2024 15:54:19 +0200 Subject: [PATCH] added create, delete, get API + db operations for pip data --- api/pip/api.go | 55 +++++++++++++++++-- api/pip/generated.go | 124 +++++++++++++++++++++++++++++++++++++++++++ db/db.go | 38 +++++++++++-- db/interface.go | 5 ++ db/types.go | 17 ++++++ main.go | 2 +- oas/pip.yaml | 40 ++++++++++---- 7 files changed, 261 insertions(+), 20 deletions(-) create mode 100644 db/types.go diff --git a/api/pip/api.go b/api/pip/api.go index dad5931..712b496 100644 --- a/api/pip/api.go +++ b/api/pip/api.go @@ -1,12 +1,57 @@ package pip -import "context" +import ( + "context" + "encoding/json" + "github.com/nuts-foundation/nuts-pxp/db" +) var _ StrictServerInterface = (*Wrapper)(nil) -type Wrapper struct{} +type Wrapper struct { + DB db.DB +} + +func (w Wrapper) CreateData(_ context.Context, request CreateDataRequestObject) (CreateDataResponseObject, error) { + // serialize authInput for storage + authInput, _ := json.Marshal(request.Body.AuthInput) + data := db.SQLData{ + Id: request.Id, + Client: request.Body.ClientId, + Scope: request.Body.Scope, + Verifier: request.Body.VerifierId, + AuthInput: string(authInput), + } + err := w.DB.Create(data) + if err != nil { + return nil, err + } + return CreateData204Response{}, nil +} + +func (w Wrapper) GetData(ctx context.Context, request GetDataRequestObject) (GetDataResponseObject, error) { + data, err := w.DB.Get(request.Id) + if err != nil { + return nil, err + } + // turn data into map[string]interface{} + var authInput map[string]interface{} + err = json.Unmarshal([]byte(data.AuthInput), &authInput) + if err != nil { + return nil, err + } + return GetData200JSONResponse{ + ClientId: data.Client, + Scope: data.Scope, + VerifierId: data.Verifier, + AuthInput: authInput, + }, nil +} -func (w Wrapper) CreateData(ctx context.Context, request CreateDataRequestObject) (CreateDataResponseObject, error) { - //TODO implement me - panic("implement me") +func (w Wrapper) DeleteData(ctx context.Context, request DeleteDataRequestObject) (DeleteDataResponseObject, error) { + err := w.DB.Delete(request.Id) + if err != nil { + return nil, err + } + return DeleteData204Response{}, nil } diff --git a/api/pip/generated.go b/api/pip/generated.go index 3ce6f44..0a83b3d 100644 --- a/api/pip/generated.go +++ b/api/pip/generated.go @@ -5,6 +5,7 @@ package pip import ( "context" + "encoding/json" "fmt" "net/http" @@ -32,6 +33,12 @@ type CreateDataJSONRequestBody = Data // ServerInterface represents all server handlers. type ServerInterface interface { + // Delete data for given id + // (DELETE /pip/{id}) + DeleteData(ctx echo.Context, id string) error + // Get pip data for given ide + // (GET /pip/{id}) + GetData(ctx echo.Context, id string) error // Add authorization data used for OPA evaluation // (POST /pip/{id}) CreateData(ctx echo.Context, id string) error @@ -42,6 +49,32 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } +// DeleteData converts echo context to params. +func (w *ServerInterfaceWrapper) DeleteData(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id string + + id = ctx.Param("id") + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.DeleteData(ctx, id) + return err +} + +// GetData converts echo context to params. +func (w *ServerInterfaceWrapper) GetData(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id string + + id = ctx.Param("id") + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetData(ctx, id) + return err +} + // CreateData converts echo context to params. func (w *ServerInterfaceWrapper) CreateData(ctx echo.Context) error { var err error @@ -83,10 +116,45 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } + router.DELETE(baseURL+"/pip/:id", wrapper.DeleteData) + router.GET(baseURL+"/pip/:id", wrapper.GetData) router.POST(baseURL+"/pip/:id", wrapper.CreateData) } +type DeleteDataRequestObject struct { + Id string `json:"id"` +} + +type DeleteDataResponseObject interface { + VisitDeleteDataResponse(w http.ResponseWriter) error +} + +type DeleteData204Response struct { +} + +func (response DeleteData204Response) VisitDeleteDataResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +type GetDataRequestObject struct { + Id string `json:"id"` +} + +type GetDataResponseObject interface { + VisitGetDataResponse(w http.ResponseWriter) error +} + +type GetData200JSONResponse Data + +func (response GetData200JSONResponse) VisitGetDataResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + type CreateDataRequestObject struct { Id string `json:"id"` Body *CreateDataJSONRequestBody @@ -106,6 +174,12 @@ func (response CreateData204Response) VisitCreateDataResponse(w http.ResponseWri // StrictServerInterface represents all server handlers. type StrictServerInterface interface { + // Delete data for given id + // (DELETE /pip/{id}) + DeleteData(ctx context.Context, request DeleteDataRequestObject) (DeleteDataResponseObject, error) + // Get pip data for given ide + // (GET /pip/{id}) + GetData(ctx context.Context, request GetDataRequestObject) (GetDataResponseObject, error) // Add authorization data used for OPA evaluation // (POST /pip/{id}) CreateData(ctx context.Context, request CreateDataRequestObject) (CreateDataResponseObject, error) @@ -123,6 +197,56 @@ type strictHandler struct { middlewares []StrictMiddlewareFunc } +// DeleteData operation middleware +func (sh *strictHandler) DeleteData(ctx echo.Context, id string) error { + var request DeleteDataRequestObject + + request.Id = id + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.DeleteData(ctx.Request().Context(), request.(DeleteDataRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DeleteData") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(DeleteDataResponseObject); ok { + return validResponse.VisitDeleteDataResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// GetData operation middleware +func (sh *strictHandler) GetData(ctx echo.Context, id string) error { + var request GetDataRequestObject + + request.Id = id + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.GetData(ctx.Request().Context(), request.(GetDataRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetData") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(GetDataResponseObject); ok { + return validResponse.VisitGetDataResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + // CreateData operation middleware func (sh *strictHandler) CreateData(ctx echo.Context, id string) error { var request CreateDataRequestObject diff --git a/db/db.go b/db/db.go index 2a544a5..3537ae8 100644 --- a/db/db.go +++ b/db/db.go @@ -3,6 +3,9 @@ package db import ( "context" "errors" + "os" + "strings" + "github.com/glebarez/sqlite" "github.com/nuts-foundation/nuts-pxp/config" sql_migrations "github.com/nuts-foundation/nuts-pxp/db/migrations" @@ -11,10 +14,10 @@ import ( "gorm.io/driver/postgres" "gorm.io/driver/sqlserver" "gorm.io/gorm" - "os" - "strings" ) +var _ DB = (*SqlDB)(nil) + type SqlDB struct { sqlDB *gorm.DB } @@ -96,10 +99,37 @@ func New(config config.Config) (*SqlDB, error) { return e, nil } -func (e *SqlDB) Close() error { - sqlDB, err := e.sqlDB.DB() +func (db *SqlDB) Close() error { + sqlDB, err := db.sqlDB.DB() if err != nil { return err } return sqlDB.Close() } + +func (db *SqlDB) Create(data SQLData) error { + return db.sqlDB.Create(data).Error +} + +func (db *SqlDB) Delete(id string) error { + return db.sqlDB.Where("id = ?", id).Delete(&SQLData{}).Error +} + +func (db *SqlDB) Get(id string) (SQLData, error) { + var record SQLData + err := db.sqlDB.Model(&SQLData{}).Where("id = ?", id).First(&record).Error + if err != nil { + return SQLData{}, err + } + return record, nil +} + +func (db *SqlDB) Query(scope string, verifier string, client string) (string, error) { + var record SQLData + err := db.sqlDB.Model(&SQLData{}).Where("scope = ? AND verifier = ? AND client = ?", scope, verifier, client). + First(&record).Error + if err != nil { + return "", err + } + return record.AuthInput, nil +} diff --git a/db/interface.go b/db/interface.go index c5f8977..96400fb 100644 --- a/db/interface.go +++ b/db/interface.go @@ -1,4 +1,9 @@ package db type DB interface { + Close() error + Create(data SQLData) error + Delete(id string) error + Get(id string) (SQLData, error) + Query(scope string, verifier string, client string) (string, error) } diff --git a/db/types.go b/db/types.go new file mode 100644 index 0000000..ca31f59 --- /dev/null +++ b/db/types.go @@ -0,0 +1,17 @@ +package db + +import "gorm.io/gorm/schema" + +var _ schema.Tabler = (*SQLData)(nil) + +type SQLData struct { + Id string `gorm:"primaryKey"` + Client string + Scope string + Verifier string + AuthInput string `gorm:"column:auth_input"` +} + +func (s SQLData) TableName() string { + return "data" +} diff --git a/main.go b/main.go index a3b5871..917df0a 100644 --- a/main.go +++ b/main.go @@ -64,7 +64,7 @@ func main() { echoServer.HidePort = true // init API & register routes - pipController := &pip.Wrapper{} + pipController := &pip.Wrapper{DB: sqlDb} opaController := &opa.Wrapper{} pip.RegisterHandlers(echoServer, pip.NewStrictHandler(pipController, []pip.StrictMiddlewareFunc{})) opa.RegisterHandlers(echoServer, opa.NewStrictHandler(opaController, []opa.StrictMiddlewareFunc{})) diff --git a/oas/pip.yaml b/oas/pip.yaml index 7d88e17..24f162f 100644 --- a/oas/pip.yaml +++ b/oas/pip.yaml @@ -7,6 +7,28 @@ servers: description: Default endpoint paths: /pip/{id}: + parameters: + - name: id + in: path + required: true + description: An identifier for the data, used for deletion and updates + content: + plain/text: + schema: + type: string + example: 1111-2222-3333-4444 + get: + operationId: getData + summary: Get pip data for given ide + tags: + - pip + responses: + '200': + description: Data known for id + content: + application/json: + schema: + $ref: '#/components/schemas/Data' post: operationId: createData summary: Add authorization data used for OPA evaluation @@ -14,16 +36,6 @@ paths: Add data to the PIP. tags: - pip - parameters: - - name: id - in: path - required: true - description: An identifier for the data, used for deletion and updates - content: - plain/text: - schema: - type: string - example: 1111-2222-3333-4444 requestBody: required: true content: @@ -33,6 +45,14 @@ paths: responses: '204': description: Successful request. No content. + delete: + operationId: deleteData + summary: Delete data for given id + tags: + - pip + responses: + '204': + description: Successful request. No content. components: schemas: Data: