Skip to content

Commit

Permalink
enhancement: add graph invite endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
fschade committed Nov 15, 2023
1 parent a7cb546 commit d533a70
Show file tree
Hide file tree
Showing 42 changed files with 3,963 additions and 1,011 deletions.
2 changes: 2 additions & 0 deletions changelog/unreleased/enhancement-sharing-ng.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ The following endpoints are added:
* /v1beta1/me/drive/sharedWithMe
* /v1beta1/roleManagement/permissions/roleDefinitions
* /v1beta1/roleManagement/permissions/roleDefinitions/{roleID}
* /v1beta1/drives/{drive-id}/items/{item-id}/createLink (create a sharing link)

https://github.com/owncloud/ocis/pull/7633
https://github.com/owncloud/ocis/pull/7686
https://github.com/owncloud/ocis/pull/7684
https://github.com/owncloud/ocis/pull/7683
https://github.com/owncloud/ocis/pull/7239
https://github.com/owncloud/ocis/pull/7687
https://github.com/owncloud/libre-graph-api/pull/112
https://github.com/owncloud/ocis/issues/7436
https://github.com/owncloud/ocis/issues/6993
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ require (
github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus v1.2.0
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0
github.com/go-ozzo/ozzo-validation/v4 v4.3.0
github.com/go-playground/validator/v10 v10.16.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/protobuf v1.5.3
Expand Down Expand Up @@ -194,9 +195,8 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-micro/plugins/v4/events/natsjs v1.2.2-0.20230807070816-bc05fb076ce7 // indirect
github.com/go-micro/plugins/v4/store/redis v1.2.1-0.20230510195111-07cd57e1bc9d // indirect
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-resty/resty/v2 v2.7.0 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
Expand Down Expand Up @@ -247,7 +247,7 @@ require (
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/libregraph/oidc-go v1.0.0 // indirect
github.com/longsleep/go-metrics v1.0.0 // indirect
github.com/longsleep/rndm v1.2.0 // indirect
Expand Down
19 changes: 9 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1213,14 +1213,13 @@ github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead5
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
Expand Down Expand Up @@ -1597,8 +1596,8 @@ github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HK
github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/leonelquinteros/gotext v1.5.3-0.20230317130943-71a59c05b2c1 h1:k56sFOOJ0CYuQtGoRSeAMhP1R692+iNH+S1dC/CEz0w=
github.com/leonelquinteros/gotext v1.5.3-0.20230317130943-71a59c05b2c1/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M=
github.com/libregraph/idm v0.4.1-0.20230221143410-3503963047a5 h1:brLMXSjWoWhGXs8LpK+Lx+FQCtGLUa51Mq/ggHv9AV0=
Expand Down
232 changes: 222 additions & 10 deletions services/graph/pkg/service/v0/driveitems.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package svc

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
Expand All @@ -10,19 +11,29 @@ 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/chi/v5"
"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.
Expand Down Expand Up @@ -234,6 +245,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 := storagespace.ParseID(chi.URLParam(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 := storagespace.ParseID(chi.URLParam(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 {
Expand Down Expand Up @@ -531,14 +743,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)
}

Expand Down
Loading

0 comments on commit d533a70

Please sign in to comment.