graph: Add delete permission endpoint

Allows the owner to remove permissions of a driveItem (i.e. to delete a
share). For now this does not work for "link" permission (i.e. public
shares)
This commit is contained in:
Ralf Haferkamp
2023-11-27 16:52:15 +01:00
committed by Ralf Haferkamp
parent a1dd520262
commit 4a94388a25
3 changed files with 237 additions and 17 deletions
+101 -16
View File
@@ -19,6 +19,7 @@ import (
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/chi/v5"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
"golang.org/x/crypto/sha3"
@@ -252,23 +253,9 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) {
return
}
driveID, err := parseIDParam(r, "driveID")
_, itemID, err := g.extractDriveAndDriveItem(r)
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")
errorcode.RenderError(w, r, err)
return
}
@@ -447,6 +434,104 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, &ListResponse{Value: value})
}
// DeletePermission removes a Permission from a Drive item
func (g Graph) DeletePermission(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
}
_, itemID, err := g.extractDriveAndDriveItem(r)
if err != nil {
errorcode.RenderError(w, r, err)
return
}
permissionID, err := url.PathUnescape(chi.URLParam(r, "permissionID"))
if err != nil {
g.logger.Debug().Err(err).Msg("could not parse driveID")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid driveID")
return
}
ctx := r.Context()
getShareResp, err := gatewayClient.GetShare(ctx,
&collaboration.GetShareRequest{
Ref: &collaboration.ShareReference{
Spec: &collaboration.ShareReference_Id{
Id: &collaboration.ShareId{
OpaqueId: permissionID,
},
},
},
})
switch {
case err != nil:
fallthrough
case getShareResp.Status.GetCode() != cs3rpc.Code_CODE_OK:
g.logger.Debug().Err(err).Interface("permissionID", permissionID).Interface("GetShare", getShareResp).Msg("GetShare failed")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return
}
sharedResourceId := getShareResp.GetShare().GetResourceId()
// The resourceID of the shared resource need to matched the item ID from the Request Path
// otherwise this is an invalid Request.
if sharedResourceId.GetStorageId() != itemID.GetStorageId() ||
sharedResourceId.GetSpaceId() != itemID.GetSpaceId() ||
sharedResourceId.GetOpaqueId() != itemID.GetOpaqueId() {
g.logger.Debug().Msg("resourceID of shared does not match itemID")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "permissionID and itemID do not match")
return
}
removeShareResp, err := gatewayClient.RemoveShare(ctx,
&collaboration.RemoveShareRequest{
Ref: &collaboration.ShareReference{
Spec: &collaboration.ShareReference_Id{
Id: &collaboration.ShareId{
OpaqueId: permissionID,
},
},
},
})
switch {
case err != nil:
fallthrough
case removeShareResp.Status.GetCode() != cs3rpc.Code_CODE_OK:
g.logger.Debug().Err(err).Interface("permissionID", permissionID).Interface("GetShare", getShareResp).Msg("GetShare failed")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return
}
render.Status(r, http.StatusNoContent)
render.NoContent(w, r)
return
}
func (g Graph) extractDriveAndDriveItem(r *http.Request) (driveID storageprovider.ResourceId, itemID storageprovider.ResourceId, err error) {
driveID, err = parseIDParam(r, "driveID")
if err != nil {
g.logger.Debug().Err(err).Msg("could not parse driveID")
return driveID, itemID, errorcode.New(errorcode.InvalidRequest, "invalid driveID")
}
itemID, err = parseIDParam(r, "itemID")
if err != nil {
g.logger.Debug().Err(err).Msg("could not parse itemID")
return driveID, itemID, errorcode.New(errorcode.InvalidRequest, "invalid itemID")
}
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")
return driveID, itemID, errorcode.New(errorcode.InvalidRequest, "driveID and itemID do not match")
}
return driveID, itemID, nil
}
func (g Graph) getDriveItem(ctx context.Context, ref storageprovider.Reference) (*libregraph.DriveItem, error) {
gatewayClient, err := g.gatewaySelector.Next()
if err != nil {
@@ -100,6 +100,137 @@ var _ = Describe("Driveitems", func() {
)
})
Describe("DeletePermission", func() {
It("validates the driveID", func() {
rctx := chi.NewRouteContext()
rctx.URLParams.Add("driveID", "")
ctx = context.WithValue(context.Background(), chi.RouteCtxKey, rctx)
svc.DeletePermission(
rr,
httptest.NewRequest(http.MethodPost, "/", nil).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.DeletePermission(
rr,
httptest.NewRequest(http.MethodPost, "/", nil).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.DeletePermission(
rr,
httptest.NewRequest(http.MethodPost, "/", nil).WithContext(ctx),
)
Expect(rr.Code).To(Equal(http.StatusBadRequest))
})
It("deletes a permission as expected", func() {
getShareMock := gatewayClient.On("GetShare",
mock.Anything,
mock.MatchedBy(func(req *collaboration.GetShareRequest) bool {
return req.GetRef().GetId().GetOpaqueId() == "permissionid"
}),
)
getShareMockResponse := &collaboration.GetShareResponse{
Status: status.NewOK(ctx),
Share: &collaboration.Share{
Id: &collaboration.ShareId{
OpaqueId: "permissionid",
},
ResourceId: &provider.ResourceId{
StorageId: "1",
SpaceId: "2",
OpaqueId: "3",
},
},
}
getShareMock.Return(getShareMockResponse, nil)
rmShareMock := gatewayClient.On("RemoveShare",
mock.Anything,
mock.MatchedBy(func(req *collaboration.RemoveShareRequest) bool {
return req.GetRef().GetId().GetOpaqueId() == "permissionid"
}),
)
rmShareMockResponse := &collaboration.RemoveShareResponse{
Status: status.NewOK(ctx),
}
rmShareMock.Return(rmShareMockResponse, nil)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("driveID", "1$2")
rctx.URLParams.Add("itemID", "1$2!3")
rctx.URLParams.Add("permissionID", "permissionid")
ctx = context.WithValue(context.Background(), chi.RouteCtxKey, rctx)
svc.DeletePermission(
rr,
httptest.NewRequest(http.MethodPost, "/", nil).WithContext(ctx),
)
Expect(rr.Code).To(Equal(http.StatusNoContent))
})
It("fails to delete permission when the item id does not match the shared resource's id", func() {
getShareMock := gatewayClient.On("GetShare",
mock.Anything,
mock.MatchedBy(func(req *collaboration.GetShareRequest) bool {
return req.GetRef().GetId().GetOpaqueId() == "permissionid"
}),
)
getShareMockResponse := &collaboration.GetShareResponse{
Status: status.NewOK(ctx),
Share: &collaboration.Share{
Id: &collaboration.ShareId{
OpaqueId: "permissionid",
},
ResourceId: &provider.ResourceId{
StorageId: "3",
SpaceId: "4",
OpaqueId: "5",
},
},
}
getShareMock.Return(getShareMockResponse, nil)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("driveID", "1$2")
rctx.URLParams.Add("itemID", "1$2!3")
rctx.URLParams.Add("permissionID", "permissionid")
ctx = context.WithValue(context.Background(), chi.RouteCtxKey, rctx)
svc.DeletePermission(
rr,
httptest.NewRequest(http.MethodPost, "/", nil).WithContext(ctx),
)
Expect(rr.Code).To(Equal(http.StatusBadRequest))
})
})
Describe("Invite", func() {
var (
itemID string
+5 -1
View File
@@ -109,6 +109,7 @@ type Service interface {
GetDriveItemChildren(w http.ResponseWriter, r *http.Request)
Invite(w http.ResponseWriter, r *http.Request)
DeletePermission(w http.ResponseWriter, r *http.Request)
GetTags(w http.ResponseWriter, r *http.Request)
AssignTags(w http.ResponseWriter, r *http.Request)
@@ -198,7 +199,10 @@ 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("/drives/{driveID}/items/{itemID}", func(r chi.Router) {
r.Post("/invite", svc.Invite)
r.Delete("/permissions/{permissionID}", svc.DeletePermission)
})
r.Route("/roleManagement/permissions/roleDefinitions", func(r chi.Router) {
r.Get("/", svc.GetRoleDefinitions)
r.Get("/{roleID}", svc.GetRoleDefinition)