diff --git a/internal/http/services/owncloud/ocs/apps.go b/internal/http/services/owncloud/ocs/apps.go index 9029bc1ddc..b6298e21a2 100644 --- a/internal/http/services/owncloud/ocs/apps.go +++ b/internal/http/services/owncloud/ocs/apps.go @@ -21,6 +21,7 @@ package ocs import ( "net/http" + "github.com/cs3org/reva/pkg/errhandler" "github.com/cs3org/reva/pkg/rhttp/router" ) @@ -50,7 +51,7 @@ func (h *AppsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } } - WriteOCSError(w, r, MetaNotFound.StatusCode, "Not found", nil) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "Not found", nil) case "notifications": head, r.URL.Path = router.ShiftPath(r.URL.Path) if head == "api" { @@ -60,8 +61,8 @@ func (h *AppsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } } - WriteOCSError(w, r, MetaNotFound.StatusCode, "Not found", nil) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "Not found", nil) default: - WriteOCSError(w, r, MetaNotFound.StatusCode, "Not found", nil) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "Not found", nil) } } diff --git a/internal/http/services/owncloud/ocs/capabilities.go b/internal/http/services/owncloud/ocs/capabilities.go index 94d276f8c3..50fcef7c41 100644 --- a/internal/http/services/owncloud/ocs/capabilities.go +++ b/internal/http/services/owncloud/ocs/capabilities.go @@ -21,6 +21,8 @@ package ocs import ( "encoding/xml" "net/http" + + "github.com/cs3org/reva/pkg/errhandler" ) // ocsBool implements the xml/json Marshaler interface. The OCS API inconsistency require us to parse boolean values @@ -231,7 +233,7 @@ func (h *CapabilitiesHandler) init(c *Config) { // Handler renders the capabilities func (h *CapabilitiesHandler) Handler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - WriteOCSSuccess(w, r, h.c) + errhandler.WriteSuccess(w, r, h.c) }) } diff --git a/internal/http/services/owncloud/ocs/cloud.go b/internal/http/services/owncloud/ocs/cloud.go index 262c98d6a7..4330492b99 100644 --- a/internal/http/services/owncloud/ocs/cloud.go +++ b/internal/http/services/owncloud/ocs/cloud.go @@ -21,6 +21,7 @@ package ocs import ( "net/http" + "github.com/cs3org/reva/pkg/errhandler" "github.com/cs3org/reva/pkg/rhttp/router" ) @@ -51,7 +52,7 @@ func (h *CloudHandler) Handler() http.Handler { case "users": h.UsersHandler.ServeHTTP(w, r) default: - WriteOCSError(w, r, MetaNotFound.StatusCode, "Not found", nil) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "Not found", nil) } }) } diff --git a/internal/http/services/owncloud/ocs/config.go b/internal/http/services/owncloud/ocs/config.go index 74630a47fd..42e058cb74 100644 --- a/internal/http/services/owncloud/ocs/config.go +++ b/internal/http/services/owncloud/ocs/config.go @@ -20,6 +20,8 @@ package ocs import ( "net/http" + + "github.com/cs3org/reva/pkg/errhandler" ) // ConfigHandler renders the config endpoint @@ -50,7 +52,7 @@ func (h *ConfigHandler) init(c *Config) { // Handler renders the config func (h *ConfigHandler) Handler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - WriteOCSSuccess(w, r, h.c) + errhandler.WriteSuccess(w, r, h.c) }) } diff --git a/internal/http/services/owncloud/ocs/ocs.go b/internal/http/services/owncloud/ocs/ocs.go index ef346325a7..187cab17aa 100644 --- a/internal/http/services/owncloud/ocs/ocs.go +++ b/internal/http/services/owncloud/ocs/ocs.go @@ -22,6 +22,7 @@ import ( "net/http" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/errhandler" "github.com/cs3org/reva/pkg/rhttp/global" "github.com/cs3org/reva/pkg/rhttp/router" "github.com/cs3org/reva/pkg/sharedconf" @@ -99,6 +100,6 @@ func (s *svc) Handler() http.Handler { return } - WriteOCSError(w, r, MetaNotFound.StatusCode, "Not found", nil) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "Not found", nil) }) } diff --git a/internal/http/services/owncloud/ocs/reqres.go b/internal/http/services/owncloud/ocs/reqres.go index 591d432f70..5809970059 100644 --- a/internal/http/services/owncloud/ocs/reqres.go +++ b/internal/http/services/owncloud/ocs/reqres.go @@ -18,131 +18,7 @@ package ocs -import ( - "encoding/json" - "encoding/xml" - "net/http" - - user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - "github.com/cs3org/reva/pkg/appctx" -) - -// Response is the top level response structure -type Response struct { - OCS *Payload `json:"ocs"` -} - -// Payload combines response metadata and data -type Payload struct { - XMLName struct{} `json:"-" xml:"ocs"` - Meta *ResponseMeta `json:"meta" xml:"meta"` - Data interface{} `json:"data,omitempty" xml:"data,omitempty"` -} - -// ResponseMeta holds response metadata -type ResponseMeta struct { - Status string `json:"status" xml:"status"` - StatusCode int `json:"statuscode" xml:"statuscode"` - Message string `json:"message" xml:"message"` - TotalItems string `json:"totalitems,omitempty" xml:"totalitems,omitempty"` - ItemsPerPage string `json:"itemsperpage,omitempty" xml:"itemsperpage,omitempty"` -} - -// MetaOK is the default ok response -var MetaOK = &ResponseMeta{Status: "ok", StatusCode: 100, Message: "OK"} - -// MetaBadRequest is used for unknown errers -var MetaBadRequest = &ResponseMeta{Status: "error", StatusCode: 400, Message: "Bad Request"} - -// MetaServerError is returned on server errors -var MetaServerError = &ResponseMeta{Status: "error", StatusCode: 996, Message: "Server Error"} - -// MetaUnauthorized is returned on unauthorized requests -var MetaUnauthorized = &ResponseMeta{Status: "error", StatusCode: 997, Message: "Unauthorised"} - -// MetaNotFound is returned when trying to access not existing resources -var MetaNotFound = &ResponseMeta{Status: "error", StatusCode: 998, Message: "Not Found"} - -// MetaUnknownError is used for unknown errers -var MetaUnknownError = &ResponseMeta{Status: "error", StatusCode: 999, Message: "Unknown Error"} - -// WriteOCSSuccess handles writing successful ocs response data -func WriteOCSSuccess(w http.ResponseWriter, r *http.Request, d interface{}) { - WriteOCSData(w, r, MetaOK, d, nil) -} - -// WriteOCSError handles writing error ocs responses -func WriteOCSError(w http.ResponseWriter, r *http.Request, c int, m string, err error) { - WriteOCSData(w, r, &ResponseMeta{Status: "error", StatusCode: c, Message: m}, nil, err) -} - -// WriteOCSData handles writing ocs data in json and xml -func WriteOCSData(w http.ResponseWriter, r *http.Request, m *ResponseMeta, d interface{}, err error) { - WriteOCSResponse(w, r, &Response{ - OCS: &Payload{ - Meta: m, - Data: d, - }, - }, err) -} - -// WriteOCSResponse handles writing ocs responses in json and xml -func WriteOCSResponse(w http.ResponseWriter, r *http.Request, res *Response, err error) { - var encoded []byte - - if err != nil { - appctx.GetLogger(r.Context()).Error().Err(err).Msg(res.OCS.Meta.Message) - } - - if r.URL.Query().Get("format") == "json" { - w.Header().Set("Content-Type", "application/json") - encoded, err = json.Marshal(res) - } else { - w.Header().Set("Content-Type", "application/xml") - _, err = w.Write([]byte(xml.Header)) - if err != nil { - appctx.GetLogger(r.Context()).Error().Err(err).Msg("error writing xml header") - w.WriteHeader(http.StatusInternalServerError) - return - } - encoded, err = xml.Marshal(res.OCS) - } - if err != nil { - appctx.GetLogger(r.Context()).Error().Err(err).Msg("error encoding ocs response") - w.WriteHeader(http.StatusInternalServerError) - return - } - - // TODO map error for v2 only? - // see https://github.com/owncloud/core/commit/bacf1603ffd53b7a5f73854d1d0ceb4ae545ce9f#diff-262cbf0df26b45bad0cf00d947345d9c - switch res.OCS.Meta.StatusCode { - case MetaNotFound.StatusCode: - w.WriteHeader(http.StatusNotFound) - case MetaServerError.StatusCode: - w.WriteHeader(http.StatusInternalServerError) - case MetaUnknownError.StatusCode: - w.WriteHeader(http.StatusInternalServerError) - case MetaUnauthorized.StatusCode: - w.WriteHeader(http.StatusUnauthorized) - case 100: - w.WriteHeader(http.StatusOK) - case 104: - w.WriteHeader(http.StatusForbidden) - default: - // any 2xx, 4xx and 5xx will be used as is - if res.OCS.Meta.StatusCode >= 200 && res.OCS.Meta.StatusCode < 600 { - w.WriteHeader(res.OCS.Meta.StatusCode) - } else { - w.WriteHeader(http.StatusBadRequest) - } - } - - _, err = w.Write(encoded) - if err != nil { - appctx.GetLogger(r.Context()).Error().Err(err).Msg("error writing ocs response") - w.WriteHeader(http.StatusInternalServerError) - } -} +import user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" // UserIDToString returns a userid string with an optional idp separated by @: "[@]" func UserIDToString(userID *user.UserId) string { diff --git a/internal/http/services/owncloud/ocs/shares.go b/internal/http/services/owncloud/ocs/shares.go index 29d07f2b99..53f52a7238 100644 --- a/internal/http/services/owncloud/ocs/shares.go +++ b/internal/http/services/owncloud/ocs/shares.go @@ -38,6 +38,7 @@ import ( "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/errhandler" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/rhttp/router" "github.com/pkg/errors" @@ -74,12 +75,12 @@ func (h *SharesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { case "PUT": h.updateShare(w, r) // TODO PUT is used with incomplete data to update a share 🤦 default: - WriteOCSError(w, r, MetaBadRequest.StatusCode, "Only GET, POST and PUT are allowed", nil) + errhandler.WriteError(w, r, errhandler.MetaBadRequest.StatusCode, "Only GET, POST and PUT are allowed", nil) } case "sharees": h.findSharees(w, r) default: - WriteOCSError(w, r, MetaNotFound.StatusCode, "Not found", nil) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "Not found", nil) } } @@ -88,7 +89,7 @@ func (h *SharesHandler) findSharees(w http.ResponseWriter, r *http.Request) { term := r.URL.Query().Get("search") if term == "" { - WriteOCSError(w, r, MetaBadRequest.StatusCode, "search must not be empty", nil) + errhandler.WriteError(w, r, errhandler.MetaBadRequest.StatusCode, "search must not be empty", nil) return } @@ -100,7 +101,7 @@ func (h *SharesHandler) findSharees(w http.ResponseWriter, r *http.Request) { res, err := gatewayProvider.FindUsers(r.Context(), &req) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error searching users", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error searching users", err) return } @@ -114,7 +115,7 @@ func (h *SharesHandler) findSharees(w http.ResponseWriter, r *http.Request) { matches = append(matches, match) } - WriteOCSSuccess(w, r, &conversions.ShareeData{ + errhandler.WriteSuccess(w, r, &conversions.ShareeData{ Exact: &conversions.ExactMatchesData{ Users: []*conversions.MatchData{}, Groups: []*conversions.MatchData{}, @@ -144,13 +145,13 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { shareType, err := strconv.Atoi(r.FormValue("shareType")) if err != nil { - WriteOCSError(w, r, MetaBadRequest.StatusCode, "shareType must be an integer", nil) + errhandler.WriteError(w, r, errhandler.MetaBadRequest.StatusCode, "shareType must be an integer", nil) return } sClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error getting storage grpc client", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error getting storage grpc client", err) return } @@ -158,7 +159,7 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { // TODO the path actually depends on the configured webdav_namespace hRes, err := sClient.GetHome(ctx, &provider.GetHomeRequest{}) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error sending a grpc get home request", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error sending a grpc get home request", err) return } prefix := hRes.GetPath() @@ -167,19 +168,19 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { // if user sharing is disabled if h.gatewayAddr == "" { - WriteOCSError(w, r, MetaServerError.StatusCode, "user sharing service not configured", nil) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "user sharing service not configured", nil) return } shareWith := r.FormValue("shareWith") if shareWith == "" { - WriteOCSError(w, r, MetaBadRequest.StatusCode, "missing shareWith", nil) + errhandler.WriteError(w, r, errhandler.MetaBadRequest.StatusCode, "missing shareWith", nil) return } gatewayClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, fmt.Sprintf("no gateway service on addr: %v", h.gatewayAddr), err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, fmt.Sprintf("no gateway service on addr: %v", h.gatewayAddr), err) return } @@ -187,12 +188,12 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { UserId: &userpb.UserId{OpaqueId: shareWith}, }) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error searching recipient", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error searching recipient", err) return } if userRes.Status.Code != rpc.Code_CODE_OK { - WriteOCSError(w, r, MetaNotFound.StatusCode, "user not found", err) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "user not found", err) return } @@ -208,12 +209,12 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { } else { pint, err := strconv.Atoi(pval) if err != nil { - WriteOCSError(w, r, MetaBadRequest.StatusCode, "permissions must be an integer", nil) + errhandler.WriteError(w, r, errhandler.MetaBadRequest.StatusCode, "permissions must be an integer", nil) return } permissions, err = conversions.NewPermissions(pint) if err != nil { - WriteOCSError(w, r, MetaBadRequest.StatusCode, err.Error(), nil) + errhandler.WriteError(w, r, errhandler.MetaBadRequest.StatusCode, err.Error(), nil) return } role = conversions.Permissions2Role(permissions) @@ -230,7 +231,7 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { roleMap := map[string]string{"name": role} val, err := json.Marshal(roleMap) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "could not encode role", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "could not encode role", err) return } @@ -244,16 +245,16 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { statRes, err := sClient.Stat(ctx, statReq) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error sending a grpc stat request", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error sending a grpc stat request", err) return } if statRes.Status.Code != rpc.Code_CODE_OK { if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND { - WriteOCSError(w, r, MetaNotFound.StatusCode, "not found", nil) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "not found", nil) return } - WriteOCSError(w, r, MetaServerError.StatusCode, "grpc stat request failed", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "grpc stat request failed", err) return } @@ -280,24 +281,24 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { createShareResponse, err := gatewayClient.CreateShare(ctx, createShareReq) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error sending a grpc create share request", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error sending a grpc create share request", err) return } if createShareResponse.Status.Code != rpc.Code_CODE_OK { if createShareResponse.Status.Code == rpc.Code_CODE_NOT_FOUND { - WriteOCSError(w, r, MetaNotFound.StatusCode, "not found", nil) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "not found", nil) return } - WriteOCSError(w, r, MetaServerError.StatusCode, "grpc create share request failed", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "grpc create share request failed", err) return } s, err := h.userShare2ShareData(ctx, createShareResponse.Share) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error mapping share data", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error mapping share data", err) return } s.Path = r.FormValue("path") // use path without user prefix - WriteOCSSuccess(w, r, s) + errhandler.WriteSuccess(w, r, s) return } @@ -307,7 +308,7 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { c, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { log.Debug().Err(err).Str("createShare", "shares").Msg("error creating public link share") - WriteOCSError(w, r, MetaServerError.StatusCode, "missing user in context", fmt.Errorf("error getting a connection to a public shares provider")) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "missing user in context", fmt.Errorf("error getting a connection to a public shares provider")) return } @@ -322,7 +323,7 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { statRes, err := c.Stat(ctx, &statReq) if err != nil { log.Debug().Err(err).Str("createShare", "shares").Msg("error on stat call") - WriteOCSError(w, r, MetaServerError.StatusCode, "missing resource information", fmt.Errorf("error getting resource information")) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "missing resource information", fmt.Errorf("error getting resource information")) return } @@ -340,24 +341,24 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { createRes, err := c.CreatePublicShare(ctx, &req) if err != nil { log.Debug().Err(err).Str("createShare", "shares").Msgf("error creating a public share to resource id: %v", statRes.Info.GetId()) - WriteOCSError(w, r, MetaServerError.StatusCode, "error creating public share", fmt.Errorf("error creating a public share to resource id: %v", statRes.Info.GetId())) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error creating public share", fmt.Errorf("error creating a public share to resource id: %v", statRes.Info.GetId())) return } if createRes.Status.Code != rpc.Code_CODE_OK { log.Debug().Err(errors.New("create public share failed")).Str("shares", "createShare").Msgf("create public share failed with status code: %v", createRes.Status.Code.String()) - WriteOCSError(w, r, MetaServerError.StatusCode, "grpc create public share request failed", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "grpc create public share request failed", err) return } // build ocs response for Phoenix s := conversions.PublicShare2ShareData(createRes.Share) - WriteOCSSuccess(w, r, s) + errhandler.WriteSuccess(w, r, s) return } - WriteOCSError(w, r, MetaBadRequest.StatusCode, "unknown share type", nil) + errhandler.WriteError(w, r, errhandler.MetaBadRequest.StatusCode, "unknown share type", nil) } // TODO sort out mapping, this is just a first guess @@ -467,18 +468,18 @@ func (h *SharesHandler) updateShare(w http.ResponseWriter, r *http.Request) { pval := r.FormValue("permissions") if pval == "" { - WriteOCSError(w, r, MetaBadRequest.StatusCode, "permissions missing", nil) + errhandler.WriteError(w, r, errhandler.MetaBadRequest.StatusCode, "permissions missing", nil) return } pint, err := strconv.Atoi(pval) if err != nil { - WriteOCSError(w, r, MetaBadRequest.StatusCode, "permissions must be an integer", nil) + errhandler.WriteError(w, r, errhandler.MetaBadRequest.StatusCode, "permissions must be an integer", nil) return } permissions, err := conversions.NewPermissions(pint) if err != nil { - WriteOCSError(w, r, MetaBadRequest.StatusCode, err.Error(), nil) + errhandler.WriteError(w, r, errhandler.MetaBadRequest.StatusCode, err.Error(), nil) return } @@ -487,7 +488,7 @@ func (h *SharesHandler) updateShare(w http.ResponseWriter, r *http.Request) { uClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error getting grpc client", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error getting grpc client", err) return } @@ -510,16 +511,16 @@ func (h *SharesHandler) updateShare(w http.ResponseWriter, r *http.Request) { } uRes, err := uClient.UpdateShare(ctx, uReq) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error sending a grpc update share request", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error sending a grpc update share request", err) return } if uRes.Status.Code != rpc.Code_CODE_OK { if uRes.Status.Code == rpc.Code_CODE_NOT_FOUND { - WriteOCSError(w, r, MetaNotFound.StatusCode, "not found", nil) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "not found", nil) return } - WriteOCSError(w, r, MetaServerError.StatusCode, "grpc update share request failed", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "grpc update share request failed", err) return } @@ -534,26 +535,26 @@ func (h *SharesHandler) updateShare(w http.ResponseWriter, r *http.Request) { } gRes, err := uClient.GetShare(ctx, gReq) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error sending a grpc get share request", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error sending a grpc get share request", err) return } if gRes.Status.Code != rpc.Code_CODE_OK { if gRes.Status.Code == rpc.Code_CODE_NOT_FOUND { - WriteOCSError(w, r, MetaNotFound.StatusCode, "not found", nil) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "not found", nil) return } - WriteOCSError(w, r, MetaServerError.StatusCode, "grpc get share request failed", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "grpc get share request failed", err) return } share, err := h.userShare2ShareData(ctx, gRes.Share) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error mapping share data", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error mapping share data", err) return } - WriteOCSSuccess(w, r, share) + errhandler.WriteSuccess(w, r, share) } func (h *SharesHandler) listShares(w http.ResponseWriter, r *http.Request) { @@ -565,7 +566,7 @@ func (h *SharesHandler) listShares(w http.ResponseWriter, r *http.Request) { if r.FormValue("shared_with_me") != "" { listSharedWithMe, err := strconv.ParseBool(r.FormValue("shared_with_me")) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, err.Error(), err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, err.Error(), err) return } if listSharedWithMe { @@ -573,7 +574,7 @@ func (h *SharesHandler) listShares(w http.ResponseWriter, r *http.Request) { sClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, err.Error(), err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, err.Error(), err) return } @@ -589,26 +590,26 @@ func (h *SharesHandler) listShares(w http.ResponseWriter, r *http.Request) { statResponse, err := sClient.Stat(r.Context(), &statRequest) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, err.Error(), err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, err.Error(), err) return } data, err := h.userShare2ShareData(r.Context(), v.Share) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, err.Error(), err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, err.Error(), err) return } err = h.addFileInfo(r.Context(), data, statResponse.Info) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, err.Error(), err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, err.Error(), err) return } shares = append(shares, data) } - WriteOCSSuccess(w, r, shares) + errhandler.WriteSuccess(w, r, shares) return } } @@ -618,7 +619,7 @@ func (h *SharesHandler) listShares(w http.ResponseWriter, r *http.Request) { if p != "" { sClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error getting storage grpc client", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error getting storage grpc client", err) return } @@ -626,37 +627,37 @@ func (h *SharesHandler) listShares(w http.ResponseWriter, r *http.Request) { // TODO the path actually depends on the configured webdav_namespace hRes, err := sClient.GetHome(r.Context(), &provider.GetHomeRequest{}) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error sending a grpc get home request", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error sending a grpc get home request", err) return } filters, err = h.addFilters(w, r, hRes.GetPath()) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, err.Error(), err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, err.Error(), err) return } } userShares, err := h.listUserShares(r, filters) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, err.Error(), err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, err.Error(), err) return } publicShares, err := h.listPublicShares(r) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, err.Error(), err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, err.Error(), err) return } shares = append(shares, append(userShares, publicShares...)...) if h.isReshareRequest(r) { - WriteOCSSuccess(w, r, &conversions.Element{Data: shares}) + errhandler.WriteSuccess(w, r, &conversions.Element{Data: shares}) return } - WriteOCSSuccess(w, r, shares) + errhandler.WriteSuccess(w, r, shares) } func (h *SharesHandler) listSharedWithMe(r *http.Request) []*collaboration.ReceivedShare { @@ -744,7 +745,7 @@ func (h *SharesHandler) addFilters(w http.ResponseWriter, r *http.Request, prefi // first check if the file exists gwClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error getting grpc storage provider client", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error getting grpc storage provider client", err) return nil, err } @@ -760,16 +761,16 @@ func (h *SharesHandler) addFilters(w http.ResponseWriter, r *http.Request, prefi res, err := gwClient.Stat(ctx, statReq) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error sending a grpc stat request", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "error sending a grpc stat request", err) return nil, err } if res.Status.Code != rpc.Code_CODE_OK { if res.Status.Code == rpc.Code_CODE_NOT_FOUND { - WriteOCSError(w, r, MetaNotFound.StatusCode, "not found", nil) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "not found", nil) return filters, errors.New("fixme") } - WriteOCSError(w, r, MetaServerError.StatusCode, "grpc stat request failed", err) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "grpc stat request failed", err) return filters, errors.New("fixme") } @@ -997,7 +998,7 @@ func (h *SharesHandler) userShare2ShareData(ctx context.Context, share *collabor func mustGetGateway(addr string, r *http.Request, w http.ResponseWriter) gateway.GatewayAPIClient { client, err := pool.GetGatewayServiceClient(addr) if err != nil { - WriteOCSError(w, r, MetaBadRequest.StatusCode, "no connection to gateway service", nil) + errhandler.WriteError(w, r, errhandler.MetaBadRequest.StatusCode, "no connection to gateway service", nil) return nil } diff --git a/internal/http/services/owncloud/ocs/user.go b/internal/http/services/owncloud/ocs/user.go index 0d2b4a9b6a..4f2085a33d 100644 --- a/internal/http/services/owncloud/ocs/user.go +++ b/internal/http/services/owncloud/ocs/user.go @@ -22,6 +22,7 @@ import ( "fmt" "net/http" + "github.com/cs3org/reva/pkg/errhandler" "github.com/cs3org/reva/pkg/user" ) @@ -35,11 +36,11 @@ func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // TODO move user to handler parameter? u, ok := user.ContextGetUser(ctx) if !ok { - WriteOCSError(w, r, MetaServerError.StatusCode, "missing user in context", fmt.Errorf("missing user in context")) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "missing user in context", fmt.Errorf("missing user in context")) return } - WriteOCSSuccess(w, r, &UserData{ + errhandler.WriteSuccess(w, r, &UserData{ ID: u.Username, DisplayName: u.DisplayName, Email: u.Mail, diff --git a/internal/http/services/owncloud/ocs/users.go b/internal/http/services/owncloud/ocs/users.go index 527b9d0745..065ed75cc5 100644 --- a/internal/http/services/owncloud/ocs/users.go +++ b/internal/http/services/owncloud/ocs/users.go @@ -22,6 +22,7 @@ import ( "fmt" "net/http" + "github.com/cs3org/reva/pkg/errhandler" "github.com/cs3org/reva/pkg/rhttp/router" ctxuser "github.com/cs3org/reva/pkg/user" ) @@ -39,12 +40,12 @@ func (h *UsersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // FIXME use ldap to fetch user info u, ok := ctxuser.ContextGetUser(ctx) if !ok { - WriteOCSError(w, r, MetaServerError.StatusCode, "missing user in context", fmt.Errorf("missing user in context")) + errhandler.WriteError(w, r, errhandler.MetaServerError.StatusCode, "missing user in context", fmt.Errorf("missing user in context")) return } if user != u.Username { // FIXME allow fetching other users info? - WriteOCSError(w, r, http.StatusForbidden, "user id mismatch", fmt.Errorf("%s tried to acces %s user info endpoint", u.Id.OpaqueId, user)) + errhandler.WriteError(w, r, http.StatusForbidden, "user id mismatch", fmt.Errorf("%s tried to acces %s user info endpoint", u.Id.OpaqueId, user)) return } @@ -52,7 +53,7 @@ func (h *UsersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { head, r.URL.Path = router.ShiftPath(r.URL.Path) switch head { case "": - WriteOCSSuccess(w, r, &UsersData{ + errhandler.WriteSuccess(w, r, &UsersData{ // FIXME query storages? cache a summary? // TODO use list of storages to allow clients to resolve quota status Quota: &QuotaData{ @@ -67,10 +68,10 @@ func (h *UsersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { }) return case "groups": - WriteOCSSuccess(w, r, &GroupsData{}) + errhandler.WriteSuccess(w, r, &GroupsData{}) return default: - WriteOCSError(w, r, MetaNotFound.StatusCode, "Not found", nil) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "Not found", nil) return } diff --git a/internal/http/services/owncloud/ocs/v1.go b/internal/http/services/owncloud/ocs/v1.go index b4e07fe0e2..b29e68fadb 100644 --- a/internal/http/services/owncloud/ocs/v1.go +++ b/internal/http/services/owncloud/ocs/v1.go @@ -21,6 +21,7 @@ package ocs import ( "net/http" + "github.com/cs3org/reva/pkg/errhandler" "github.com/cs3org/reva/pkg/rhttp/router" ) @@ -56,7 +57,7 @@ func (h *V1Handler) Handler() http.Handler { case "config": h.ConfigHandler.Handler().ServeHTTP(w, r) default: - WriteOCSError(w, r, MetaNotFound.StatusCode, "Not found", nil) + errhandler.WriteError(w, r, errhandler.MetaNotFound.StatusCode, "Not found", nil) } }) } diff --git a/pkg/errhandler/errhandler.go b/pkg/errhandler/errhandler.go new file mode 100644 index 0000000000..8bf56b1722 --- /dev/null +++ b/pkg/errhandler/errhandler.go @@ -0,0 +1,144 @@ +// Copyright 2018-2020 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 errhandler + +import ( + "encoding/json" + "encoding/xml" + "net/http" + + "github.com/cs3org/reva/pkg/appctx" +) + +// Response is the top level response structure +type Response struct { + Payload *Payload `json:"payload"` +} + +// Payload combines response metadata and data +type Payload struct { + XMLName struct{} `json:"-" xml:"payload"` + Meta *ResponseMeta `json:"meta" xml:"meta"` + Data interface{} `json:"data,omitempty" xml:"data,omitempty"` +} + +// ResponseMeta holds response metadata +type ResponseMeta struct { + Status string `json:"status" xml:"status"` + StatusCode int `json:"statuscode" xml:"statuscode"` + Message string `json:"message" xml:"message"` + TotalItems string `json:"totalitems,omitempty" xml:"totalitems,omitempty"` + ItemsPerPage string `json:"itemsperpage,omitempty" xml:"itemsperpage,omitempty"` +} + +// MetaOK is the default ok response +var MetaOK = &ResponseMeta{Status: "ok", StatusCode: 100, Message: "OK"} + +// MetaBadRequest is used for unknown errers +var MetaBadRequest = &ResponseMeta{Status: "error", StatusCode: 400, Message: "Bad Request"} + +// MetaServerError is returned on server errors +var MetaServerError = &ResponseMeta{Status: "error", StatusCode: 996, Message: "Server Error"} + +// MetaUnauthorized is returned on unauthorized requests +var MetaUnauthorized = &ResponseMeta{Status: "error", StatusCode: 997, Message: "Unauthorised"} + +// MetaNotFound is returned when trying to access not existing resources +var MetaNotFound = &ResponseMeta{Status: "error", StatusCode: 998, Message: "Not Found"} + +// MetaUnknownError is used for unknown errers +var MetaUnknownError = &ResponseMeta{Status: "error", StatusCode: 999, Message: "Unknown Error"} + +// WriteSuccess handles writing successful response data +func WriteSuccess(w http.ResponseWriter, r *http.Request, d interface{}) { + WriteData(w, r, MetaOK, d, nil) +} + +// WriteError handles writing error responses +func WriteError(w http.ResponseWriter, r *http.Request, c int, m string, err error) { + WriteData(w, r, &ResponseMeta{Status: "error", StatusCode: c, Message: m}, nil, err) +} + +// WriteData handles writing data in json and xml +func WriteData(w http.ResponseWriter, r *http.Request, m *ResponseMeta, d interface{}, err error) { + WriteResponse(w, r, &Response{ + Payload: &Payload{ + Meta: m, + Data: d, + }, + }, err) +} + +// WriteResponse handles writing responses in json and xml +func WriteResponse(w http.ResponseWriter, r *http.Request, res *Response, err error) { + var encoded []byte + + if err != nil { + appctx.GetLogger(r.Context()).Error().Err(err).Msg(res.Payload.Meta.Message) + } + + if r.URL.Query().Get("format") == "json" { + w.Header().Set("Content-Type", "application/json") + encoded, err = json.Marshal(res) + } else { + w.Header().Set("Content-Type", "application/xml") + _, err = w.Write([]byte(xml.Header)) + if err != nil { + appctx.GetLogger(r.Context()).Error().Err(err).Msg("error writing xml header") + w.WriteHeader(http.StatusInternalServerError) + return + } + encoded, err = xml.Marshal(res.Payload) + } + if err != nil { + appctx.GetLogger(r.Context()).Error().Err(err).Msg("error encoding response") + w.WriteHeader(http.StatusInternalServerError) + return + } + + // TODO map error for v2 only? + // see https://github.com/owncloud/core/commit/bacf1603ffd53b7a5f73854d1d0ceb4ae545ce9f#diff-262cbf0df26b45bad0cf00d947345d9c + switch res.Payload.Meta.StatusCode { + case MetaNotFound.StatusCode: + w.WriteHeader(http.StatusNotFound) + case MetaServerError.StatusCode: + w.WriteHeader(http.StatusInternalServerError) + case MetaUnknownError.StatusCode: + w.WriteHeader(http.StatusInternalServerError) + case MetaUnauthorized.StatusCode: + w.WriteHeader(http.StatusUnauthorized) + case 100: + w.WriteHeader(http.StatusOK) + case 104: + w.WriteHeader(http.StatusForbidden) + default: + // any 2xx, 4xx and 5xx will be used as is + if res.Payload.Meta.StatusCode >= 200 && res.Payload.Meta.StatusCode < 600 { + w.WriteHeader(res.Payload.Meta.StatusCode) + } else { + w.WriteHeader(http.StatusBadRequest) + } + } + + _, err = w.Write(encoded) + if err != nil { + appctx.GetLogger(r.Context()).Error().Err(err).Msg("error writing response") + w.WriteHeader(http.StatusInternalServerError) + } +}