From e93f8b79b6335ae27f5a349c54d0416c2835286f Mon Sep 17 00:00:00 2001 From: stefanvitanov <75947010+stefanvit@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:02:13 +0300 Subject: [PATCH] [ID-106]Expose TPS API for adding images (#107) * in progress * add the Changelog.md * in progress * in progress * fix * fix lint issue * TPS is third party, not first party * Update comment * Add the tps permission policy file file in dockerfile * Please maintan the same allignment.. * Do not add the old doc annotations * More generic name * Fix path * Fix the doc description * Fix creating tps handler * Remove wrong comment --------- Co-authored-by: Stefan Vitanov Co-authored-by: Petyo Stoyanov --- CHANGELOG.md | 2 + Dockerfile | 1 + driver/web/adapter.go | 14 ++- driver/web/auth.go | 33 ++++++- .../authorization_tps_permission_policy.csv | 1 + driver/web/docs/gen/def.yaml | 51 ++++++++++ driver/web/docs/index.yaml | 4 + driver/web/docs/resources/tps/image.yaml | 50 ++++++++++ driver/web/rest/apis.go | 5 + driver/web/rest/tpsapis.go | 97 +++++++++++++++++++ 10 files changed, 254 insertions(+), 4 deletions(-) create mode 100644 driver/web/authorization_tps_permission_policy.csv create mode 100644 driver/web/docs/resources/tps/image.yaml create mode 100644 driver/web/rest/tpsapis.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 50cf7b7..d742464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Expose TPS API for adding images [#106](https://github.com/rokwire/content-building-block/issues/106) ## [1.7.0] - 2024-06-26 ### Added diff --git a/Dockerfile b/Dockerfile index 221dc73..7b331fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,7 @@ COPY --from=builder /content-app/driver/web/docs/gen/def.yaml /driver/web/docs/g COPY --from=builder /content-app/driver/web/authorization_model.conf /driver/web/authorization_model.conf COPY --from=builder /content-app/driver/web/authorization_policy.csv /driver/web/authorization_policy.csv COPY --from=builder /content-app/driver/web/authorization_bbs_permission_policy.csv /driver/web/authorization_bbs_permission_policy.csv +COPY --from=builder /content-app/driver/web/authorization_tps_permission_policy.csv /driver/web/authorization_tps_permission_policy.csv COPY --from=builder /content-app/vendor/github.com/rokwire/core-auth-library-go/v2/authorization/authorization_model_scope.conf /content-app/vendor/github.com/rokwire/core-auth-library-go/v2/authorization/authorization_model_scope.conf COPY --from=builder /content-app/vendor/github.com/rokwire/core-auth-library-go/v2/authorization/authorization_model_string.conf /content-app/vendor/github.com/rokwire/core-auth-library-go/v2/authorization/authorization_model_string.conf diff --git a/driver/web/adapter.go b/driver/web/adapter.go index f069dcf..ec0c53e 100644 --- a/driver/web/adapter.go +++ b/driver/web/adapter.go @@ -44,6 +44,7 @@ type Adapter struct { apisHandler rest.ApisHandler adminApisHandler rest.AdminApisHandler bbsApisHandler rest.BBsApisHandler + tpsApisHandler rest.TPsApisHandler app *core.Application @@ -192,7 +193,11 @@ func (we Adapter) Start() { // handle bbs apis bbsSubRouter := contentRouter.PathPrefix("/bbs").Subrouter() - bbsSubRouter.HandleFunc("/image", we.bbsAuthWrapFunc(we.bbsApisHandler.UploadImage, we.auth.bbs.Permissions)).Methods("POST") + bbsSubRouter.HandleFunc("/image", we.authWrapFunc(we.bbsApisHandler.UploadImage, we.auth.bbs.Permissions)).Methods("POST") + + // handle tps apis + tpsSubRouter := contentRouter.PathPrefix("/tps").Subrouter() + tpsSubRouter.HandleFunc("/image", we.authWrapFunc(we.tpsApisHandler.UploadImage, we.auth.tps.Permissions)).Methods("POST") log.Fatal(http.ListenAndServe(":"+we.port, router)) } @@ -268,7 +273,7 @@ func (we Adapter) coreAuthWrapFunc(handler coreAuthFunc, authorization Authoriza type bbsAuthFunc = func(*tokenauth.Claims, http.ResponseWriter, *http.Request) -func (we Adapter) bbsAuthWrapFunc(handler bbsAuthFunc, authorization tokenauth.Handler) http.HandlerFunc { +func (we Adapter) authWrapFunc(handler bbsAuthFunc, authorization tokenauth.Handler) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { utils.LogRequest(req) @@ -294,8 +299,11 @@ func NewWebAdapter(host string, port string, app *core.Application, serviceRegMa apisHandler := rest.NewApisHandler(app) adminApisHandler := rest.NewAdminApisHandler(app) bbsApisHandler := rest.NewBBSApisHandler(app) + tpsApisHandler := rest.NewTPSApisHandler(app) return Adapter{host: host, port: port, cachedYamlDoc: yamlDoc, auth: auth, - apisHandler: apisHandler, adminApisHandler: adminApisHandler, bbsApisHandler: bbsApisHandler, app: app, logger: logger} + apisHandler: apisHandler, adminApisHandler: adminApisHandler, + bbsApisHandler: bbsApisHandler, tpsApisHandler: tpsApisHandler, + app: app, logger: logger} } // AppListener implements core.ApplicationListener interface diff --git a/driver/web/auth.go b/driver/web/auth.go index 0061eba..57dcdaf 100644 --- a/driver/web/auth.go +++ b/driver/web/auth.go @@ -37,6 +37,7 @@ type Authorization interface { type Auth struct { coreAuth *CoreAuth bbs tokenauth.Handlers + tps tokenauth.Handlers logger *logs.Logger } @@ -50,7 +51,13 @@ func NewAuth(app *core.Application, serviceRegManager *authservice.ServiceRegMan } bbsHandlers := tokenauth.NewHandlers(bbsStandardHandler) //add permissions, user and authenticated - auth := Auth{coreAuth: coreAuth, bbs: bbsHandlers, logger: logger} + tpsStandardHandler, err := newTPsStandardHandler(serviceRegManager) + if err != nil { + return nil + } + tpsHandlers := tokenauth.NewHandlers(tpsStandardHandler) //add permissions, user and authenticated + + auth := Auth{coreAuth: coreAuth, bbs: bbsHandlers, tps: tpsHandlers, logger: logger} return &auth } @@ -105,6 +112,30 @@ func newBBsStandardHandler(serviceRegManager *authservice.ServiceRegManager) (*t return &auth, nil } +// TPs auth /////////// +func newTPsStandardHandler(serviceRegManager *authservice.ServiceRegManager) (*tokenauth.StandardHandler, error) { + tpsPermissionAuth := authorization.NewCasbinStringAuthorization("driver/web/authorization_tps_permission_policy.csv") + tpsTokenAuth, err := tokenauth.NewTokenAuth(true, serviceRegManager, tpsPermissionAuth, nil) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionCreate, "tps token auth", nil, err) + } + + check := func(claims *tokenauth.Claims, req *http.Request) (int, error) { + if !claims.Service { + return http.StatusUnauthorized, errors.ErrorData(logutils.StatusInvalid, "service claim", nil) + } + + if claims.FirstParty { + return http.StatusUnauthorized, errors.ErrorData(logutils.StatusInvalid, "first party claim", nil) + } + + return http.StatusOK, nil + } + + auth := tokenauth.NewStandardHandler(*tpsTokenAuth, check) + return &auth, nil +} + // PermissionsAuth entity // This enforces that the user has permissions matching the policy type PermissionsAuth struct { diff --git a/driver/web/authorization_tps_permission_policy.csv b/driver/web/authorization_tps_permission_policy.csv new file mode 100644 index 0000000..2cc1db3 --- /dev/null +++ b/driver/web/authorization_tps_permission_policy.csv @@ -0,0 +1 @@ +p, tps_upload-images, /content/tps/image, (POST), Allow uploading images \ No newline at end of file diff --git a/driver/web/docs/gen/def.yaml b/driver/web/docs/gen/def.yaml index 69afc25..940b4f7 100644 --- a/driver/web/docs/gen/def.yaml +++ b/driver/web/docs/gen/def.yaml @@ -2869,6 +2869,57 @@ paths: description: Unauthorized '500': description: Internal error + /tps/image: + post: + tags: + - TPs + summary: Uploads an image to the Rokwire system + description: | + Uploads an image to the Rokwire system + security: + - bearerAuth: [] + parameters: + - name: height + in: query + description: height of the image to resize. + required: true + style: form + explode: false + schema: + type: string + - name: width + in: query + description: width of the image to resize + required: true + style: form + explode: false + schema: + type: string + - name: quality + in: query + description: quality of the image. Default - 100 + required: true + style: form + explode: false + schema: + type: string + - name: fileName + in: query + description: the uploaded file name + required: true + style: form + explode: false + schema: + type: string + responses: + '200': + description: Success + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error components: securitySchemes: bearerAuth: diff --git a/driver/web/docs/index.yaml b/driver/web/docs/index.yaml index b6ba0bb..913c030 100644 --- a/driver/web/docs/index.yaml +++ b/driver/web/docs/index.yaml @@ -113,6 +113,10 @@ paths: #BBs /bbs/image: $ref: "./resources/bbs/image.yaml" + + #TPs + /tps/image: + $ref: "./resources/tps/image.yaml" components: securitySchemes: diff --git a/driver/web/docs/resources/tps/image.yaml b/driver/web/docs/resources/tps/image.yaml new file mode 100644 index 0000000..6b1bc8a --- /dev/null +++ b/driver/web/docs/resources/tps/image.yaml @@ -0,0 +1,50 @@ +post: + tags: + - TPs + summary: Uploads an image to the Rokwire system + description: | + Uploads an image to the Rokwire system + security: + - bearerAuth: [] + parameters: + - name: height + in: query + description: height of the image to resize. + required: true + style: form + explode: false + schema: + type: string + - name: width + in: query + description: width of the image to resize + required: true + style: form + explode: false + schema: + type: string + - name: quality + in: query + description: quality of the image. Default - 100 + required: true + style: form + explode: false + schema: + type: string + - name: fileName + in: query + description: the uploaded file name + required: true + style: form + explode: false + schema: + type: string + responses: + 200: + description: Success + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error diff --git a/driver/web/rest/apis.go b/driver/web/rest/apis.go index 0b14737..322f1dd 100644 --- a/driver/web/rest/apis.go +++ b/driver/web/rest/apis.go @@ -868,3 +868,8 @@ func NewAdminApisHandler(app *core.Application) AdminApisHandler { func NewBBSApisHandler(app *core.Application) BBsApisHandler { return BBsApisHandler{app: app} } + +// NewTPSApisHandler creates new rest Handler instance +func NewTPSApisHandler(app *core.Application) TPsApisHandler { + return TPsApisHandler{app: app} +} diff --git a/driver/web/rest/tpsapis.go b/driver/web/rest/tpsapis.go new file mode 100644 index 0000000..3da163c --- /dev/null +++ b/driver/web/rest/tpsapis.go @@ -0,0 +1,97 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rest + +import ( + "content/core" + "content/core/model" + "encoding/json" + "io/ioutil" + "log" + "net/http" + + "github.com/rokwire/core-auth-library-go/v2/tokenauth" +) + +// TPsApisHandler handles the rest TPs APIs implementation +type TPsApisHandler struct { + app *core.Application +} + +// UploadImage Uploads an image to the Rokwire system +func (h TPsApisHandler) UploadImage(claims *tokenauth.Claims, w http.ResponseWriter, r *http.Request) { + path := "tps-images" + + heightParam := intPostValueFromString(r.PostFormValue("height")) + widthParam := intPostValueFromString(r.PostFormValue("width")) + qualityParam := intPostValueFromString(r.PostFormValue("quality")) + imgSpec := model.ImageSpec{Height: heightParam, Width: widthParam, Quality: qualityParam} + + // validate file size + r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize) + if err := r.ParseMultipartForm(maxUploadSize); err != nil { + log.Print("File is too big\n") + http.Error(w, "File is too big", http.StatusBadRequest) + return + } + + // parse and validate file and post parameters + file, _, err := r.FormFile("fileName") + if err != nil { + log.Print("Invalid file\n") + http.Error(w, "Invalid file", http.StatusBadRequest) + return + } + defer file.Close() + fileBytes, err := ioutil.ReadAll(file) + if err != nil { + log.Print("Invalid file\n") + http.Error(w, "Invalid file", http.StatusBadRequest) + return + } + + // check file type, detectcontenttype only needs the first 512 bytes + filetype := http.DetectContentType(fileBytes) + switch filetype { + case "image/jpeg", "image/jpg": + case "image/gif", "image/png": + case "image/webp": + break + default: + log.Print("Invalid file type\n") + http.Error(w, "Invalid file type", http.StatusBadRequest) + return + } + + // pass the file to be processed by the use case handler + url, err := h.app.Services.UploadImage(fileBytes, path, imgSpec) + if err != nil { + log.Printf("Error converting image: %s\n", err) + http.Error(w, "Error converting image", http.StatusInternalServerError) + return + } + + jsonData := map[string]string{"url": *url} + jsonBynaryData, err := json.Marshal(jsonData) + if err != nil { + log.Println("Error on marshal s3 location data") + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(jsonBynaryData) +}