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  }