github.com/cs3org/reva/v2@v2.27.7/pkg/conversions/main.go (about) 1 // Copyright 2018-2021 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 // Package conversions sits between CS3 type definitions and OCS API Responses 20 package conversions 21 22 import ( 23 "context" 24 "encoding/base64" 25 "fmt" 26 "net/http" 27 "path" 28 "path/filepath" 29 "time" 30 31 "github.com/cs3org/reva/v2/pkg/errtypes" 32 "github.com/cs3org/reva/v2/pkg/mime" 33 "github.com/cs3org/reva/v2/pkg/publicshare" 34 "github.com/cs3org/reva/v2/pkg/storagespace" 35 "github.com/cs3org/reva/v2/pkg/user" 36 "github.com/cs3org/reva/v2/pkg/utils" 37 38 grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" 39 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 40 collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" 41 link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" 42 ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" 43 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 44 types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 45 publicsharemgr "github.com/cs3org/reva/v2/pkg/publicshare/manager/registry" 46 usermgr "github.com/cs3org/reva/v2/pkg/user/manager/registry" 47 ) 48 49 const ( 50 // ShareTypeUser refers to user shares 51 ShareTypeUser ShareType = 0 52 53 // ShareTypePublicLink refers to public link shares 54 ShareTypePublicLink ShareType = 3 55 56 // ShareTypeGroup represents a group share 57 ShareTypeGroup ShareType = 1 58 59 // ShareTypeFederatedCloudShare represents a federated share 60 ShareTypeFederatedCloudShare ShareType = 6 61 62 // ShareTypeSpaceMembershipUser represents an action regarding user type space members 63 ShareTypeSpaceMembershipUser ShareType = 7 64 65 // ShareTypeSpaceMembershipGroup represents an action regarding group type space members 66 ShareTypeSpaceMembershipGroup ShareType = 8 67 68 // ShareWithUserTypeUser represents a normal user 69 ShareWithUserTypeUser ShareWithUserType = 0 70 71 // ShareWithUserTypeGuest represents a guest user 72 ShareWithUserTypeGuest ShareWithUserType = 1 73 74 // The datetime format of ISO8601 75 _iso8601 = "2006-01-02T15:04:05Z0700" 76 ) 77 78 // ResourceType indicates the OCS type of the resource 79 type ResourceType int 80 81 func (rt ResourceType) String() (s string) { 82 switch rt { 83 case 0: 84 s = "invalid" 85 case 1: 86 s = "file" 87 case 2: 88 s = "folder" 89 case 3: 90 s = "reference" 91 default: 92 s = "invalid" 93 } 94 return 95 } 96 97 // ShareType denotes a type of share 98 type ShareType int 99 100 // ShareWithUserType denotes a type of user 101 type ShareWithUserType int 102 103 // ShareData represents https://doc.owncloud.com/server/developer_manual/core/ocs-share-api.html#response-attributes-1 104 type ShareData struct { 105 // TODO int? 106 ID string `json:"id" xml:"id"` 107 // The share’s type 108 ShareType ShareType `json:"share_type" xml:"share_type"` 109 // The username of the owner of the share. 110 UIDOwner string `json:"uid_owner" xml:"uid_owner"` 111 // The display name of the owner of the share. 112 DisplaynameOwner string `json:"displayname_owner" xml:"displayname_owner"` 113 // Additional info to identify the share owner, eg. the email or username 114 AdditionalInfoOwner string `json:"additional_info_owner" xml:"additional_info_owner"` 115 // The permission attribute set on the file. 116 // TODO(jfd) change the default to read only 117 Permissions Permissions `json:"permissions" xml:"permissions"` 118 // The UNIX timestamp when the share was created. 119 STime uint64 `json:"stime" xml:"stime"` 120 // ? 121 Parent string `json:"parent" xml:"parent"` 122 // The UNIX timestamp when the share expires. 123 Expiration string `json:"expiration" xml:"expiration"` 124 // The public link to the item being shared. 125 Token string `json:"token" xml:"token"` 126 // The unique id of the user that owns the file or folder being shared. 127 UIDFileOwner string `json:"uid_file_owner" xml:"uid_file_owner"` 128 // The display name of the user that owns the file or folder being shared. 129 DisplaynameFileOwner string `json:"displayname_file_owner" xml:"displayname_file_owner"` 130 // Additional info to identify the file owner, eg. the email or username 131 AdditionalInfoFileOwner string `json:"additional_info_file_owner" xml:"additional_info_file_owner"` 132 // share state, 0 = accepted, 1 = pending, 2 = declined 133 State int `json:"state" xml:"state"` 134 // The path to the shared file or folder. 135 Path string `json:"path" xml:"path"` 136 // The type of the object being shared. This can be one of 'file' or 'folder'. 137 ItemType string `json:"item_type" xml:"item_type"` 138 // The RFC2045-compliant mimetype of the file. 139 MimeType string `json:"mimetype" xml:"mimetype"` 140 // The space ID of the original file location 141 SpaceID string `json:"space_id" xml:"space_id"` 142 // The space alias of the original file location 143 SpaceAlias string `json:"space_alias" xml:"space_alias"` 144 StorageID string `json:"storage_id" xml:"storage_id"` 145 Storage uint64 `json:"storage" xml:"storage"` 146 // The unique node id of the item being shared. 147 ItemSource string `json:"item_source" xml:"item_source"` 148 // The unique node id of the item being shared. For legacy reasons item_source and file_source attributes have the same value. 149 FileSource string `json:"file_source" xml:"file_source"` 150 // The unique node id of the parent node of the item being shared. 151 FileParent string `json:"file_parent" xml:"file_parent"` 152 // The basename of the shared file. 153 FileTarget string `json:"file_target" xml:"file_target"` 154 // The uid of the share recipient. This is either 155 // - a GID (group id) if it is being shared with a group or 156 // - a UID (user id) if the share is shared with a user. 157 // - a password for public links 158 ShareWith string `json:"share_with,omitempty" xml:"share_with,omitempty"` 159 // The type of user 160 // - 0 = normal user 161 // - 1 = guest account 162 ShareWithUserType ShareWithUserType `json:"share_with_user_type" xml:"share_with_user_type"` 163 // The display name of the share recipient 164 ShareWithDisplayname string `json:"share_with_displayname,omitempty" xml:"share_with_displayname,omitempty"` 165 // Additional info to identify the share recipient, eg. the email or username 166 ShareWithAdditionalInfo string `json:"share_with_additional_info" xml:"share_with_additional_info"` 167 // Whether the recipient was notified, by mail, about the share being shared with them. 168 MailSend int `json:"mail_send" xml:"mail_send"` 169 // Name of the public share 170 Name string `json:"name" xml:"name"` 171 // URL of the public share 172 URL string `json:"url,omitempty" xml:"url,omitempty"` 173 // Attributes associated 174 Attributes string `json:"attributes,omitempty" xml:"attributes,omitempty"` 175 // Quicklink indicates if the link is the quicklink 176 Quicklink bool `json:"quicklink,omitempty" xml:"quicklink,omitempty"` 177 // PasswordProtected represents a public share is password protected 178 // PasswordProtected bool `json:"password_protected,omitempty" xml:"password_protected,omitempty"` 179 Hidden bool `json:"hidden" xml:"hidden"` 180 } 181 182 // ShareeData holds share recipient search results 183 type ShareeData struct { 184 Exact *ExactMatchesData `json:"exact" xml:"exact"` 185 Users []*MatchData `json:"users" xml:"users>element"` 186 Groups []*MatchData `json:"groups" xml:"groups>element"` 187 Remotes []*MatchData `json:"remotes" xml:"remotes>element"` 188 } 189 190 // TokenInfo holds token information 191 type TokenInfo struct { 192 // for all callers 193 Token string `json:"token" xml:"token"` 194 LinkURL string `json:"link_url" xml:"link_url"` 195 PasswordProtected bool `json:"password_protected" xml:"password_protected"` 196 Aliaslink bool `json:"alias_link" xml:"alias_link"` 197 198 // if not password protected 199 ID string `json:"id" xml:"id"` 200 StorageID string `json:"storage_id" xml:"storage_id"` 201 SpaceID string `json:"space_id" xml:"space_id"` 202 OpaqueID string `json:"opaque_id" xml:"opaque_id"` 203 Path string `json:"path" xml:"path"` 204 205 // if native access 206 SpacePath string `json:"space_path" xml:"space_path"` 207 SpaceAlias string `json:"space_alias" xml:"space_alias"` 208 SpaceURL string `json:"space_url" xml:"space_url"` 209 SpaceType string `json:"space_type" xml:"space_type"` 210 } 211 212 // ExactMatchesData hold exact matches 213 type ExactMatchesData struct { 214 Users []*MatchData `json:"users" xml:"users>element"` 215 Groups []*MatchData `json:"groups" xml:"groups>element"` 216 Remotes []*MatchData `json:"remotes" xml:"remotes>element"` 217 } 218 219 // MatchData describes a single match 220 type MatchData struct { 221 Label string `json:"label" xml:"label,omitempty"` 222 Value *MatchValueData `json:"value" xml:"value"` 223 } 224 225 // MatchValueData holds the type and actual value 226 type MatchValueData struct { 227 ShareType int `json:"shareType" xml:"shareType"` 228 ShareWith string `json:"shareWith" xml:"shareWith"` 229 ShareWithProvider string `json:"shareWithProvider" xml:"shareWithProvider"` 230 ShareWithAdditionalInfo string `json:"shareWithAdditionalInfo" xml:"shareWithAdditionalInfo,omitempty"` 231 UserType int `json:"userType" xml:"userType"` 232 } 233 234 // CS3Share2ShareData converts a cs3api user share into shareData data model 235 func CS3Share2ShareData(ctx context.Context, share *collaboration.Share) *ShareData { 236 sd := &ShareData{ 237 // share.permissions are mapped below 238 // Displaynames are added later 239 UIDOwner: LocalUserIDToString(share.GetCreator()), 240 UIDFileOwner: LocalUserIDToString(share.GetOwner()), 241 } 242 243 if share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { 244 sd.ShareType = ShareTypeUser 245 sd.ShareWith = LocalUserIDToString(share.Grantee.GetUserId()) 246 shareType := share.GetGrantee().GetUserId().GetType() 247 if shareType == userpb.UserType_USER_TYPE_LIGHTWEIGHT || shareType == userpb.UserType_USER_TYPE_GUEST { 248 sd.ShareWithUserType = ShareWithUserTypeGuest 249 } else { 250 sd.ShareWithUserType = ShareWithUserTypeUser 251 } 252 } else if share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { 253 sd.ShareType = ShareTypeGroup 254 sd.ShareWith = LocalGroupIDToString(share.Grantee.GetGroupId()) 255 } 256 257 if share.Id != nil { 258 sd.ID = share.Id.OpaqueId 259 } 260 if share.GetPermissions().GetPermissions() != nil { 261 sd.Permissions = RoleFromResourcePermissions(share.GetPermissions().GetPermissions(), false).OCSPermissions() 262 } 263 if share.Ctime != nil { 264 sd.STime = share.Ctime.Seconds // TODO CS3 api birth time = btime 265 } 266 267 if share.Expiration != nil { 268 expiration := time.Unix(int64(share.Expiration.Seconds), int64(share.Expiration.Nanos)) 269 sd.Expiration = expiration.Format(_iso8601) 270 } 271 272 return sd 273 } 274 275 // PublicShare2ShareData converts a cs3api public share into shareData data model 276 func PublicShare2ShareData(share *link.PublicShare, r *http.Request, publicURL string) *ShareData { 277 sd := &ShareData{ 278 // share.permissions are mapped below 279 // Displaynames are added later 280 ShareType: ShareTypePublicLink, 281 Token: share.Token, 282 Name: share.DisplayName, 283 MailSend: 0, 284 URL: publicURL + path.Join("/", "s/"+share.Token), 285 UIDOwner: LocalUserIDToString(share.Creator), 286 UIDFileOwner: LocalUserIDToString(share.Owner), 287 Quicklink: share.Quicklink, 288 } 289 if share.Id != nil { 290 sd.ID = share.Id.OpaqueId 291 } 292 293 if s := share.GetPermissions().GetPermissions(); s != nil { 294 sd.Permissions = RoleFromResourcePermissions(share.GetPermissions().GetPermissions(), true).OCSPermissions() 295 } 296 297 if share.Expiration != nil { 298 sd.Expiration = timestampToExpiration(share.Expiration) 299 } 300 if share.Ctime != nil { 301 sd.STime = share.Ctime.Seconds // TODO CS3 api birth time = btime 302 } 303 304 // hide password 305 if share.PasswordProtected { 306 sd.ShareWith = "***redacted***" 307 sd.ShareWithDisplayname = "***redacted***" 308 } 309 310 return sd 311 } 312 313 func formatRemoteUser(u *userpb.UserId) string { 314 return fmt.Sprintf("%s@%s", u.OpaqueId, u.Idp) 315 } 316 317 func webdavInfo(protocols []*ocm.Protocol) (*ocm.WebDAVProtocol, bool) { 318 for _, p := range protocols { 319 if opt, ok := p.Term.(*ocm.Protocol_WebdavOptions); ok { 320 return opt.WebdavOptions, true 321 } 322 } 323 return nil, false 324 } 325 326 // ReceivedOCMShare2ShareData converts a cs3 ocm received share into a share data model. 327 func ReceivedOCMShare2ShareData(share *ocm.ReceivedShare, path string) (*ShareData, error) { 328 webdav, ok := webdavInfo(share.Protocols) 329 if !ok { 330 return nil, errtypes.InternalError("webdav endpoint not in share") 331 } 332 333 opaqueid := base64.StdEncoding.EncodeToString([]byte("/")) 334 335 shareTarget := filepath.Join("/Shares", share.Name) 336 337 s := &ShareData{ 338 ID: share.Id.OpaqueId, 339 UIDOwner: formatRemoteUser(share.Creator), 340 UIDFileOwner: formatRemoteUser(share.Owner), 341 ShareWith: share.Grantee.GetUserId().OpaqueId, 342 Permissions: RoleFromResourcePermissions(webdav.GetPermissions().GetPermissions(), false).OCSPermissions(), 343 ShareType: ShareTypeFederatedCloudShare, 344 Path: shareTarget, 345 FileTarget: shareTarget, 346 MimeType: mime.Detect(share.ResourceType == provider.ResourceType_RESOURCE_TYPE_CONTAINER, share.Name), 347 ItemType: ResourceType(share.ResourceType).String(), 348 ItemSource: storagespace.FormatResourceID(&provider.ResourceId{ 349 StorageId: utils.OCMStorageProviderID, 350 SpaceId: share.Id.OpaqueId, 351 OpaqueId: opaqueid, 352 }), 353 STime: share.Ctime.Seconds, 354 Name: share.Name, 355 SpaceID: storagespace.FormatStorageID(utils.OCMStorageProviderID, share.Id.OpaqueId), 356 } 357 358 if share.Expiration != nil { 359 s.Expiration = timestampToExpiration(share.Expiration) 360 } 361 return s, nil 362 } 363 364 func webdavAMInfo(methods []*ocm.AccessMethod) (*ocm.WebDAVAccessMethod, bool) { 365 for _, a := range methods { 366 if opt, ok := a.Term.(*ocm.AccessMethod_WebdavOptions); ok { 367 return opt.WebdavOptions, true 368 } 369 } 370 return nil, false 371 } 372 373 // OCMShare2ShareData converts a cs3 ocm share into a share data model. 374 func OCMShare2ShareData(share *ocm.Share) (*ShareData, error) { 375 webdav, ok := webdavAMInfo(share.AccessMethods) 376 if !ok { 377 return nil, errtypes.InternalError("webdav endpoint not in share") 378 } 379 380 s := &ShareData{ 381 ID: share.Id.OpaqueId, 382 UIDOwner: share.Creator.OpaqueId, 383 UIDFileOwner: share.Owner.OpaqueId, 384 ShareWith: formatRemoteUser(share.Grantee.GetUserId()), 385 Permissions: RoleFromResourcePermissions(webdav.GetPermissions(), false).OCSPermissions(), 386 ShareType: ShareTypeFederatedCloudShare, 387 STime: share.Ctime.Seconds, 388 Name: share.Name, 389 } 390 391 if share.Expiration != nil { 392 s.Expiration = timestampToExpiration(share.Expiration) 393 } 394 395 return s, nil 396 } 397 398 // LocalUserIDToString transforms a cs3api user id into an ocs data model without domain name 399 // TODO ocs uses user names ... so an additional lookup is needed. see mapUserIds() 400 func LocalUserIDToString(userID *userpb.UserId) string { 401 if userID == nil || userID.OpaqueId == "" { 402 return "" 403 } 404 return userID.OpaqueId 405 } 406 407 // LocalGroupIDToString transforms a cs3api group id into an ocs data model without domain name 408 func LocalGroupIDToString(groupID *grouppb.GroupId) string { 409 if groupID == nil || groupID.OpaqueId == "" { 410 return "" 411 } 412 return groupID.OpaqueId 413 } 414 415 // GetUserManager returns a connection to a user share manager 416 func GetUserManager(manager string, m map[string]map[string]interface{}) (user.Manager, error) { 417 if f, ok := usermgr.NewFuncs[manager]; ok { 418 return f(m[manager]) 419 } 420 421 return nil, fmt.Errorf("driver %s not found for user manager", manager) 422 } 423 424 // GetPublicShareManager returns a connection to a public share manager 425 func GetPublicShareManager(manager string, m map[string]map[string]interface{}) (publicshare.Manager, error) { 426 if f, ok := publicsharemgr.NewFuncs[manager]; ok { 427 return f(m[manager]) 428 } 429 430 return nil, fmt.Errorf("driver %s not found for public shares manager", manager) 431 } 432 433 // timestamp is assumed to be UTC ... just human readable ... 434 // FIXME and ambiguous / error prone because there is no time zone ... 435 func timestampToExpiration(t *types.Timestamp) string { 436 return time.Unix(int64(t.Seconds), int64(t.Nanos)).UTC().Format("2006-01-02 15:05:05") 437 } 438 439 // ParseTimestamp tries to parse the ocs expiry into a CS3 Timestamp 440 func ParseTimestamp(timestampString string) (*types.Timestamp, error) { 441 parsedTime, err := time.Parse("2006-01-02T15:04:05Z0700", timestampString) 442 if err != nil { 443 parsedTime, err = time.Parse("2006-01-02", timestampString) 444 if err == nil { 445 // the link needs to be valid for the whole day 446 parsedTime = parsedTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second) 447 } 448 } 449 if err != nil { 450 return nil, fmt.Errorf("datetime format invalid: %v, %s", timestampString, err.Error()) 451 } 452 final := parsedTime.UnixNano() 453 454 return &types.Timestamp{ 455 Seconds: uint64(final / 1000000000), 456 Nanos: uint32(final % 1000000000), 457 }, nil 458 } 459 460 // UserTypeString returns human readable strings for various user types 461 func UserTypeString(userType userpb.UserType) string { 462 switch userType { 463 case userpb.UserType_USER_TYPE_PRIMARY: 464 return "primary" 465 case userpb.UserType_USER_TYPE_SECONDARY: 466 return "secondary" 467 case userpb.UserType_USER_TYPE_SERVICE: 468 return "service" 469 case userpb.UserType_USER_TYPE_APPLICATION: 470 return "application" 471 case userpb.UserType_USER_TYPE_GUEST: 472 return "guest" 473 case userpb.UserType_USER_TYPE_FEDERATED: 474 return "federated" 475 case userpb.UserType_USER_TYPE_LIGHTWEIGHT: 476 return "lightweight" 477 } 478 return "invalid" 479 }