enhancement: add graph invite endpoint (#7687)

This commit is contained in:
Florian Schade
2023-11-17 16:36:54 +01:00
committed by GitHub
parent 8fb8cb064f
commit ad57d59738
43 changed files with 3968 additions and 1016 deletions
+4 -3
View File
@@ -8,6 +8,10 @@ import (
"github.com/cs3org/reva/v2/pkg/events/stream"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
chimiddleware "github.com/go-chi/chi/v5/middleware"
"github.com/pkg/errors"
"go-micro.dev/v4"
"go-micro.dev/v4/events"
"github.com/owncloud/ocis/v2/ocis-pkg/account"
"github.com/owncloud/ocis/v2/ocis-pkg/cors"
"github.com/owncloud/ocis/v2/ocis-pkg/keycloak"
@@ -21,9 +25,6 @@ import (
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
graphMiddleware "github.com/owncloud/ocis/v2/services/graph/pkg/middleware"
svc "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0"
"github.com/pkg/errors"
"go-micro.dev/v4"
"go-micro.dev/v4/events"
)
// Server initializes the http service and server.
+221 -10
View File
@@ -2,6 +2,7 @@ package svc
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
@@ -10,19 +11,28 @@ import (
"reflect"
"strconv"
"strings"
"sync"
"time"
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
"golang.org/x/crypto/sha3"
"golang.org/x/sync/errgroup"
"github.com/cs3org/reva/v2/pkg/conversions"
revactx "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode"
"golang.org/x/crypto/sha3"
"github.com/owncloud/ocis/v2/services/graph/pkg/validate"
)
// GetRootDriveChildren implements the Service interface.
@@ -234,6 +244,207 @@ func (g Graph) GetDriveItemChildren(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, &ListResponse{Value: files})
}
// Invite invites a user to a storage drive (space).
func (g Graph) Invite(w http.ResponseWriter, r *http.Request) {
gatewayClient, err := g.gatewaySelector.Next()
if err != nil {
g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return
}
driveID, err := parseIDParam(r, "driveID")
if err != nil {
g.logger.Debug().Err(err).Msg("could not parse driveID")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid driveID")
return
}
itemID, err := parseIDParam(r, "itemID")
if err != nil {
g.logger.Debug().Err(err).Msg("could not parse itemID")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid itemID")
return
}
if driveID.GetStorageId() != itemID.GetStorageId() || driveID.GetSpaceId() != itemID.GetSpaceId() {
g.logger.Debug().Interface("driveID", driveID).Interface("itemID", itemID).Msg("driveID and itemID do not match")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "driveID and itemID do not match")
return
}
driveItemInvite := &libregraph.DriveItemInvite{}
if err := StrictJSONUnmarshal(r.Body, driveItemInvite); err != nil {
g.logger.Debug().Err(err).Interface("Body", r.Body).Msg("failed unmarshalling request body")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid request body")
return
}
ctx := r.Context()
if err = validate.StructCtx(ctx, driveItemInvite); err != nil {
g.logger.Debug().Err(err).Interface("Body", r.Body).Msg("invalid request body")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
return
}
statResponse, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &itemID}})
switch {
case err != nil:
fallthrough
case statResponse.GetStatus().GetCode() != cs3rpc.Code_CODE_OK:
g.logger.Debug().Err(err).Interface("itemID", itemID).Interface("Stat", statResponse).Msg("stat failed")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return
}
role := conversions.RoleFromName(driveItemInvite.GetRoles()[0], g.config.FilesSharing.EnableResharing)
roleJson, err := json.Marshal(role)
if err != nil {
g.logger.Debug().Err(err).Interface("role", role).Msg("stat marshaling failed")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return
}
createShareErrors := sync.Map{}
createShareSuccesses := sync.Map{}
shareCreateGroup, ctx := errgroup.WithContext(ctx)
for _, driveRecipient := range driveItemInvite.GetRecipients() {
// not needed anymore with go 1.22 and higher
driveRecipient := driveRecipient // https://golang.org/doc/faq#closures_and_goroutines,
shareCreateGroup.Go(func() error {
objectId := driveRecipient.GetObjectId()
if objectId == "" {
return nil
}
createShareRequest := &collaboration.CreateShareRequest{
Opaque: &types.Opaque{
Map: map[string]*types.OpaqueEntry{
"role": {
Decoder: "json",
Value: roleJson,
},
},
},
ResourceInfo: statResponse.GetInfo(),
Grant: &collaboration.ShareGrant{
Permissions: &collaboration.SharePermissions{
Permissions: role.CS3ResourcePermissions(),
},
},
}
permission := &libregraph.Permission{
Roles: []string{role.Name},
}
switch driveRecipient.GetLibreGraphRecipientType() {
case "group":
group, err := g.identityCache.GetGroup(ctx, objectId)
if err != nil {
g.logger.Debug().Err(err).Interface("groupId", objectId).Msg("failed group lookup")
createShareErrors.Store(objectId, errorcode.GeneralException.CreateOdataError(r.Context(), http.StatusText(http.StatusInternalServerError)))
return nil
}
createShareRequest.GetGrant().Grantee = &storageprovider.Grantee{
Type: storageprovider.GranteeType_GRANTEE_TYPE_GROUP,
Id: &storageprovider.Grantee_GroupId{GroupId: &grouppb.GroupId{
OpaqueId: group.GetId(),
}},
}
permission.GrantedToV2 = &libregraph.SharePointIdentitySet{
Group: &libregraph.Identity{
DisplayName: group.GetDisplayName(),
Id: libregraph.PtrString(group.GetId()),
},
}
default:
user, err := g.identityCache.GetUser(ctx, objectId)
if err != nil {
g.logger.Debug().Err(err).Interface("userId", objectId).Msg("failed user lookup")
createShareErrors.Store(objectId, errorcode.GeneralException.CreateOdataError(r.Context(), http.StatusText(http.StatusInternalServerError)))
return nil
}
createShareRequest.GetGrant().Grantee = &storageprovider.Grantee{
Type: storageprovider.GranteeType_GRANTEE_TYPE_USER,
Id: &storageprovider.Grantee_UserId{UserId: &userpb.UserId{
OpaqueId: user.GetId(),
}},
}
permission.GrantedToV2 = &libregraph.SharePointIdentitySet{
User: &libregraph.Identity{
DisplayName: user.GetDisplayName(),
Id: libregraph.PtrString(user.GetId()),
},
}
}
if driveItemInvite.ExpirationDateTime != nil {
createShareRequest.GetGrant().Expiration = utils.TimeToTS(*driveItemInvite.ExpirationDateTime)
}
createShareResponse, err := gatewayClient.CreateShare(ctx, createShareRequest)
switch {
case err != nil:
fallthrough
case createShareResponse.GetStatus().GetCode() != cs3rpc.Code_CODE_OK:
g.logger.Debug().Err(err).Msg("share creation failed")
createShareErrors.Store(objectId, errorcode.GeneralException.CreateOdataError(r.Context(), http.StatusText(http.StatusInternalServerError)))
return nil
}
if id := createShareResponse.GetShare().GetId().GetOpaqueId(); id != "" {
permission.Id = libregraph.PtrString(id)
}
if expiration := createShareResponse.GetShare().GetExpiration(); expiration != nil {
permission.ExpirationDateTime = libregraph.PtrTime(utils.TSToTime(expiration))
}
createShareSuccesses.Store(objectId, permission)
return nil
})
}
if err := shareCreateGroup.Wait(); err != nil {
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return
}
value := make([]interface{}, 0, len(driveItemInvite.Recipients))
hasSuccesses := false
createShareSuccesses.Range(func(key, permission interface{}) bool {
value = append(value, permission)
hasSuccesses = true
return true
})
hasErrors := false
createShareErrors.Range(func(key, err interface{}) bool {
value = append(value, err)
hasErrors = true
return true
})
switch {
case hasErrors && hasSuccesses:
render.Status(r, http.StatusMultiStatus)
case hasSuccesses:
render.Status(r, http.StatusCreated)
default:
render.Status(r, http.StatusInternalServerError)
}
render.JSON(w, r, &ListResponse{Value: value})
}
func (g Graph) getDriveItem(ctx context.Context, ref storageprovider.Reference) (*libregraph.DriveItem, error) {
gatewayClient, err := g.gatewaySelector.Next()
if err != nil {
@@ -531,14 +742,14 @@ func spaceRootStatKey(id *storageprovider.ResourceId, imagenode, readmeNode stri
if id == nil {
return ""
}
sha3 := sha3.NewShake256()
_, _ = sha3.Write([]byte(id.GetStorageId()))
_, _ = sha3.Write([]byte(id.GetSpaceId()))
_, _ = sha3.Write([]byte(id.GetOpaqueId()))
_, _ = sha3.Write([]byte(imagenode))
_, _ = sha3.Write([]byte(readmeNode))
shakeHash := sha3.NewShake256()
_, _ = shakeHash.Write([]byte(id.GetStorageId()))
_, _ = shakeHash.Write([]byte(id.GetSpaceId()))
_, _ = shakeHash.Write([]byte(id.GetOpaqueId()))
_, _ = shakeHash.Write([]byte(imagenode))
_, _ = shakeHash.Write([]byte(readmeNode))
h := make([]byte, 64)
_, _ = sha3.Read(h)
_, _ = shakeHash.Read(h)
return fmt.Sprintf("%x", h)
}
@@ -7,28 +7,34 @@ import (
"io"
"net/http"
"net/http/httptest"
"strings"
"time"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/go-chi/chi/v5"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/stretchr/testify/mock"
"github.com/tidwall/gjson"
"google.golang.org/grpc"
revactx "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/utils"
cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
"github.com/go-chi/chi/v5"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
"github.com/owncloud/ocis/v2/services/graph/mocks"
"github.com/owncloud/ocis/v2/services/graph/pkg/config"
"github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults"
identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks"
service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0"
"github.com/stretchr/testify/mock"
"google.golang.org/grpc"
)
type itemsList struct {
@@ -93,8 +99,302 @@ var _ = Describe("Driveitems", func() {
)
})
Describe("Invite", func() {
var (
itemID string
driveItemInvite *libregraph.DriveItemInvite
listSharesMock *mock.Call
listSharesResponse *collaboration.ListSharesResponse
statMock *mock.Call
statResponse *provider.StatResponse
getUserResponse *userpb.GetUserResponse
getUserMock *mock.Call
getGroupResponse *grouppb.GetGroupResponse
getGroupMock *mock.Call
createShareResponse *collaboration.CreateShareResponse
createShareMock *mock.Call
)
BeforeEach(func() {
itemID = "f0042750-23c5-441c-9f2c-ff7c53e5bd2a$cd621428-dfbe-44c1-9393-65bf0dd440a6!1177add3-b4eb-434e-a2e8-1859b31b17bf"
rctx := chi.NewRouteContext()
rctx.URLParams.Add("driveID", "f0042750-23c5-441c-9f2c-ff7c53e5bd2a$cd621428-dfbe-44c1-9393-65bf0dd440a6!cd621428-dfbe-44c1-9393-65bf0dd440a6")
rctx.URLParams.Add("itemID", itemID)
ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx)
ctx = revactx.ContextSetUser(ctx, currentUser)
driveItemInvite = &libregraph.DriveItemInvite{
Recipients: []libregraph.DriveRecipient{
{ObjectId: libregraph.PtrString("1")},
},
Roles: []string{"viewer"},
}
statMock = gatewayClient.On("Stat", mock.Anything, mock.Anything)
statResponse = &provider.StatResponse{
Status: status.NewOK(ctx),
}
statMock.Return(statResponse, nil)
getUserMock = gatewayClient.On("GetUser", mock.Anything, mock.Anything)
getUserResponse = &userpb.GetUserResponse{
Status: status.NewOK(ctx),
User: &userpb.User{
Id: &userpb.UserId{OpaqueId: "1"},
DisplayName: "Cem Kaner",
},
}
getUserMock.Return(getUserResponse, nil)
getGroupMock = gatewayClient.On("GetGroup", mock.Anything, mock.Anything)
getGroupResponse = &grouppb.GetGroupResponse{
Status: status.NewOK(ctx),
Group: &grouppb.Group{
Id: &grouppb.GroupId{OpaqueId: "2"},
GroupName: "Florida Institute of Technology",
},
}
getGroupMock.Return(getGroupResponse, nil)
listSharesMock = gatewayClient.On("ListShares", mock.Anything, mock.Anything)
listSharesResponse = &collaboration.ListSharesResponse{
Status: status.NewOK(ctx),
}
listSharesMock.Return(listSharesResponse, nil)
createShareMock = gatewayClient.On("CreateShare", mock.Anything, mock.Anything)
createShareResponse = &collaboration.CreateShareResponse{
Status: status.NewOK(ctx),
}
createShareMock.Return(createShareResponse, nil)
})
toJSONReader := func(v any) *strings.Reader {
driveItemInviteBytes, err := json.Marshal(v)
Expect(err).ToNot(HaveOccurred())
return strings.NewReader(string(driveItemInviteBytes))
}
It("creates user and group shares as expected (happy path)", func() {
driveItemInvite.Recipients = []libregraph.DriveRecipient{
{ObjectId: libregraph.PtrString("1")},
{ObjectId: libregraph.PtrString("2"), LibreGraphRecipientType: libregraph.PtrString("group")},
}
driveItemInvite.ExpirationDateTime = libregraph.PtrTime(time.Now().Add(time.Hour))
createShareResponse.Share = &collaboration.Share{
Id: &collaboration.ShareId{OpaqueId: "123"},
Expiration: utils.TimeToTS(*driveItemInvite.ExpirationDateTime),
}
svc.Invite(
rr,
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
WithContext(ctx),
)
jsonData := gjson.Get(rr.Body.String(), "value")
Expect(rr.Code).To(Equal(http.StatusCreated))
Expect(jsonData.Get("#").Num).To(Equal(float64(2)))
Expect(jsonData.Get("0.id").Str).To(Equal("123"))
Expect(jsonData.Get("1.id").Str).To(Equal("123"))
Expect(jsonData.Get("0.expirationDateTime").Str).To(Equal(driveItemInvite.ExpirationDateTime.Format(time.RFC3339Nano)))
Expect(jsonData.Get("1.expirationDateTime").Str).To(Equal(driveItemInvite.ExpirationDateTime.Format(time.RFC3339Nano)))
Expect(jsonData.Get("0.roles.#").Num).To(Equal(float64(1)))
Expect(jsonData.Get("0.roles.0").String()).To(Equal("viewer"))
Expect(jsonData.Get("1.roles.#").Num).To(Equal(float64(1)))
Expect(jsonData.Get("1.roles.0").String()).To(Equal("viewer"))
Expect(jsonData.Get("#.grantedToV2.user.displayName").Array()[0].Str).To(Equal(getUserResponse.User.DisplayName))
Expect(jsonData.Get("#.grantedToV2.user.id").Array()[0].Str).To(Equal("1"))
Expect(jsonData.Get("#.grantedToV2.group.displayName").Array()[0].Str).To(Equal(getGroupResponse.Group.GroupName))
Expect(jsonData.Get("#.grantedToV2.group.id").Array()[0].Str).To(Equal("2"))
})
It("validates the driveID", func() {
rctx := chi.NewRouteContext()
rctx.URLParams.Add("driveID", "")
ctx = context.WithValue(context.Background(), chi.RouteCtxKey, rctx)
svc.Invite(
rr,
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
WithContext(ctx),
)
Expect(rr.Code).To(Equal(http.StatusBadRequest))
})
It("validates the itemID", func() {
rctx := chi.NewRouteContext()
rctx.URLParams.Add("driveID", "f0042750-23c5-441c-9f2c-ff7c53e5bd2a$cd621428-dfbe-44c1-9393-65bf0dd440a6!cd621428-dfbe-44c1-9393-65bf0dd440a6")
rctx.URLParams.Add("itemID", "")
ctx = context.WithValue(context.Background(), chi.RouteCtxKey, rctx)
svc.Invite(
rr,
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
WithContext(ctx),
)
Expect(rr.Code).To(Equal(http.StatusBadRequest))
})
It("checks if the itemID and driveID is compatible to each other", func() {
rctx := chi.NewRouteContext()
rctx.URLParams.Add("driveID", "1$2!3")
rctx.URLParams.Add("itemID", "4$5!6")
ctx = context.WithValue(context.Background(), chi.RouteCtxKey, rctx)
svc.Invite(
rr,
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
WithContext(ctx),
)
Expect(rr.Code).To(Equal(http.StatusBadRequest))
})
It("fails if the request body is empty", func() {
svc.Invite(
rr,
httptest.NewRequest(http.MethodPost, "/", nil).
WithContext(ctx),
)
Expect(rr.Code).To(Equal(http.StatusBadRequest))
})
DescribeTable("request validations",
func(body func() *strings.Reader, code int) {
svc.Invite(
rr,
httptest.NewRequest(http.MethodPost, "/", body()).
WithContext(ctx),
)
Expect(rr.Code).To(Equal(code))
},
Entry("fails on unknown fields", func() *strings.Reader {
return strings.NewReader(`{"unknown":"field"}`)
}, http.StatusBadRequest),
Entry("fails without recipients", func() *strings.Reader {
driveItemInvite.Recipients = nil
return toJSONReader(driveItemInvite)
}, http.StatusBadRequest),
Entry("fails without roles", func() *strings.Reader {
driveItemInvite.Roles = []string{}
return toJSONReader(driveItemInvite)
}, http.StatusBadRequest),
Entry("fails if more than one role item is present", func() *strings.Reader {
driveItemInvite.Roles = []string{"", ""}
return toJSONReader(driveItemInvite)
}, http.StatusBadRequest),
Entry("fails if the ExpirationDateTime is not in the future", func() *strings.Reader {
driveItemInvite.ExpirationDateTime = libregraph.PtrTime(time.Now())
return toJSONReader(driveItemInvite)
}, http.StatusBadRequest),
)
DescribeTable("Stat",
func(prep func(), code int) {
prep()
svc.Invite(
rr,
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
WithContext(ctx),
)
Expect(rr.Code).To(Equal(code))
statMock.Parent.AssertNumberOfCalls(GinkgoT(), "Stat", 1)
},
Entry("fails if not ok", func() {
statResponse.Status = status.NewNotFound(context.Background(), "")
}, http.StatusInternalServerError),
Entry("fails if errors", func() {
statMock.Return(nil, errors.New("error"))
}, http.StatusInternalServerError),
)
DescribeTable("GetGroup",
func(prep func(), code int) {
driveItemInvite.Recipients = []libregraph.DriveRecipient{
{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("group")},
}
prep()
svc.Invite(
rr,
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
WithContext(ctx),
)
Expect(rr.Code).To(Equal(code))
getGroupMock.Parent.AssertNumberOfCalls(GinkgoT(), "GetGroup", 1)
},
Entry("fails if not ok", func() {
getGroupResponse.Status = status.NewNotFound(context.Background(), "")
}, http.StatusInternalServerError),
Entry("fails if errors", func() {
getGroupMock.Return(nil, errors.New("error"))
}, http.StatusInternalServerError),
)
DescribeTable("GetUser",
func(prep func(), code int) {
prep()
svc.Invite(
rr,
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
WithContext(ctx),
)
Expect(rr.Code).To(Equal(code))
getUserMock.Parent.AssertNumberOfCalls(GinkgoT(), "GetUser", 1)
},
Entry("fails if not ok", func() {
getUserResponse.Status = status.NewNotFound(context.Background(), "")
}, http.StatusInternalServerError),
Entry("fails if errors", func() {
getUserMock.Return(nil, errors.New("error"))
}, http.StatusInternalServerError),
)
DescribeTable("CreateShare",
func(prep func(), code int) {
prep()
svc.Invite(
rr,
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
WithContext(ctx),
)
Expect(rr.Code).To(Equal(code))
createShareMock.Parent.AssertNumberOfCalls(GinkgoT(), "CreateShare", 1)
},
Entry("fails if not ok", func() {
createShareResponse.Status = status.NewNotFound(context.Background(), "")
}, http.StatusInternalServerError),
Entry("fails if errors", func() {
createShareMock.Return(nil, errors.New("error"))
}, http.StatusInternalServerError),
)
})
Describe("GetRootDriveChildren", func() {
It("handles ListStorageSpaces not found ", func() {
It("handles ListStorageSpaces not found", func() {
gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{
Status: status.NewNotFound(ctx, "not found"),
}, nil)
@@ -2,6 +2,7 @@
package errorcode
import (
"context"
"errors"
"net/http"
"time"
@@ -91,20 +92,24 @@ func New(e ErrorCode, msg string) Error {
// Render writes an Graph ErrorCode object to the response writer
func (e ErrorCode) Render(w http.ResponseWriter, r *http.Request, status int, msg string) {
render.Status(r, status)
render.JSON(w, r, e.CreateOdataError(r.Context(), msg))
}
// CreateOdataError creates and populates a Graph ErrorCode object
func (e ErrorCode) CreateOdataError(ctx context.Context, msg string) *libregraph.OdataError {
innererror := map[string]interface{}{
"date": time.Now().UTC().Format(time.RFC3339),
}
innererror["request-id"] = middleware.GetReqID(r.Context())
resp := &libregraph.OdataError{
innererror["request-id"] = middleware.GetReqID(ctx)
return &libregraph.OdataError{
Error: libregraph.OdataErrorMain{
Code: e.String(),
Message: msg,
Innererror: innererror,
},
}
render.Status(r, status)
render.JSON(w, r, resp)
}
// Render writes an Graph Error object to the response writer
+6 -2
View File
@@ -10,14 +10,15 @@ import (
"strconv"
"time"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/store"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
ldapv3 "github.com/go-ldap/ldap/v3"
"github.com/jellydator/ttlcache/v3"
microstore "go-micro.dev/v4/store"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/store"
ocisldap "github.com/owncloud/ocis/v2/ocis-pkg/ldap"
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
"github.com/owncloud/ocis/v2/ocis-pkg/roles"
@@ -107,6 +108,8 @@ type Service interface {
GetDriveItem(w http.ResponseWriter, r *http.Request)
GetDriveItemChildren(w http.ResponseWriter, r *http.Request)
Invite(w http.ResponseWriter, r *http.Request)
GetTags(w http.ResponseWriter, r *http.Request)
AssignTags(w http.ResponseWriter, r *http.Request)
UnassignTags(w http.ResponseWriter, r *http.Request)
@@ -195,6 +198,7 @@ func NewService(opts ...Option) (Graph, error) {
r.Route("/v1beta1", func(r chi.Router) {
r.Get("/me/drive/sharedByMe", svc.GetSharedByMe)
r.Get("/me/drive/sharedWithMe", svc.ListSharedWithMe)
r.Post("/drives/{driveID}/items/{itemID}/invite", svc.Invite)
r.Route("/roleManagement/permissions/roleDefinitions", func(r chi.Router) {
r.Get("/", svc.GetRoleDefinitions)
r.Get("/{roleID}", svc.GetRoleDefinition)
+36
View File
@@ -0,0 +1,36 @@
package validate
import (
"context"
"sync/atomic"
"github.com/go-playground/validator/v10"
libregraph "github.com/owncloud/libre-graph-api-go"
)
var defaultValidator atomic.Value
var structMapValidations = map[any]map[string]string{
&libregraph.DriveItemInvite{}: {
"Recipients": "min=1",
"Roles": "len=1", // currently it is not possible to set more than one role
"ExpirationDateTime": "omitnil,gt",
},
}
func init() {
v := validator.New()
for s, rules := range structMapValidations {
v.RegisterStructValidationMapRules(rules, s)
}
defaultValidator.Store(v)
}
// Default returns the default validator.
func Default() *validator.Validate { return defaultValidator.Load().(*validator.Validate) }
// StructCtx validates a struct and returns the error.
func StructCtx(ctx context.Context, s interface{}) error {
return Default().StructCtx(ctx, s)
}