From cb79c38647e274ef73d6d8c0346453efb911cba4 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Tue, 17 Aug 2021 17:50:34 +0200 Subject: [PATCH] owncloud/ocs: Move to go-chi/chi based URL routing This should help with code readability and make it easier to figure out which code is executed for which URL. Closes #1986 --- .../owncloud-ocs-move-to-go-chi-routing.md | 4 + go.mod | 1 + go.sum | 2 + .../owncloud/ocs/handlers/apps/apps.go | 72 ---------- .../apps/notifications/notifications.go | 41 ------ .../handlers/apps/sharing/sharees/sharees.go | 19 +-- .../handlers/apps/sharing/shares/pending.go | 13 ++ .../handlers/apps/sharing/shares/remote.go | 8 +- .../handlers/apps/sharing/shares/shares.go | 128 +++++------------- .../ocs/handlers/apps/sharing/sharing.go | 68 ---------- .../cloud/capabilities/capabilities.go | 8 +- .../owncloud/ocs/handlers/cloud/cloud.go | 64 --------- .../owncloud/ocs/handlers/cloud/user/user.go | 3 +- .../ocs/handlers/cloud/users/users.go | 59 ++++---- .../owncloud/ocs/handlers/config/config.go | 6 +- internal/http/services/owncloud/ocs/ocs.go | 96 +++++++++---- .../owncloud/ocs/response/response.go | 22 ++- internal/http/services/owncloud/ocs/v1.go | 66 --------- 18 files changed, 184 insertions(+), 496 deletions(-) create mode 100644 changelog/unreleased/owncloud-ocs-move-to-go-chi-routing.md delete mode 100644 internal/http/services/owncloud/ocs/handlers/apps/apps.go delete mode 100644 internal/http/services/owncloud/ocs/handlers/apps/notifications/notifications.go delete mode 100644 internal/http/services/owncloud/ocs/handlers/apps/sharing/sharing.go delete mode 100644 internal/http/services/owncloud/ocs/handlers/cloud/cloud.go delete mode 100644 internal/http/services/owncloud/ocs/v1.go diff --git a/changelog/unreleased/owncloud-ocs-move-to-go-chi-routing.md b/changelog/unreleased/owncloud-ocs-move-to-go-chi-routing.md new file mode 100644 index 00000000000..ca3e8063e64 --- /dev/null +++ b/changelog/unreleased/owncloud-ocs-move-to-go-chi-routing.md @@ -0,0 +1,4 @@ +Enhancement: Move owncloud/ocs API to go-chi/chi based URL routing + +https://github.com/cs3org/reva/pull/2006 +https://github.com/cs3org/reva/issues/1986 diff --git a/go.mod b/go.mod index 485d8217557..72439c90093 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/cs3org/go-cs3apis v0.0.0-20210802070913-970eec344e59 github.com/eventials/go-tus v0.0.0-20200718001131-45c7ec8f5d59 github.com/gdexlab/go-render v1.0.1 + github.com/go-chi/chi/v5 v5.0.3 github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-openapi/errors v0.20.0 // indirect github.com/go-openapi/strfmt v0.19.5 // indirect diff --git a/go.sum b/go.sum index e2122d1a8eb..ecda1fbf025 100644 --- a/go.sum +++ b/go.sum @@ -111,6 +111,8 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4= +github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= diff --git a/internal/http/services/owncloud/ocs/handlers/apps/apps.go b/internal/http/services/owncloud/ocs/handlers/apps/apps.go deleted file mode 100644 index 019f1249393..00000000000 --- a/internal/http/services/owncloud/ocs/handlers/apps/apps.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2018-2021 CERN -// -// 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. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package apps - -import ( - "net/http" - - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/config" - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/apps/notifications" - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/apps/sharing" - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" - "github.com/cs3org/reva/pkg/rhttp/router" -) - -// Handler holds references to individual app handlers -type Handler struct { - SharingHandler *sharing.Handler - NotificationsHandler *notifications.Handler -} - -// Init initializes this and any contained handlers -func (h *Handler) Init(c *config.Config) error { - h.SharingHandler = new(sharing.Handler) - h.NotificationsHandler = new(notifications.Handler) - return h.SharingHandler.Init(c) -} - -// ServeHTTP routes the known apps -func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - var head string - head, r.URL.Path = router.ShiftPath(r.URL.Path) - switch head { - case "files_sharing": - head, r.URL.Path = router.ShiftPath(r.URL.Path) - if head == "api" { - head, r.URL.Path = router.ShiftPath(r.URL.Path) - if head == "v1" { - h.SharingHandler.ServeHTTP(w, r) - return - } - } - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "Not found", nil) - case "notifications": - head, r.URL.Path = router.ShiftPath(r.URL.Path) - if head == "api" { - head, r.URL.Path = router.ShiftPath(r.URL.Path) - if head == "v1" { - h.NotificationsHandler.ServeHTTP(w, r) - return - } - } - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "Not found", nil) - default: - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "Not found", nil) - } -} diff --git a/internal/http/services/owncloud/ocs/handlers/apps/notifications/notifications.go b/internal/http/services/owncloud/ocs/handlers/apps/notifications/notifications.go deleted file mode 100644 index 9d966af2f18..00000000000 --- a/internal/http/services/owncloud/ocs/handlers/apps/notifications/notifications.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2018-2021 CERN -// -// 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. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package notifications - -import ( - "net/http" - - "github.com/cs3org/reva/pkg/appctx" - "github.com/cs3org/reva/pkg/rhttp/router" -) - -// Handler placeholder for notifications -type Handler struct { -} - -func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - log := appctx.GetLogger(r.Context()) - - var head string - head, r.URL.Path = router.ShiftPath(r.URL.Path) - - log.Debug().Str("head", head).Str("tail", r.URL.Path).Msg("http routing") - - w.WriteHeader(http.StatusOK) -} diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go index ea8ad4a95a7..f15192d7258 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go @@ -29,7 +29,6 @@ import ( "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/pkg/rhttp/router" "github.com/cs3org/reva/pkg/storage/utils/templates" ) @@ -40,24 +39,13 @@ type Handler struct { } // Init initializes this and any contained handlers -func (h *Handler) Init(c *config.Config) error { +func (h *Handler) Init(c *config.Config) { h.gatewayAddr = c.GatewaySvc h.additionalInfoAttribute = c.AdditionalInfoAttribute - return nil } -func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - log := appctx.GetLogger(r.Context()) - - var head string - head, r.URL.Path = router.ShiftPath(r.URL.Path) - - log.Debug().Str("head", head).Str("tail", r.URL.Path).Msg("http routing") - - h.findSharees(w, r) -} - -func (h *Handler) findSharees(w http.ResponseWriter, r *http.Request) { +// FindSharees implements the /apps/files_sharing/api/v1/sharees endpoint +func (h *Handler) FindSharees(w http.ResponseWriter, r *http.Request) { log := appctx.GetLogger(r.Context()) term := r.URL.Query().Get("search") @@ -71,7 +59,6 @@ func (h *Handler) findSharees(w http.ResponseWriter, r *http.Request) { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting gateway grpc client", err) return } - usersRes, err := gwc.FindUsers(r.Context(), &userpb.FindUsersRequest{Filter: term}) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching users", err) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go index 71b83b23894..76bc15fbef6 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go @@ -28,9 +28,22 @@ import ( "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/go-chi/chi/v5" "github.com/pkg/errors" ) +// AcceptReceivedShare handles Post Requests on /apps/files_sharing/api/v1/shares/{shareid} +func (h *Handler) AcceptReceivedShare(w http.ResponseWriter, r *http.Request) { + shareID := chi.URLParam(r, "shareid") + h.updateReceivedShare(w, r, shareID, false) +} + +// RejectReceivedShare handles DELETE Requests on /apps/files_sharing/api/v1/shares/{shareid} +func (h *Handler) RejectReceivedShare(w http.ResponseWriter, r *http.Request) { + shareID := chi.URLParam(r, "shareid") + h.updateReceivedShare(w, r, shareID, true) +} + func (h *Handler) updateReceivedShare(w http.ResponseWriter, r *http.Request, shareID string, rejectShare bool) { ctx := r.Context() logger := appctx.GetLogger(ctx) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go index ab7f38ce1c1..bdc77662a78 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go @@ -29,6 +29,7 @@ import ( ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/go-chi/chi/v5" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" @@ -119,11 +120,13 @@ func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Reque response.WriteOCSSuccess(w, r, "OCM Share created") } -func (h *Handler) getFederatedShare(w http.ResponseWriter, r *http.Request, shareID string) { +// GetFederatedShare handles GET requests on /apps/files_sharing/api/v1/shares/remote_shares/{shareid} +func (h *Handler) GetFederatedShare(w http.ResponseWriter, r *http.Request) { // TODO: Implement response with HAL schemating ctx := r.Context() + shareID := chi.URLParam(r, "shareid") gatewayClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) @@ -153,7 +156,8 @@ func (h *Handler) getFederatedShare(w http.ResponseWriter, r *http.Request, shar response.WriteOCSSuccess(w, r, share) } -func (h *Handler) listFederatedShares(w http.ResponseWriter, r *http.Request) { +// ListFederatedShares handles GET requests on /apps/files_sharing/api/v1/shares/remote_shares +func (h *Handler) ListFederatedShares(w http.ResponseWriter, r *http.Request) { // TODO Implement pagination. // TODO Implement response with HAL schemating diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index ce315925bb9..31409e512d9 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -39,6 +39,7 @@ import ( collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/go-chi/chi/v5" "github.com/rs/zerolog/log" "github.com/ReneKroon/ttlcache/v2" @@ -49,7 +50,6 @@ import ( "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/pkg/rhttp/router" "github.com/cs3org/reva/pkg/share/cache" "github.com/cs3org/reva/pkg/share/cache/registry" "github.com/cs3org/reva/pkg/utils" @@ -83,7 +83,7 @@ func getCacheWarmupManager(c *config.Config) (cache.Warmup, error) { } // Init initializes this and any contained handlers -func (h *Handler) Init(c *config.Config) error { +func (h *Handler) Init(c *config.Config) { h.gatewayAddr = c.GatewaySvc h.publicURL = c.Config.Host h.sharePrefix = c.SharePrefix @@ -102,8 +102,6 @@ func (h *Handler) Init(c *config.Config) error { go h.startCacheWarmup(cwm) } } - - return nil } func (h *Handler) startCacheWarmup(c cache.Warmup) { @@ -117,89 +115,8 @@ func (h *Handler) startCacheWarmup(c cache.Warmup) { } } -func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - log := appctx.GetLogger(r.Context()) - - var head string - head, r.URL.Path = router.ShiftPath(r.URL.Path) - - log.Debug().Str("head", head).Str("tail", r.URL.Path).Msg("http routing") - - switch head { - case "": - switch r.Method { - case "OPTIONS": - w.WriteHeader(http.StatusOK) - case "GET": - if h.isListSharesWithMe(w, r) { - h.listSharesWithMe(w, r) - } else { - h.listSharesWithOthers(w, r) - } - case "POST": - h.createShare(w, r) - default: - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Only GET, POST and PUT are allowed", nil) - } - - case "pending": - var shareID string - shareID, r.URL.Path = router.ShiftPath(r.URL.Path) - - log.Debug().Str("share_id", shareID).Str("tail", r.URL.Path).Msg("http routing") - - switch r.Method { - case "POST": - h.updateReceivedShare(w, r, shareID, false) - case "DELETE": - h.updateReceivedShare(w, r, shareID, true) - default: - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Only POST and DELETE are allowed", nil) - } - - case "remote_shares": - var shareID string - shareID, r.URL.Path = router.ShiftPath(r.URL.Path) - - log.Debug().Str("share_id", shareID).Str("tail", r.URL.Path).Msg("http routing") - - switch r.Method { - case "GET": - if shareID == "" { - h.listFederatedShares(w, r) - } else { - h.getFederatedShare(w, r, shareID) - } - default: - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Only GET method is allowed", nil) - } - - default: - switch r.Method { - case "GET": - h.getShare(w, r, head) - case "PUT": - // FIXME: isPublicShare is already doing a GetShare and GetPublicShare, - // we should just reuse that object when doing updates - if h.isPublicShare(r, strings.ReplaceAll(head, "/", "")) { - h.updatePublicShare(w, r, strings.ReplaceAll(head, "/", "")) - return - } - h.updateShare(w, r, head) // TODO PUT is used with incomplete data to update a share - case "DELETE": - shareID := strings.ReplaceAll(head, "/", "") - if h.isPublicShare(r, shareID) { - h.removePublicShare(w, r, shareID) - return - } - h.removeUserShare(w, r, head) - default: - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Only GET, POST and PUT are allowed", nil) - } - } -} - -func (h *Handler) createShare(w http.ResponseWriter, r *http.Request) { +// CreateShare handles POST requests on /apps/files_sharing/api/v1/shares +func (h *Handler) CreateShare(w http.ResponseWriter, r *http.Request) { ctx := r.Context() shareType, err := strconv.Atoi(r.FormValue("shareType")) if err != nil { @@ -329,9 +246,11 @@ func (h *Handler) extractPermissions(w http.ResponseWriter, r *http.Request, ri // PublicShareContextName represent cross boundaries context for the name of the public share type PublicShareContextName string -func (h *Handler) getShare(w http.ResponseWriter, r *http.Request, shareID string) { +// GetShare handles GET requests on /apps/files_sharing/api/v1/shares/(shareid) +func (h *Handler) GetShare(w http.ResponseWriter, r *http.Request) { var share *conversions.ShareData var resourceID *provider.ResourceId + shareID := chi.URLParam(r, "shareid") ctx := r.Context() logger := appctx.GetLogger(r.Context()) logger.Debug().Str("shareID", shareID).Msg("get share by id") @@ -442,6 +361,18 @@ func (h *Handler) getShare(w http.ResponseWriter, r *http.Request, shareID strin response.WriteOCSSuccess(w, r, []*conversions.ShareData{share}) } +// UpdateShare handles PUT requests on /apps/files_sharing/api/v1/shares/(shareid) +func (h *Handler) UpdateShare(w http.ResponseWriter, r *http.Request) { + shareID := chi.URLParam(r, "shareid") + // FIXME: isPublicShare is already doing a GetShare and GetPublicShare, + // we should just reuse that object when doing updates + if h.isPublicShare(r, shareID) { + h.updatePublicShare(w, r, shareID) + return + } + h.updateShare(w, r, shareID) // TODO PUT is used with incomplete data to update a share} +} + func (h *Handler) updateShare(w http.ResponseWriter, r *http.Request, shareID string) { ctx := r.Context() @@ -537,15 +468,30 @@ func (h *Handler) updateShare(w http.ResponseWriter, r *http.Request, shareID st response.WriteOCSSuccess(w, r, share) } -func (h *Handler) isListSharesWithMe(w http.ResponseWriter, r *http.Request) (listSharedWithMe bool) { +// RemoveShare handles DELETE requests on /apps/files_sharing/api/v1/shares/(shareid) +func (h *Handler) RemoveShare(w http.ResponseWriter, r *http.Request) { + shareID := chi.URLParam(r, "shareid") + if h.isPublicShare(r, shareID) { + h.removePublicShare(w, r, shareID) + return + } + h.removeUserShare(w, r, shareID) +} + +// ListShares handles GET requests on /apps/files_sharing/api/v1/shares +func (h *Handler) ListShares(w http.ResponseWriter, r *http.Request) { if r.FormValue("shared_with_me") != "" { var err error - listSharedWithMe, err = strconv.ParseBool(r.FormValue("shared_with_me")) + listSharedWithMe, err := strconv.ParseBool(r.FormValue("shared_with_me")) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err) } + if listSharedWithMe { + h.listSharesWithMe(w, r) + return + } } - return + h.listSharesWithOthers(w, r) } const ( diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharing.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharing.go deleted file mode 100644 index ed3df32a1cf..00000000000 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharing.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2018-2021 CERN -// -// 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. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package sharing - -import ( - "net/http" - - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/config" - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees" - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares" - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" - "github.com/cs3org/reva/pkg/appctx" - "github.com/cs3org/reva/pkg/rhttp/router" -) - -// Handler implements the ownCloud sharing API -type Handler struct { - gatewayAddr string - SharesHandler *shares.Handler - ShareesHandler *sharees.Handler -} - -// Init initializes this and any contained handlers -func (h *Handler) Init(c *config.Config) error { - h.gatewayAddr = c.GatewaySvc - h.SharesHandler = new(shares.Handler) - h.ShareesHandler = new(sharees.Handler) - if err := h.SharesHandler.Init(c); err != nil { - return err - } - - return h.ShareesHandler.Init(c) - -} - -func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - log := appctx.GetLogger(r.Context()) - - var head string - head, r.URL.Path = router.ShiftPath(r.URL.Path) - - log.Debug().Str("head", head).Str("tail", r.URL.Path).Msg("http routing") - - switch head { - case "shares": - h.SharesHandler.ServeHTTP(w, r) - case "sharees": - h.ShareesHandler.ServeHTTP(w, r) - default: - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "Not found", nil) - } -} diff --git a/internal/http/services/owncloud/ocs/handlers/cloud/capabilities/capabilities.go b/internal/http/services/owncloud/ocs/handlers/cloud/capabilities/capabilities.go index 35b7324fb15..6a4e572862d 100644 --- a/internal/http/services/owncloud/ocs/handlers/cloud/capabilities/capabilities.go +++ b/internal/http/services/owncloud/ocs/handlers/cloud/capabilities/capabilities.go @@ -217,9 +217,7 @@ func (h *Handler) Init(c *config.Config) { } // Handler renders the capabilities -func (h *Handler) Handler() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c := h.getCapabilitiesForUserAgent(r.UserAgent()) - response.WriteOCSSuccess(w, r, c) - }) +func (h *Handler) GetCapabilities(w http.ResponseWriter, r *http.Request) { + c := h.getCapabilitiesForUserAgent(r.UserAgent()) + response.WriteOCSSuccess(w, r, c) } diff --git a/internal/http/services/owncloud/ocs/handlers/cloud/cloud.go b/internal/http/services/owncloud/ocs/handlers/cloud/cloud.go deleted file mode 100644 index 8b099b7b0ca..00000000000 --- a/internal/http/services/owncloud/ocs/handlers/cloud/cloud.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2018-2021 CERN -// -// 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. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package cloud - -import ( - "net/http" - - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/config" - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/cloud/capabilities" - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/cloud/user" - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/cloud/users" - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" - "github.com/cs3org/reva/pkg/rhttp/router" -) - -// Handler holds references to UserHandler and CapabilitiesHandler -type Handler struct { - UserHandler *user.Handler - UsersHandler *users.Handler - CapabilitiesHandler *capabilities.Handler -} - -// Init initializes this and any contained handlers -func (h *Handler) Init(c *config.Config) error { - h.UserHandler = new(user.Handler) - h.CapabilitiesHandler = new(capabilities.Handler) - h.CapabilitiesHandler.Init(c) - h.UsersHandler = new(users.Handler) - return h.UsersHandler.Init(c) -} - -// Handler routes the cloud endpoints -func (h *Handler) Handler() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var head string - head, r.URL.Path = router.ShiftPath(r.URL.Path) - switch head { - case "capabilities": - h.CapabilitiesHandler.Handler().ServeHTTP(w, r) - case "user": - h.UserHandler.ServeHTTP(w, r) - case "users": - h.UsersHandler.ServeHTTP(w, r) - default: - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "Not found", nil) - } - }) -} diff --git a/internal/http/services/owncloud/ocs/handlers/cloud/user/user.go b/internal/http/services/owncloud/ocs/handlers/cloud/user/user.go index ed6eb3d1f38..e1feaaa1fda 100644 --- a/internal/http/services/owncloud/ocs/handlers/cloud/user/user.go +++ b/internal/http/services/owncloud/ocs/handlers/cloud/user/user.go @@ -30,7 +30,8 @@ import ( type Handler struct { } -func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { +// GetSelf handles GET requests on /cloud/user +func (h *Handler) GetSelf(w http.ResponseWriter, r *http.Request) { ctx := r.Context() // TODO move user to handler parameter? diff --git a/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go b/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go index 8cfb52f4d4f..c8f7b1f9125 100644 --- a/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go +++ b/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go @@ -32,7 +32,7 @@ import ( "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/pkg/rhttp/router" + "github.com/go-chi/chi/v5" ) // Handler renders user data for the user id given in the url path @@ -41,43 +41,14 @@ type Handler struct { } // Init initializes this and any contained handlers -func (h *Handler) Init(c *config.Config) error { +func (h *Handler) Init(c *config.Config) { h.gatewayAddr = c.GatewaySvc - return nil } -func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var user string - user, r.URL.Path = router.ShiftPath(r.URL.Path) - - // FIXME use ldap to fetch user info - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "missing user in context", fmt.Errorf("missing user in context")) - return - } - if user != u.Username { - // FIXME allow fetching other users info? only for admins - response.WriteOCSError(w, r, http.StatusForbidden, "user id mismatch", fmt.Errorf("%s tried to access %s user info endpoint", u.Id.OpaqueId, user)) - return - } - - var head string - head, r.URL.Path = router.ShiftPath(r.URL.Path) - switch head { - case "": - h.handleUsers(w, r, u) - return - case "groups": - response.WriteOCSSuccess(w, r, &Groups{}) - return - default: - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "Not found", nil) - return - } - +// GetGroups handles GET requests on /cloud/users/groups +// TODO: implement +func (h *Handler) GetGroups(w http.ResponseWriter, r *http.Request) { + response.WriteOCSSuccess(w, r, &Groups{}) } // Quota holds quota information @@ -104,10 +75,26 @@ type Groups struct { Groups []string `json:"groups" xml:"groups>element"` } -func (h *Handler) handleUsers(w http.ResponseWriter, r *http.Request, u *userpb.User) { +// GetUsers handles GET requests on /cloud/users +// Only allow self-read currently. TODO: List Users and Get on other users (both require +// administrative privileges) +func (h *Handler) GetUsers(w http.ResponseWriter, r *http.Request) { ctx := r.Context() sublog := appctx.GetLogger(r.Context()) + user := chi.URLParam(r, "userid") + // FIXME use ldap to fetch user info + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "missing user in context", fmt.Errorf("missing user in context")) + return + } + if user != u.Username { + // FIXME allow fetching other users info? only for admins + response.WriteOCSError(w, r, http.StatusForbidden, "user id mismatch", fmt.Errorf("%s tried to access %s user info endpoint", u.Id.OpaqueId, user)) + return + } + gc, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { sublog.Error().Err(err).Msg("error getting gateway client") diff --git a/internal/http/services/owncloud/ocs/handlers/config/config.go b/internal/http/services/owncloud/ocs/handlers/config/config.go index e010094f16a..852e12b8133 100644 --- a/internal/http/services/owncloud/ocs/handlers/config/config.go +++ b/internal/http/services/owncloud/ocs/handlers/config/config.go @@ -53,8 +53,6 @@ func (h *Handler) Init(c *config.Config) { } // Handler renders the config -func (h *Handler) Handler() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - response.WriteOCSSuccess(w, r, h.c) - }) +func (h *Handler) GetConfig(w http.ResponseWriter, r *http.Request) { + response.WriteOCSSuccess(w, r, h.c) } diff --git a/internal/http/services/owncloud/ocs/ocs.go b/internal/http/services/owncloud/ocs/ocs.go index a82e0c24632..e50e9467ff8 100644 --- a/internal/http/services/owncloud/ocs/ocs.go +++ b/internal/http/services/owncloud/ocs/ocs.go @@ -22,29 +22,29 @@ import ( "net/http" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/config" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/cloud/capabilities" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/cloud/user" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/cloud/users" + configHandler "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/config" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp/global" - "github.com/cs3org/reva/pkg/rhttp/router" + "github.com/go-chi/chi/v5" "github.com/mitchellh/mapstructure" "github.com/rs/zerolog" ) -const ( - apiV1 = "v1.php" - apiV2 = "v2.php" -) - func init() { global.Register("ocs", New) } type svc struct { - c *config.Config - V1Handler *V1Handler + c *config.Config + router *chi.Mux } -// New returns a new capabilitiessvc func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) { conf := &config.Config{} if err := mapstructure.Decode(m, conf); err != nil { @@ -53,13 +53,13 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) conf.Init() + r := chi.NewRouter() s := &svc{ - c: conf, - V1Handler: new(V1Handler), + c: conf, + router: r, } - // initialize handlers and set default configs - if err := s.V1Handler.init(conf); err != nil { + if err := s.routerInit(); err != nil { return nil, err } @@ -78,20 +78,66 @@ func (s *svc) Unprotected() []string { return []string{} } +func (s *svc) routerInit() error { + capabilitiesHandler := new(capabilities.Handler) + userHandler := new(user.Handler) + usersHandler := new(users.Handler) + configHandler := new(configHandler.Handler) + sharesHandler := new(shares.Handler) + shareesHandler := new(sharees.Handler) + capabilitiesHandler.Init(s.c) + usersHandler.Init(s.c) + configHandler.Init(s.c) + sharesHandler.Init(s.c) + shareesHandler.Init(s.c) + + s.router.Route("/v{version:(1|2)}.php", func(r chi.Router) { + r.Use(response.VersionCtx) + r.Route("/apps/files_sharing/api/v1", func(r chi.Router) { + r.Route("/shares", func(r chi.Router) { + r.Get("/", sharesHandler.ListShares) + r.Options("/", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + r.Post("/", sharesHandler.CreateShare) + r.Route("/pending/{shareid}", func(r chi.Router) { + r.Post("/", sharesHandler.AcceptReceivedShare) + r.Delete("/", sharesHandler.RejectReceivedShare) + }) + r.Route("/remote_shares", func(r chi.Router) { + r.Get("/", sharesHandler.ListFederatedShares) + r.Get("/{shareid}", sharesHandler.GetFederatedShare) + }) + r.Get("/{shareid}", sharesHandler.GetShare) + r.Put("/{shareid}", sharesHandler.UpdateShare) + r.Delete("/{shareid}", sharesHandler.RemoveShare) + }) + r.Get("/sharees", shareesHandler.FindSharees) + }) + + // placeholder for notifications + r.Get("/apps/notifications/api/v1/notifications", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + r.Get("/config", configHandler.GetConfig) + + r.Route("/cloud", func(r chi.Router) { + r.Get("/capabilities", capabilitiesHandler.GetCapabilities) + r.Get("/user", userHandler.GetSelf) + r.Route("/users", func(r chi.Router) { + r.Get("/{userid}", usersHandler.GetUsers) + r.Get("/{userid}/groups", usersHandler.GetGroups) + }) + }) + }) + return nil +} + func (s *svc) Handler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log := appctx.GetLogger(r.Context()) - - var head string - head, r.URL.Path = router.ShiftPath(r.URL.Path) - - log.Debug().Str("head", head).Str("tail", r.URL.Path).Msg("ocs routing") - - if !(head == apiV1 || head == apiV2) { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "Not found", nil) - return - } - ctx := response.WithAPIVersion(r.Context(), head) - s.V1Handler.Handler().ServeHTTP(w, r.WithContext(ctx)) + log.Debug().Str("path", r.URL.Path).Msg("ocs routing") + s.router.ServeHTTP(w, r) }) } diff --git a/internal/http/services/owncloud/ocs/response/response.go b/internal/http/services/owncloud/ocs/response/response.go index abf9628407b..f92af90875f 100644 --- a/internal/http/services/owncloud/ocs/response/response.go +++ b/internal/http/services/owncloud/ocs/response/response.go @@ -27,6 +27,7 @@ import ( "reflect" "github.com/cs3org/reva/pkg/appctx" + "github.com/go-chi/chi/v5" ) type key int @@ -155,7 +156,7 @@ func WriteOCSResponse(w http.ResponseWriter, r *http.Request, res Response, err version := APIVersion(r.Context()) m := statusCodeMapper(version) statusCode := m(res.OCS.Meta) - if version == "v2.php" && statusCode == http.StatusOK { + if version == "2" && statusCode == http.StatusOK { res.OCS.Meta.StatusCode = statusCode } @@ -233,8 +234,19 @@ func OcsV2StatusCodes(meta Meta) int { } // WithAPIVersion puts the api version in the context. -func WithAPIVersion(parent context.Context, version string) context.Context { - return context.WithValue(parent, apiVersionKey, version) +func VersionCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + version := chi.URLParam(r, "version") + if version == "" { + WriteOCSError(w, r, MetaBadRequest.StatusCode, "unknown ocs api version", nil) + return + } + w.Header().Set("Ocs-Api-Version", version) + + // store version in context so handlers can access it + ctx := context.WithValue(r.Context(), apiVersionKey, version) + next.ServeHTTP(w, r.WithContext(ctx)) + }) } // APIVersion retrieves the api version from the context. @@ -249,9 +261,9 @@ func APIVersion(ctx context.Context) string { func statusCodeMapper(version string) func(Meta) int { var mapper func(Meta) int switch version { - case "v1.php": + case "1": mapper = OcsV1StatusCodes - case "v2.php": + case "2": mapper = OcsV2StatusCodes default: mapper = defaultStatusCodeMapper diff --git a/internal/http/services/owncloud/ocs/v1.go b/internal/http/services/owncloud/ocs/v1.go deleted file mode 100644 index f22f4d5392a..00000000000 --- a/internal/http/services/owncloud/ocs/v1.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2018-2021 CERN -// -// 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. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package ocs - -import ( - "net/http" - - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/config" - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/apps" - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/cloud" - configHandler "github.com/cs3org/reva/internal/http/services/owncloud/ocs/handlers/config" - "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" - "github.com/cs3org/reva/pkg/rhttp/router" -) - -// V1Handler routes to the different sub handlers -type V1Handler struct { - AppsHandler *apps.Handler - CloudHandler *cloud.Handler - ConfigHandler *configHandler.Handler -} - -func (h *V1Handler) init(c *config.Config) error { - h.ConfigHandler = new(configHandler.Handler) - h.ConfigHandler.Init(c) - h.AppsHandler = new(apps.Handler) - if err := h.AppsHandler.Init(c); err != nil { - return err - } - h.CloudHandler = new(cloud.Handler) - return h.CloudHandler.Init(c) -} - -// Handler handles requests -func (h *V1Handler) Handler() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var head string - head, r.URL.Path = router.ShiftPath(r.URL.Path) - switch head { - case "apps": - h.AppsHandler.ServeHTTP(w, r) - case "cloud": - h.CloudHandler.Handler().ServeHTTP(w, r) - case "config": - h.ConfigHandler.Handler().ServeHTTP(w, r) - default: - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "Not found", nil) - } - }) -}