diff --git a/internal/grpc/services/gateway/publicshareprovider.go b/internal/grpc/services/gateway/publicshareprovider.go index 299c576ba6..fcbb675279 100644 --- a/internal/grpc/services/gateway/publicshareprovider.go +++ b/internal/grpc/services/gateway/publicshareprovider.go @@ -33,10 +33,17 @@ func (s *svc) CreatePublicShare(ctx context.Context, req *publicshareproviderv0a log := appctx.GetLogger(ctx) log.Info().Msg("create public share") - res := &publicshareproviderv0alphapb.CreatePublicShareResponse{ - Status: status.NewOK(ctx), - // Share: share, + c, err := pool.GetPublicShareProviderClient(s.c.PublicShareProviderEndpoint) + if err != nil { + return nil, err + } + + res, err := c.CreatePublicShare(ctx, req) + if err != nil { + return nil, err } + + // TODO(refs) commit to storage if configured return res, nil } @@ -70,11 +77,11 @@ func (s *svc) GetPublicShare(ctx context.Context, req *publicshareproviderv0alph func (s *svc) ListPublicShares(ctx context.Context, req *publicshareproviderv0alphapb.ListPublicSharesRequest) (*publicshareproviderv0alphapb.ListPublicSharesResponse, error) { log := appctx.GetLogger(ctx) - log.Info().Msg("list public share") + log.Info().Msg("listing public shares") - pClient, err := pool.GetPublicShareProviderClient(s.c.UserShareProviderEndpoint) + pClient, err := pool.GetPublicShareProviderClient(s.c.PublicShareProviderEndpoint) if err != nil { - log.Err(err).Msg("gateway: error getting usershareprovider client") + log.Err(err).Msg("error connecting to a public share provider") return &publicshareproviderv0alphapb.ListPublicSharesResponse{ Status: &rpcpb.Status{ Code: rpcpb.Code_CODE_INTERNAL, @@ -84,7 +91,7 @@ func (s *svc) ListPublicShares(ctx context.Context, req *publicshareproviderv0al res, err := pClient.ListPublicShares(ctx, req) if err != nil { - return nil, errors.Wrap(err, "gateway: error calling ListShares") + return nil, errors.Wrap(err, "error calling ListShares") } // res := &publicshareproviderv0alphapb.ListPublicSharesResponse{ diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 2bbf9a2591..05ef6242c9 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -285,6 +285,7 @@ func (s *svc) UnsetArbitraryMetadata(ctx context.Context, req *storageproviderv0 } func (s *svc) Stat(ctx context.Context, req *storageproviderv0alphapb.StatRequest) (*storageproviderv0alphapb.StatResponse, error) { + // TODO(refs) do we need to append home to every stat request? c, err := s.find(ctx, req.Ref) if err != nil { if _, ok := err.(errtypes.IsNotFound); ok { diff --git a/internal/grpc/services/publicshareprovider/publicshareprovider.go b/internal/grpc/services/publicshareprovider/publicshareprovider.go index aff9f68168..d3fe43f76a 100644 --- a/internal/grpc/services/publicshareprovider/publicshareprovider.go +++ b/internal/grpc/services/publicshareprovider/publicshareprovider.go @@ -97,9 +97,19 @@ func (s *service) CreatePublicShare(ctx context.Context, req *publicshareprovide log := appctx.GetLogger(ctx) log.Info().Msg("create public share") + u, ok := user.ContextGetUser(ctx) + if !ok { + log.Error().Msg("error getting user from context") + } + + share, err := s.sm.CreatePublicShare(ctx, u, req.ResourceInfo, req.Grant) + if err != nil { + log.Debug().Err(err).Str("createShare", "shares").Msg("error connecting to storage provider") + } + res := &publicshareproviderv0alphapb.CreatePublicShareResponse{ Status: status.NewOK(ctx), - // Share: share, + Share: share, } return res, nil } diff --git a/internal/http/services/owncloud/ocs/conversions/main.go b/internal/http/services/owncloud/ocs/conversions/main.go new file mode 100644 index 0000000000..068bd79ae0 --- /dev/null +++ b/internal/http/services/owncloud/ocs/conversions/main.go @@ -0,0 +1,402 @@ +// Copyright 2018-2019 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 conversions sits between CS3 type definitions and OCS API Responses +package conversions + +import ( + "fmt" + "time" + + "github.com/cs3org/reva/pkg/publicshare" + "github.com/cs3org/reva/pkg/user" + + publicshareproviderv0alphapb "github.com/cs3org/go-cs3apis/cs3/publicshareprovider/v0alpha" + storageprovider "github.com/cs3org/go-cs3apis/cs3/storageprovider/v0alpha" + typespb "github.com/cs3org/go-cs3apis/cs3/types" + usershareproviderv0alphapb "github.com/cs3org/go-cs3apis/cs3/usershareprovider/v0alpha" + publicsharemgr "github.com/cs3org/reva/pkg/publicshare/manager/registry" + usermgr "github.com/cs3org/reva/pkg/user/manager/registry" +) + +const ( + // ShareTypeUser refers to user shares + ShareTypeUser ShareType = 0 + + // ShareTypePublicLink refers to public link shares + ShareTypePublicLink ShareType = 3 + + // ShareTypeGroup represents a group share + // ShareTypeGroup shareType = 1 + + // ShareTypeFederatedCloudShare represents a federated share + // ShareTypeFederatedCloudShare shareType = 6 +) + +// ResourceType indicates the OCS type of the resource +type ResourceType int + +func (rt ResourceType) String() (s string) { + switch rt { + case 0: + s = "invalid" + case 1: + s = "file" + case 2: + s = "folder" + case 3: + s = "reference" + default: + s = "invalid" + } + return +} + +// Permissions reflects the CRUD permissions used in the OCS sharing API +type Permissions uint + +// ShareType denotes a type of share +type ShareType int + +// Element is a commodity type wraps members of any interface (as per Owncloud's OCS V1 Spec) +type Element struct { + Data interface{} `json:"element" xml:"element"` +} + +// ShareData represents https://doc.owncloud.com/server/developer_manual/core/ocs-share-api.html#response-attributes-1 +type ShareData struct { + // TODO int? + ID string `json:"id" xml:"id"` + // The share’s type + ShareType ShareType `json:"share_type" xml:"share_type"` + // The username of the owner of the share. + UIDOwner string `json:"uid_owner" xml:"uid_owner"` + // The display name of the owner of the share. + DisplaynameOwner string `json:"displayname_owner" xml:"displayname_owner"` + // The permission attribute set on the file. + // TODO(jfd) change the default to read only + Permissions Permissions `json:"permissions" xml:"permissions"` + // The UNIX timestamp when the share was created. + STime uint64 `json:"stime" xml:"stime"` + // ? + Parent string `json:"parent" xml:"parent"` + // The UNIX timestamp when the share expires. + Expiration string `json:"expiration" xml:"expiration"` + // The public link to the item being shared. + Token string `json:"token" xml:"token"` + // The unique id of the user that owns the file or folder being shared. + UIDFileOwner string `json:"uid_file_owner" xml:"uid_file_owner"` + // The display name of the user that owns the file or folder being shared. + DisplaynameFileOwner string `json:"displayname_file_owner" xml:"displayname_file_owner"` + // The path to the shared file or folder. + Path string `json:"path" xml:"path"` + // The type of the object being shared. This can be one of file or folder. + ItemType string `json:"item_type" xml:"item_type"` + // The RFC2045-compliant mimetype of the file. + MimeType string `json:"mimetype" xml:"mimetype"` + StorageID string `json:"storage_id" xml:"storage_id"` + Storage uint64 `json:"storage" xml:"storage"` + // The unique node id of the item being shared. + ItemSource string `json:"item_source" xml:"item_source"` + // The unique node id of the item being shared. For legacy reasons item_source and file_source attributes have the same value. + FileSource string `json:"file_source" xml:"file_source"` + // The unique node id of the parent node of the item being shared. + FileParent string `json:"file_parent" xml:"file_parent"` + // The name of the shared file. + FileTarget string `json:"file_target" xml:"file_target"` + // The uid of the receiver of the file. This is either + // - a GID (group id) if it is being shared with a group or + // - a UID (user id) if the share is shared with a user. + ShareWith string `json:"share_with" xml:"share_with"` + // The display name of the receiver of the file. + ShareWithDisplayname string `json:"share_with_displayname" xml:"share_with_displayname"` + // sharee Additional info + ShareWithAdditionalInfo string `json:"share_with_additional_info" xml:"share_with_additional_info"` + // Whether the recipient was notified, by mail, about the share being shared with them. + MailSend string `json:"mail_send" xml:"mail_send"` +} + +// ShareeData holds share recipient search results +type ShareeData struct { + Exact *ExactMatchesData `json:"exact" xml:"exact"` + Users []*MatchData `json:"users" xml:"users"` + Groups []*MatchData `json:"groups" xml:"groups"` + Remotes []*MatchData `json:"remotes" xml:"remotes"` +} + +// ExactMatchesData hold exact matches +type ExactMatchesData struct { + Users []*MatchData `json:"users" xml:"users"` + Groups []*MatchData `json:"groups" xml:"groups"` + Remotes []*MatchData `json:"remotes" xml:"remotes"` +} + +// MatchData describes a single match +type MatchData struct { + Label string `json:"label" xml:"label"` + Value *MatchValueData `json:"value" xml:"value"` +} + +// MatchValueData holds the type and actual value +type MatchValueData struct { + ShareType int `json:"shareType" xml:"shareType"` + ShareWith string `json:"shareWith" xml:"shareWith"` +} + +// Role2CS3Permissions converts string roles (from the request body) into cs3 permissions +// TODO(refs) consider using a mask instead of booleans here, might reduce all this boilerplate +func Role2CS3Permissions(r string) (*storageprovider.ResourcePermissions, error) { + switch r { + case RoleViewer: + return &storageprovider.ResourcePermissions{ + ListContainer: true, + ListGrants: true, + ListFileVersions: true, + ListRecycle: true, + Stat: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + }, nil + case RoleEditor: + return &storageprovider.ResourcePermissions{ + ListContainer: true, + ListGrants: true, + ListFileVersions: true, + ListRecycle: true, + Stat: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + + Move: true, + InitiateFileUpload: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + CreateContainer: true, + Delete: true, + PurgeRecycle: true, + }, nil + case RoleCoowner: + return &storageprovider.ResourcePermissions{ + ListContainer: true, + ListGrants: true, + ListFileVersions: true, + ListRecycle: true, + Stat: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + + Move: true, + InitiateFileUpload: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + CreateContainer: true, + Delete: true, + PurgeRecycle: true, + + AddGrant: true, + RemoveGrant: true, // TODO when are you able to unshare / delete + UpdateGrant: true, + }, nil + default: + return nil, fmt.Errorf("unknown role: %s", r) + } +} + +// AsCS3Permissions returns permission values as cs3api permissions +// TODO sort out mapping, this is just a first guess +// TODO use roles to make this configurable +func AsCS3Permissions(p int, rp *storageprovider.ResourcePermissions) *storageprovider.ResourcePermissions { + if rp == nil { + rp = &storageprovider.ResourcePermissions{} + } + + if p&int(PermissionRead) != 0 { + rp.ListContainer = true + rp.ListGrants = true + rp.ListFileVersions = true + rp.ListRecycle = true + rp.Stat = true + rp.GetPath = true + rp.GetQuota = true + rp.InitiateFileDownload = true + } + if p&int(PermissionWrite) != 0 { + rp.InitiateFileUpload = true + rp.RestoreFileVersion = true + rp.RestoreRecycleItem = true + } + if p&int(PermissionCreate) != 0 { + rp.CreateContainer = true + // FIXME permissions mismatch: double check create vs write file + rp.InitiateFileUpload = true + if p&int(PermissionWrite) != 0 { + rp.Move = true // TODO move only when create and write? + } + } + if p&int(PermissionDelete) != 0 { + rp.Delete = true + rp.PurgeRecycle = true + } + if p&int(PermissionShare) != 0 { + rp.AddGrant = true + rp.RemoveGrant = true // TODO when are you able to unshare / delete + rp.UpdateGrant = true + } + return rp +} + +// PublicShare2ShareData converts a cs3api public share into shareData data model +func PublicShare2ShareData(share *publicshareproviderv0alphapb.PublicShare) *ShareData { + return &ShareData{ + // TODO map share.resourceId to path and storage ... requires a stat call + // share.permissions ar mapped below + // TODO lookup user metadata + //DisplaynameOwner: creator.DisplayName, + // TODO lookup user metadata + // DisplaynameFileOwner: owner.DisplayName, + ID: share.Id.OpaqueId, + Permissions: publicSharePermissions2OCSPermissions(share.GetPermissions()), + ShareType: ShareTypePublicLink, + UIDOwner: UserIDToString(share.Creator), + STime: share.Ctime.Seconds, // TODO CS3 api birth time = btime + UIDFileOwner: UserIDToString(share.Owner), + Token: share.Token, + Expiration: timestampToExpiration(share.Expiration), + } + // actually clients should be able to GET and cache the user info themselves ... + // TODO check grantee type for user vs group +} + +// UserIDToString transforms a cs3api user id into an ocs data model +func UserIDToString(userID *typespb.UserId) string { + if userID == nil || userID.OpaqueId == "" { + return "" + } + if userID.Idp == "" { + return userID.OpaqueId + } + return userID.OpaqueId + "@" + userID.Idp +} + +// UserSharePermissions2OCSPermissions transforms cs3api permissions into OCS Permissions data model +func UserSharePermissions2OCSPermissions(sp *usershareproviderv0alphapb.SharePermissions) Permissions { + if sp != nil { + return permissions2OCSPermissions(sp.GetPermissions()) + } + return PermissionInvalid +} + +// GetUserManager returns a connection to a user share manager +func GetUserManager(manager string, m map[string]map[string]interface{}) (user.Manager, error) { + if f, ok := usermgr.NewFuncs[manager]; ok { + return f(m[manager]) + } + + return nil, fmt.Errorf("driver %s not found for user manager", manager) +} + +// GetPublicShareManager returns a connection to a public share manager +func GetPublicShareManager(manager string, m map[string]map[string]interface{}) (publicshare.Manager, error) { + if f, ok := publicsharemgr.NewFuncs[manager]; ok { + return f(m[manager]) + } + + return nil, fmt.Errorf("driver %s not found for public shares manager", manager) +} + +// Permissions2Role performs permission conversions +func Permissions2Role(p int) string { + role := RoleLegacy + if p == int(PermissionRead) { + role = RoleViewer + } + if p&int(PermissionWrite) == 1 { + role = RoleEditor + } + if p&int(PermissionShare) == 1 { + role = RoleCoowner + } + return role +} + +func publicSharePermissions2OCSPermissions(sp *publicshareproviderv0alphapb.PublicSharePermissions) Permissions { + if sp != nil { + return permissions2OCSPermissions(sp.GetPermissions()) + } + return PermissionInvalid +} + +// TODO sort out mapping, this is just a first guess +func permissions2OCSPermissions(p *storageprovider.ResourcePermissions) Permissions { + permissions := PermissionInvalid + if p != nil { + if p.ListContainer { + permissions += PermissionRead + } + if p.InitiateFileUpload { + permissions += PermissionWrite + } + if p.CreateContainer { + permissions += PermissionCreate + } + if p.Delete { + permissions += PermissionDelete + } + if p.AddGrant { + permissions += PermissionShare + } + } + return permissions +} + +// timestamp is assumed to be UTC ... just human readable ... +// FIXME and ambiguous / error prone because there is no time zone ... +func timestampToExpiration(t *typespb.Timestamp) string { + return time.Unix(int64(t.Seconds), int64(t.Nanos)).Format("2006-01-02 15:05:05") +} + +const ( + // RoleLegacy provides backwards compatibility + RoleLegacy string = "legacy" + // RoleViewer grants non-editor role on a resource + RoleViewer string = "viewer" + // RoleEditor grants editor permission on a resource + RoleEditor string = "editor" + // RoleCoowner grants owner permissions on a resource + RoleCoowner string = "coowner" +) + +const ( + // PermissionInvalid grants no permissions on a resource + PermissionInvalid Permissions = 0 + // PermissionRead grants read permissions on a resource + PermissionRead Permissions = 1 + // PermissionWrite grants write permissions on a resource + PermissionWrite Permissions = 2 + // PermissionCreate grants create permissions on a resource + PermissionCreate Permissions = 4 + // PermissionDelete grants delete permissions on a resource + PermissionDelete Permissions = 8 + // PermissionShare grants share permissions on a resource + PermissionShare Permissions = 16 + // PermissionAll grants all permissions on a resource + //PermissionAll Permissions = 31 +) diff --git a/internal/http/services/owncloud/ocs/shares.go b/internal/http/services/owncloud/ocs/shares.go index 0a85f548cc..89f9487baa 100644 --- a/internal/http/services/owncloud/ocs/shares.go +++ b/internal/http/services/owncloud/ocs/shares.go @@ -21,6 +21,7 @@ package ocs import ( "context" "encoding/json" + "errors" "fmt" "net/http" "path" @@ -28,26 +29,27 @@ import ( "strings" "time" + gatewayv0alpahpb "github.com/cs3org/go-cs3apis/cs3/gateway/v0alpha" publicshareproviderv0alphapb "github.com/cs3org/go-cs3apis/cs3/publicshareprovider/v0alpha" rpcpb "github.com/cs3org/go-cs3apis/cs3/rpc" storageproviderv0alphapb "github.com/cs3org/go-cs3apis/cs3/storageprovider/v0alpha" typespb "github.com/cs3org/go-cs3apis/cs3/types" userproviderv0alphapb "github.com/cs3org/go-cs3apis/cs3/userprovider/v0alpha" usershareproviderv0alphapb "github.com/cs3org/go-cs3apis/cs3/usershareprovider/v0alpha" + + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/rhttp" - "github.com/cs3org/reva/pkg/user" ) // SharesHandler implements the ownCloud sharing API type SharesHandler struct { - gatewaySvc string - userManager user.Manager + gatewayAddr string } func (h *SharesHandler) init(c *Config) error { - h.gatewaySvc = c.GatewaySvc + h.gatewayAddr = c.GatewaySvc return nil } @@ -63,15 +65,14 @@ func (h *SharesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { case "shares": switch r.Method { case "OPTIONS": - w.WriteHeader(http.StatusOK) // TODO cors? + w.WriteHeader(http.StatusOK) return case "GET": h.listShares(w, r) case "POST": h.createShare(w, r) case "PUT": - // TODO PUT is used with incomplete data to update a share 🤦 - h.updateShare(w, r) + 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) } @@ -83,50 +84,53 @@ func (h *SharesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func (h *SharesHandler) findSharees(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - log := appctx.GetLogger(ctx) - - search := r.URL.Query().Get("search") + log := appctx.GetLogger(r.Context()) + term := r.URL.Query().Get("search") - if search == "" { + if term == "" { WriteOCSError(w, r, MetaBadRequest.StatusCode, "search must not be empty", nil) return } - // TODO sanitize query - users, err := h.userManager.FindUsers(ctx, search) + gatewayProvider := mustGetGateway(h.gatewayAddr, r, w) + + req := userproviderv0alphapb.FindUsersRequest{ + Filter: term, + } + + res, err := gatewayProvider.FindUsers(r.Context(), &req) if err != nil { WriteOCSError(w, r, MetaServerError.StatusCode, "error searching users", err) return } - log.Debug().Int("count", len(users)).Str("search", search).Msg("users found") + log.Debug().Int("count", len(res.GetUsers())).Str("search", term).Msg("users found") - matches := make([]*MatchData, 0, len(users)) + matches := make([]*conversions.MatchData, 0, len(res.GetUsers())) - for _, user := range users { + for _, user := range res.GetUsers() { match := h.userAsMatch(user) log.Debug().Interface("user", user).Interface("match", match).Msg("mapped") matches = append(matches, match) } - WriteOCSSuccess(w, r, &ShareeData{ - Exact: &ExactMatchesData{ - Users: []*MatchData{}, - Groups: []*MatchData{}, - Remotes: []*MatchData{}, + WriteOCSSuccess(w, r, &conversions.ShareeData{ + Exact: &conversions.ExactMatchesData{ + Users: []*conversions.MatchData{}, + Groups: []*conversions.MatchData{}, + Remotes: []*conversions.MatchData{}, }, Users: matches, - Groups: []*MatchData{}, - Remotes: []*MatchData{}, + Groups: []*conversions.MatchData{}, + Remotes: []*conversions.MatchData{}, }) } -func (h *SharesHandler) userAsMatch(u *userproviderv0alphapb.User) *MatchData { - return &MatchData{ +func (h *SharesHandler) userAsMatch(u *userproviderv0alphapb.User) *conversions.MatchData { + return &conversions.MatchData{ Label: u.DisplayName, - Value: &MatchValueData{ - ShareType: int(shareTypeUser), + Value: &conversions.MatchValueData{ + ShareType: int(conversions.ShareTypeUser), // TODO(jfd) find more robust userid // username might be ok as it is uniqe at a given point in time ShareWith: u.Username, @@ -144,10 +148,10 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { return } - if shareType == int(shareTypeUser) { + if shareType == int(conversions.ShareTypeUser) { // if user sharing is disabled - if h.gatewaySvc == "" { + if h.gatewayAddr == "" { WriteOCSError(w, r, MetaServerError.StatusCode, "user sharing service not configured", nil) return } @@ -158,31 +162,28 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { return } - // find recipient based on username - users, err := h.userManager.FindUsers(ctx, shareWith) + 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) + return + } + + res, err := gatewayClient.FindUsers(ctx, &userproviderv0alphapb.FindUsersRequest{ + Filter: shareWith, + }) if err != nil { WriteOCSError(w, r, MetaServerError.StatusCode, "error searching recipient", err) return } + var recipient *userproviderv0alphapb.User - for _, user := range users { + for _, user := range res.GetUsers() { if user.Username == shareWith { recipient = user break } } - // we need to prefix the path with the user id - u, ok := user.ContextGetUser(ctx) - if !ok { - WriteOCSError(w, r, MetaServerError.StatusCode, "missing user in context", fmt.Errorf("missing user in context")) - return - } - - // TODO how do we get the home of a user? The path in the sharing api is relative to the users home - p := r.FormValue("path") - p = path.Join("/", u.Username, p) - var pint int role := r.FormValue("role") @@ -191,7 +192,7 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { pval := r.FormValue("permissions") if pval == "" { // by default only allow read permissions / assign viewer role - role = roleViewer + role = conversions.RoleViewer } else { pint, err = strconv.Atoi(pval) if err != nil { @@ -202,8 +203,6 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { } } - // map role to permissions - var permissions *storageproviderv0alphapb.ResourcePermissions permissions, err = h.role2CS3Permissions(role) if err != nil { @@ -211,11 +210,6 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { permissions = asCS3Permissions(pint, nil) } - uClient, err := pool.GetUserShareProviderClient(h.gatewaySvc) - if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error getting grpc client", err) - return - } roleMap := map[string]string{"name": role} val, err := json.Marshal(roleMap) if err != nil { @@ -223,20 +217,20 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { return } + sClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + WriteOCSError(w, r, MetaServerError.StatusCode, "error getting storage grpc client", err) + return + } + statReq := &storageproviderv0alphapb.StatRequest{ Ref: &storageproviderv0alphapb.Reference{ Spec: &storageproviderv0alphapb.Reference_Path{ - Path: p, + Path: r.FormValue("path"), }, }, } - sClient, err := pool.GetGatewayServiceClient(h.gatewaySvc) - if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error getting storage grpc client", err) - return - } - statRes, err := sClient.Stat(ctx, statReq) if err != nil { WriteOCSError(w, r, MetaServerError.StatusCode, "error sending a grpc stat request", err) @@ -252,7 +246,7 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { return } - req := &usershareproviderv0alphapb.CreateShareRequest{ + createShareReq := &usershareproviderv0alphapb.CreateShareRequest{ Opaque: &typespb.Opaque{ Map: map[string]*typespb.OpaqueEntry{ "role": &typespb.OpaqueEntry{ @@ -272,20 +266,21 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { }, }, } - res, err := uClient.CreateShare(ctx, req) + + createShareResponse, err := gatewayClient.CreateShare(ctx, createShareReq) if err != nil { WriteOCSError(w, r, MetaServerError.StatusCode, "error sending a grpc create share request", err) return } - if res.Status.Code != rpcpb.Code_CODE_OK { - if res.Status.Code == rpcpb.Code_CODE_NOT_FOUND { + if createShareResponse.Status.Code != rpcpb.Code_CODE_OK { + if createShareResponse.Status.Code == rpcpb.Code_CODE_NOT_FOUND { WriteOCSError(w, r, MetaNotFound.StatusCode, "not found", nil) return } WriteOCSError(w, r, MetaServerError.StatusCode, "grpc create share request failed", err) return } - s, err := h.userShare2ShareData(ctx, res.Share) + s, err := h.userShare2ShareData(ctx, createShareResponse.Share) if err != nil { WriteOCSError(w, r, MetaServerError.StatusCode, "error mapping share data", err) return @@ -293,33 +288,65 @@ func (h *SharesHandler) createShare(w http.ResponseWriter, r *http.Request) { s.Path = r.FormValue("path") // use path without user prefix WriteOCSSuccess(w, r, s) return - } - WriteOCSError(w, r, MetaBadRequest.StatusCode, "unknown share type", nil) -} + if shareType == int(conversions.ShareTypePublicLink) { + // create a public link share + // get a connection to the public shares service + 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")) + return + } -// TODO sort out mapping, this is just a first guess -func permissions2OCSPermissions(p *storageproviderv0alphapb.ResourcePermissions) Permissions { - permissions := permissionInvalid - if p != nil { - if p.ListContainer { - permissions += permissionRead + statReq := storageproviderv0alphapb.StatRequest{ + Ref: &storageproviderv0alphapb.Reference{ + Spec: &storageproviderv0alphapb.Reference_Path{ + Path: r.FormValue("path"), + }, + }, } - if p.InitiateFileUpload { - permissions += permissionWrite + + 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")) + return } - if p.CreateContainer { - permissions += permissionCreate + + // TODO(refs) set expiration date to whatever phoenix sends + req := publicshareproviderv0alphapb.CreatePublicShareRequest{ + ResourceInfo: statRes.GetInfo(), + Grant: &publicshareproviderv0alphapb.Grant{ + Expiration: &typespb.Timestamp{ + Nanos: uint32(time.Now().Add(time.Duration(31536000)).Nanosecond()), + Seconds: uint64(time.Now().Add(time.Duration(31536000)).Second()), + }, + }, } - if p.Delete { - permissions += permissionDelete + + 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())) + return } - if p.AddGrant { - permissions += permissionShare + + if createRes.Status.Code != rpcpb.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) + return } + + // build ocs response for Phoenix + s := conversions.PublicShare2ShareData(createRes.Share) + WriteOCSSuccess(w, r, s) + + return } - return permissions + + WriteOCSError(w, r, MetaBadRequest.StatusCode, "unknown share type", nil) } // TODO sort out mapping, this is just a first guess @@ -329,7 +356,7 @@ func asCS3Permissions(p int, rp *storageproviderv0alphapb.ResourcePermissions) * rp = &storageproviderv0alphapb.ResourcePermissions{} } - if p&int(permissionRead) != 0 { + if p&int(conversions.PermissionRead) != 0 { rp.ListContainer = true rp.ListGrants = true rp.ListFileVersions = true @@ -339,24 +366,24 @@ func asCS3Permissions(p int, rp *storageproviderv0alphapb.ResourcePermissions) * rp.GetQuota = true rp.InitiateFileDownload = true } - if p&int(permissionWrite) != 0 { + if p&int(conversions.PermissionWrite) != 0 { rp.InitiateFileUpload = true rp.RestoreFileVersion = true rp.RestoreRecycleItem = true } - if p&int(permissionCreate) != 0 { + if p&int(conversions.PermissionCreate) != 0 { rp.CreateContainer = true // FIXME permissions mismatch: double check create vs write file rp.InitiateFileUpload = true - if p&int(permissionWrite) != 0 { + if p&int(conversions.PermissionWrite) != 0 { rp.Move = true // TODO move only when create and write? } } - if p&int(permissionDelete) != 0 { + if p&int(conversions.PermissionDelete) != 0 { rp.Delete = true rp.PurgeRecycle = true } - if p&int(permissionShare) != 0 { + if p&int(conversions.PermissionShare) != 0 { rp.AddGrant = true rp.RemoveGrant = true // TODO when are you able to unshare / delete rp.UpdateGrant = true @@ -365,22 +392,22 @@ func asCS3Permissions(p int, rp *storageproviderv0alphapb.ResourcePermissions) * } func (h *SharesHandler) permissions2Role(p int) string { - role := roleLegacy - if p == int(permissionRead) { - role = roleViewer + role := conversions.RoleLegacy + if p == int(conversions.PermissionRead) { + role = conversions.RoleViewer } - if p&int(permissionWrite) == 1 { - role = roleEditor + if p&int(conversions.PermissionWrite) == 1 { + role = conversions.RoleEditor } - if p&int(permissionShare) == 1 { - role = roleCoowner + if p&int(conversions.PermissionShare) == 1 { + role = conversions.RoleCoowner } return role } func (h *SharesHandler) role2CS3Permissions(r string) (*storageproviderv0alphapb.ResourcePermissions, error) { switch r { - case roleViewer: + case conversions.RoleViewer: return &storageproviderv0alphapb.ResourcePermissions{ ListContainer: true, ListGrants: true, @@ -391,7 +418,7 @@ func (h *SharesHandler) role2CS3Permissions(r string) (*storageproviderv0alphapb GetQuota: true, InitiateFileDownload: true, }, nil - case roleEditor: + case conversions.RoleEditor: return &storageproviderv0alphapb.ResourcePermissions{ ListContainer: true, ListGrants: true, @@ -410,7 +437,7 @@ func (h *SharesHandler) role2CS3Permissions(r string) (*storageproviderv0alphapb Delete: true, PurgeRecycle: true, }, nil - case roleCoowner: + case conversions.RoleCoowner: return &storageproviderv0alphapb.ResourcePermissions{ ListContainer: true, ListGrants: true, @@ -456,7 +483,7 @@ func (h *SharesHandler) updateShare(w http.ResponseWriter, r *http.Request) { shareID := strings.TrimLeft(r.URL.Path, "/") // TODO we need to lookup the storage that is responsible for this share - uClient, err := pool.GetUserShareProviderClient(h.gatewaySvc) + uClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { WriteOCSError(w, r, MetaServerError.StatusCode, "error getting grpc client", err) return @@ -528,146 +555,289 @@ func (h *SharesHandler) updateShare(w http.ResponseWriter, r *http.Request) { } func (h *SharesHandler) listShares(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - log := appctx.GetLogger(ctx) - + shares := make([]*conversions.ShareData, 0) filters := []*usershareproviderv0alphapb.ListSharesRequest_Filter{} - var info *storageproviderv0alphapb.ResourceInfo + var err error + + // do shared with me. Please abstract this piece, this reads like hell. + 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) + return + } + if listSharedWithMe { + sharedWithMe := h.listSharedWithMe(r) + sClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + WriteOCSError(w, r, MetaServerError.StatusCode, err.Error(), err) + return + } + + // TODO(refs) filter out "invalid" shares + for _, v := range sharedWithMe { + statRequest := storageproviderv0alphapb.StatRequest{ + Ref: &storageproviderv0alphapb.Reference{ + Spec: &storageproviderv0alphapb.Reference_Id{ + Id: v.Share.ResourceId, + }, + }, + } + + statResponse, err := sClient.Stat(r.Context(), &statRequest) + if err != nil { + WriteOCSError(w, r, 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) + return + } + + err = h.addFileInfo(r.Context(), data, statResponse.Info) + if err != nil { + WriteOCSError(w, r, MetaServerError.StatusCode, err.Error(), err) + return + } + + shares = append(shares, data) + } + + WriteOCSSuccess(w, r, shares) + return + } + } + + // shared with others p := r.URL.Query().Get("path") - log.Debug().Str("path", p).Msg("listShares") if p != "" { - // we need to prefix the path with the user id - u, ok := user.ContextGetUser(ctx) - if !ok { - WriteOCSError(w, r, MetaServerError.StatusCode, "missing user in context", fmt.Errorf("missing user in context")) + filters, err = h.addFilters(w, r) + if err != nil { + WriteOCSError(w, r, MetaServerError.StatusCode, err.Error(), err) return } - // TODO how do we get the home of a user? The path in the sharing api is relative to the users home - fn := path.Join("/", u.Username, p) + } + + userShares, err := h.listUserShares(r, filters) + if err != nil { + WriteOCSError(w, r, MetaServerError.StatusCode, err.Error(), err) + return + } + + publicShares, err := h.listPublicShares(r) + if err != nil { + WriteOCSError(w, r, MetaServerError.StatusCode, err.Error(), err) + return + } + + shares = append(shares, append(userShares, publicShares...)...) + + if h.isReshareRequest(r) { + WriteOCSSuccess(w, r, &conversions.Element{Data: shares}) + return + } + + WriteOCSSuccess(w, r, shares) +} + +func (h *SharesHandler) listSharedWithMe(r *http.Request) []*usershareproviderv0alphapb.ReceivedShare { + c, err := pool.GetUserShareProviderClient(h.gatewayAddr) + if err != nil { + panic(err) + } - log.Debug().Str("path", p).Str("fn", fn).Interface("user", u).Msg("resolved path for user") + lrs := usershareproviderv0alphapb.ListReceivedSharesRequest{} + // TODO(refs) handle error... + shares, _ := c.ListReceivedShares(r.Context(), &lrs) + return shares.GetShares() +} - // first check if the file exists - sClient, err := pool.GetGatewayServiceClient(h.gatewaySvc) +func (h *SharesHandler) isReshareRequest(r *http.Request) bool { + return r.URL.Query().Get("reshares") != "" +} + +func (h *SharesHandler) listPublicShares(r *http.Request) ([]*conversions.ShareData, error) { + ctx := r.Context() + log := appctx.GetLogger(ctx) + + // TODO(refs) why is this guard needed? Are we moving towards a gateway only for service discovery? without a gateway this is dead code. + if h.gatewayAddr != "" { + c, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error getting grpc storage provider client", err) - return + return nil, err } - ref := &storageproviderv0alphapb.Reference{ - Spec: &storageproviderv0alphapb.Reference_Path{Path: fn}, + filters := []*publicshareproviderv0alphapb.ListPublicSharesRequest_Filter{} + req := publicshareproviderv0alphapb.ListPublicSharesRequest{ + Filters: filters, } - req := &storageproviderv0alphapb.StatRequest{Ref: ref} - res, err := sClient.Stat(ctx, req) + + res, err := c.ListPublicShares(ctx, &req) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error sending a grpc stat request", err) - return + return nil, err } - if res.Status.Code != rpcpb.Code_CODE_OK { - if res.Status.Code == rpcpb.Code_CODE_NOT_FOUND { - WriteOCSError(w, r, MetaNotFound.StatusCode, "not found", nil) - return + ocsDataPayload := make([]*conversions.ShareData, 0) + for _, share := range res.GetShare() { + sClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + return nil, err } - WriteOCSError(w, r, MetaServerError.StatusCode, "grpc stat request failed", err) - return + + statRequest := &storageproviderv0alphapb.StatRequest{ + Ref: &storageproviderv0alphapb.Reference{ + Spec: &storageproviderv0alphapb.Reference_Id{ + Id: share.ResourceId, + }, + }, + } + + statResponse, err := sClient.Stat(ctx, statRequest) + if err != nil { + return nil, err + } + + sData := conversions.PublicShare2ShareData(share) + if statResponse.Status.Code != rpcpb.Code_CODE_OK { + return nil, err + } + + if h.addFileInfo(ctx, sData, statResponse.Info) != nil { + return nil, err + } + + log.Debug().Interface("share", share).Interface("info", statResponse.Info).Interface("shareData", share).Msg("mapped") + ocsDataPayload = append(ocsDataPayload, sData) + } - info = res.Info + return ocsDataPayload, nil + } - log.Debug().Interface("info", info).Msg("path found") + return nil, errors.New("bad request") +} + +func (h *SharesHandler) addFilters(w http.ResponseWriter, r *http.Request) ([]*usershareproviderv0alphapb.ListSharesRequest_Filter, error) { + filters := []*usershareproviderv0alphapb.ListSharesRequest_Filter{} + var info *storageproviderv0alphapb.ResourceInfo + ctx := r.Context() + + // 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) + return nil, err + } - filters = append(filters, &usershareproviderv0alphapb.ListSharesRequest_Filter{ - Type: usershareproviderv0alphapb.ListSharesRequest_Filter_LIST_SHARES_REQUEST_FILTER_TYPE_RESOURCE_ID, - Term: &usershareproviderv0alphapb.ListSharesRequest_Filter_ResourceId{ - ResourceId: info.Id, + statReq := &storageproviderv0alphapb.StatRequest{ + Ref: &storageproviderv0alphapb.Reference{ + Spec: &storageproviderv0alphapb.Reference_Path{ + Path: r.FormValue("path"), }, - }) + }, } - shares := []*ShareData{} + res, err := gwClient.Stat(ctx, statReq) + if err != nil { + WriteOCSError(w, r, MetaServerError.StatusCode, "error sending a grpc stat request", err) + return nil, err + } - // fetch user shares if configured - if h.gatewaySvc != "" { - userShareProviderClient, err := pool.GetGatewayServiceClient(h.gatewaySvc) - if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error getting grpc user share handler client", err) - return + if res.Status.Code != rpcpb.Code_CODE_OK { + if res.Status.Code == rpcpb.Code_CODE_NOT_FOUND { + WriteOCSError(w, r, MetaNotFound.StatusCode, "not found", nil) + return filters, errors.New("fixme") } - req := &usershareproviderv0alphapb.ListSharesRequest{ - Filters: filters, + WriteOCSError(w, r, MetaServerError.StatusCode, "grpc stat request failed", err) + return filters, errors.New("fixme") + } + + info = res.Info + + filters = append(filters, &usershareproviderv0alphapb.ListSharesRequest_Filter{ + Type: usershareproviderv0alphapb.ListSharesRequest_Filter_LIST_SHARES_REQUEST_FILTER_TYPE_RESOURCE_ID, + Term: &usershareproviderv0alphapb.ListSharesRequest_Filter_ResourceId{ + ResourceId: info.Id, + }, + }) + + return filters, nil +} + +func (h *SharesHandler) listUserShares(r *http.Request, filters []*usershareproviderv0alphapb.ListSharesRequest_Filter) ([]*conversions.ShareData, error) { + var rInfo *storageproviderv0alphapb.ResourceInfo + ctx := r.Context() + log := appctx.GetLogger(ctx) + + lsUserSharesRequest := usershareproviderv0alphapb.ListSharesRequest{ + Filters: filters, + } + + ocsDataPayload := make([]*conversions.ShareData, 0) + if h.gatewayAddr != "" { + // get a connection to the users share provider + c, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + return nil, err } - res, err := userShareProviderClient.ListShares(ctx, req) + + // do list shares request. unfiltered + lsUserSharesResponse, err := c.ListShares(ctx, &lsUserSharesRequest) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error sending a grpc list shares request", err) - return + return nil, err } - if res.Status.Code != rpcpb.Code_CODE_OK { - if res.Status.Code == rpcpb.Code_CODE_NOT_FOUND { - WriteOCSError(w, r, MetaNotFound.StatusCode, "not found", nil) - return - } - WriteOCSError(w, r, MetaServerError.StatusCode, "grpc list shares request failed", err) - return + + if lsUserSharesResponse.Status.Code != rpcpb.Code_CODE_OK { + return nil, err } - for _, s := range res.Shares { + + // build OCS response payload + for _, s := range lsUserSharesResponse.Shares { share, err := h.userShare2ShareData(ctx, s) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error mapping share data", err) - return + return nil, err } - if h.addFileInfo(ctx, share, info) != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error adding file info", err) - return + + // check if the resource exists + sClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + return nil, err } - log.Debug().Interface("share", s).Interface("info", info).Interface("shareData", share).Msg("mapped") - shares = append(shares, share) - } - // TODO(refs): refactor - pClient, err := pool.GetGatewayServiceClient(h.gatewaySvc) - if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error getting public share provider grpc client", err) - } else { - req := &publicshareproviderv0alphapb.ListPublicSharesRequest{} - res, err := pClient.ListPublicShares(ctx, req) + // prepare the stat request + statReq := &storageproviderv0alphapb.StatRequest{ + // prepare the reference + Ref: &storageproviderv0alphapb.Reference{ + // using ResourceId from the share + Spec: &storageproviderv0alphapb.Reference_Id{Id: s.ResourceId}, + }, + } + + statResponse, err := sClient.Stat(ctx, statReq) if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error sending a grpc list public shares request", err) - return + return nil, err } - if res.Status.Code != rpcpb.Code_CODE_OK { - if res.Status.Code == rpcpb.Code_CODE_NOT_FOUND { - WriteOCSError(w, r, MetaNotFound.StatusCode, "not found", nil) - return - } - WriteOCSError(w, r, MetaServerError.StatusCode, "grpc list shares request failed", err) - return + + if statResponse.Status.Code != rpcpb.Code_CODE_OK { + return nil, err } - for _, s := range res.Share { - share := h.publicShare2ShareData(s) - err := h.addFileInfo(ctx, share, info) - if err != nil { - WriteOCSError(w, r, MetaServerError.StatusCode, "error adding file info", err) - return - } - log.Debug().Interface("share", s).Interface("info", info).Interface("shareData", share).Msg("mapped") - shares = append(shares, share) + if h.addFileInfo(ctx, share, statResponse.Info) != nil { + return nil, err } + + log.Debug().Interface("share", s).Interface("info", rInfo).Interface("shareData", share).Msg("mapped") + ocsDataPayload = append(ocsDataPayload, share) } } - // TODO fetch group shares - // TODO fetch federated shares - - // fetch public link shares if configured - WriteOCSSuccess(w, r, &SharesData{ - Shares: shares, - }) + return ocsDataPayload, nil } -func (h *SharesHandler) addFileInfo(ctx context.Context, s *ShareData, info *storageproviderv0alphapb.ResourceInfo) error { +func (h *SharesHandler) addFileInfo(ctx context.Context, s *conversions.ShareData, info *storageproviderv0alphapb.ResourceInfo) error { if info != nil { // TODO The owner is not set in the storage stat metadata ... s.MimeType = info.MimeType @@ -679,60 +849,83 @@ func (h *SharesHandler) addFileInfo(ctx context.Context, s *ShareData, info *sto s.FileTarget = path.Join("/", path.Base(info.Path)) s.Path = info.Path // TODO hm this might have to be relative to the users home ... // TODO FileParent: + // item type + s.ItemType = conversions.ResourceType(info.GetType()).String() + + c, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + return err + } // file owner might not yet be set. Use file info if s.UIDFileOwner == "" { s.UIDFileOwner = UserIDToString(info.Owner) } if s.DisplaynameFileOwner == "" && info.Owner != nil { - owner, err := h.userManager.GetUser(ctx, info.Owner) + owner, err := c.GetUser(ctx, &userproviderv0alphapb.GetUserRequest{ + UserId: info.Owner, + }) if err != nil { return err } - s.DisplaynameFileOwner = owner.DisplayName + s.DisplaynameFileOwner = owner.GetUser().DisplayName } // share owner might not yet be set. Use file info if s.UIDOwner == "" { s.UIDOwner = UserIDToString(info.Owner) } if s.DisplaynameOwner == "" && info.Owner != nil { - owner, err := h.userManager.GetUser(ctx, info.Owner) + owner, err := c.GetUser(ctx, &userproviderv0alphapb.GetUserRequest{ + UserId: info.Owner, + }) if err != nil { return err } - s.DisplaynameOwner = owner.DisplayName + s.DisplaynameOwner = owner.GetUser().DisplayName } } return nil } // TODO(jfd) merge userShare2ShareData with publicShare2ShareData -func (h *SharesHandler) userShare2ShareData(ctx context.Context, share *usershareproviderv0alphapb.Share) (*ShareData, error) { - sd := &ShareData{ - Permissions: userSharePermissions2OCSPermissions(share.GetPermissions()), - ShareType: shareTypeUser, +func (h *SharesHandler) userShare2ShareData(ctx context.Context, share *usershareproviderv0alphapb.Share) (*conversions.ShareData, error) { + sd := &conversions.ShareData{ + Permissions: conversions.UserSharePermissions2OCSPermissions(share.GetPermissions()), + ShareType: conversions.ShareTypeUser, + } + + c, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + return nil, err } + if share.Creator != nil { - if creator, err := h.userManager.GetUser(ctx, share.Creator); err == nil { + if creator, err := c.GetUser(ctx, &userproviderv0alphapb.GetUserRequest{ + UserId: share.Creator, + }); err == nil { // TODO the user from GetUser might not have an ID set, so we are using the one we have sd.UIDOwner = UserIDToString(share.Creator) - sd.DisplaynameOwner = creator.DisplayName + sd.DisplaynameOwner = creator.GetUser().DisplayName } else { return nil, err } } if share.Owner != nil { - if owner, err := h.userManager.GetUser(ctx, share.Owner); err == nil { + if owner, err := c.GetUser(ctx, &userproviderv0alphapb.GetUserRequest{ + UserId: share.Owner, + }); err == nil { sd.UIDFileOwner = UserIDToString(share.Owner) - sd.DisplaynameFileOwner = owner.DisplayName + sd.DisplaynameFileOwner = owner.GetUser().DisplayName } else { return nil, err } } if share.Grantee.Id != nil { - if grantee, err := h.userManager.GetUser(ctx, share.Grantee.Id); err == nil { + if grantee, err := c.GetUser(ctx, &userproviderv0alphapb.GetUserRequest{ + UserId: share.Grantee.GetId(), + }); err == nil { sd.ShareWith = UserIDToString(share.Grantee.Id) - sd.ShareWithDisplayname = grantee.DisplayName + sd.ShareWithDisplayname = grantee.GetUser().DisplayName } else { return nil, err } @@ -748,174 +941,13 @@ func (h *SharesHandler) userShare2ShareData(ctx context.Context, share *usershar return sd, nil } -func userSharePermissions2OCSPermissions(sp *usershareproviderv0alphapb.SharePermissions) Permissions { - if sp != nil { - return permissions2OCSPermissions(sp.GetPermissions()) - } - return permissionInvalid -} - -func publicSharePermissions2OCSPermissions(sp *publicshareproviderv0alphapb.PublicSharePermissions) Permissions { - if sp != nil { - return permissions2OCSPermissions(sp.GetPermissions()) - } - return permissionInvalid -} - -func (h *SharesHandler) publicShare2ShareData(share *publicshareproviderv0alphapb.PublicShare) *ShareData { - sd := &ShareData{ - ID: share.Id.OpaqueId, - // TODO map share.resourceId to path and storage ... requires a stat call - // share.permissions ar mapped below - Permissions: publicSharePermissions2OCSPermissions(share.GetPermissions()), - ShareType: shareTypePublicLink, - UIDOwner: UserIDToString(share.Creator), - // TODO lookup user metadata - //DisplaynameOwner: creator.DisplayName, - STime: share.Ctime.Seconds, // TODO CS3 api birth time = btime - UIDFileOwner: UserIDToString(share.Owner), - // TODO lookup user metadata - //DisplaynameFileOwner: owner.DisplayName, - Token: share.Token, - Expiration: timestampToExpiration(share.Expiration), +// mustGetGateway returns a client to the gateway service, returns an error otherwise +func mustGetGateway(addr string, r *http.Request, w http.ResponseWriter) gatewayv0alpahpb.GatewayServiceClient { + client, err := pool.GetGatewayServiceClient(addr) + if err != nil { + WriteOCSError(w, r, MetaBadRequest.StatusCode, "no connection to gateway service", nil) + return nil } - // actually clients should be able to GET and cache the user info themselves ... - // TODO check grantee type for user vs group - return sd -} - -// timestamp is assumed to be UTC ... just human readable ... -// FIXME and ambiguous / error prone because there is no time zone ... -func timestampToExpiration(t *typespb.Timestamp) string { - return time.Unix(int64(t.Seconds), int64(t.Nanos)).Format("2006-01-02 15:05:05") -} - -// SharesData holds a list of share data -type SharesData struct { - Shares []*ShareData `json:"element" xml:"element"` -} - -// ShareType indicates the type of share -// TODO Phoenix should be able to handle int shareType in json -type ShareType int - -const ( - shareTypeUser ShareType = 0 - // shareTypeGroup ShareType = 1 - shareTypePublicLink ShareType = 3 - // shareTypeFederatedCloudShare ShareType = 6 -) - -// Permissions reflects the CRUD permissions used in the OCS sharing API -type Permissions uint - -const ( - permissionInvalid Permissions = 0 - permissionRead Permissions = 1 - permissionWrite Permissions = 2 - permissionCreate Permissions = 4 - permissionDelete Permissions = 8 - permissionShare Permissions = 16 - //permissionAll Permissions = 31 -) - -const ( - roleLegacy string = "legacy" - roleViewer string = "viewer" - roleEditor string = "editor" - roleCoowner string = "coowner" -) - -// ShareData holds share data, see https://doc.owncloud.com/server/developer_manual/core/ocs-share-api.html#response-attributes-1 -type ShareData struct { - // TODO int? - ID string `json:"id" xml:"id"` - // The share’s type. This can be one of: - // 0 = user - // 1 = group - // 3 = public link - // 6 = federated cloud share - ShareType ShareType `json:"share_type" xml:"share_type"` - // The username of the owner of the share. - UIDOwner string `json:"uid_owner" xml:"uid_owner"` - // The display name of the owner of the share. - DisplaynameOwner string `json:"displayname_owner" xml:"displayname_owner"` - // The permission attribute set on the file. Options are: - // * 1 = Read - // * 2 = Update - // * 4 = Create - // * 8 = Delete - // * 16 = Share - // * 31 = All permissions - // The default is 31, and for public shares is 1. - // TODO we should change the default to read only - Permissions Permissions `json:"permissions" xml:"permissions"` - // The UNIX timestamp when the share was created. - STime uint64 `json:"stime" xml:"stime"` - // ? - Parent string `json:"parent" xml:"parent"` - // The UNIX timestamp when the share expires. - Expiration string `json:"expiration" xml:"expiration"` - // The public link to the item being shared. - Token string `json:"token" xml:"token"` - // The unique id of the user that owns the file or folder being shared. - UIDFileOwner string `json:"uid_file_owner" xml:"uid_file_owner"` - // The display name of the user that owns the file or folder being shared. - DisplaynameFileOwner string `json:"displayname_file_owner" xml:"displayname_file_owner"` - // The path to the shared file or folder. - Path string `json:"path" xml:"path"` - // The type of the object being shared. This can be one of file or folder. - ItemType string `json:"item_type" xml:"item_type"` - // The RFC2045-compliant mimetype of the file. - MimeType string `json:"mimetype" xml:"mimetype"` - StorageID string `json:"storage_id" xml:"storage_id"` - Storage uint64 `json:"storage" xml:"storage"` - // The unique node id of the item being shared. - // TODO int? - ItemSource string `json:"item_source" xml:"item_source"` - // The unique node id of the item being shared. For legacy reasons item_source and file_source attributes have the same value. - // TODO int? - FileSource string `json:"file_source" xml:"file_source"` - // The unique node id of the parent node of the item being shared. - // TODO int? - FileParent string `json:"file_parent" xml:"file_parent"` - // The name of the shared file. - FileTarget string `json:"file_target" xml:"file_target"` - // The uid of the receiver of the file. This is either - // - a GID (group id) if it is being shared with a group or - // - a UID (user id) if the share is shared with a user. - ShareWith string `json:"share_with" xml:"share_with"` - // The display name of the receiver of the file. - ShareWithDisplayname string `json:"share_with_displayname" xml:"share_with_displayname"` - // Whether the recipient was notified, by mail, about the share being shared with them. - MailSend string `json:"mail_send" xml:"mail_send"` - // A (human-readable) name for the share, which can be up to 64 characters in length - Name string `json:"name" xml:"name"` -} - -// ShareeData holds share recipaent search results -type ShareeData struct { - Exact *ExactMatchesData `json:"exact" xml:"exact"` - Users []*MatchData `json:"users" xml:"users"` - Groups []*MatchData `json:"groups" xml:"groups"` - Remotes []*MatchData `json:"remotes" xml:"remotes"` -} - -// ExactMatchesData hold exact matches -type ExactMatchesData struct { - Users []*MatchData `json:"users" xml:"users"` - Groups []*MatchData `json:"groups" xml:"groups"` - Remotes []*MatchData `json:"remotes" xml:"remotes"` -} - -// MatchData describes a single match -type MatchData struct { - Label string `json:"label" xml:"label"` - Value *MatchValueData `json:"value" xml:"value"` -} -// MatchValueData holds the type and actual value -type MatchValueData struct { - ShareType int `json:"shareType" xml:"shareType"` - ShareWith string `json:"shareWith" xml:"shareWith"` + return client } diff --git a/pkg/storage/registry/static/static.go b/pkg/storage/registry/static/static.go index 8d77950bbb..c6cdbbf4a8 100644 --- a/pkg/storage/registry/static/static.go +++ b/pkg/storage/registry/static/static.go @@ -53,9 +53,9 @@ func (b *reg) ListProviders(ctx context.Context) ([]*storagetypespb.ProviderInfo // returns the the root path of the first provider in the list. // TODO(labkode): this is not production ready. func (b *reg) GetHome(ctx context.Context) (string, error) { - for _, v := range b.rules { - if strings.HasPrefix(v, "/") { - return v, nil + for k := range b.rules { + if strings.HasPrefix(k, "/") { + return k, nil } } return "", errors.New("static: home not found")