enhancement: add graph invite endpoint (#7687)
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user