github.com/cs3org/reva/v2@v2.27.7/pkg/storage/fs/nextcloud/nextcloud.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 nextcloud
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"io"
    26  	"net/http"
    27  	"net/url"
    28  	"path"
    29  	"strings"
    30  
    31  	user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    32  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    33  	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    34  	"github.com/mitchellh/mapstructure"
    35  	"github.com/pkg/errors"
    36  	"github.com/rs/zerolog"
    37  
    38  	"github.com/cs3org/reva/v2/pkg/appctx"
    39  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    40  	"github.com/cs3org/reva/v2/pkg/errtypes"
    41  	"github.com/cs3org/reva/v2/pkg/events"
    42  	"github.com/cs3org/reva/v2/pkg/storage"
    43  	"github.com/cs3org/reva/v2/pkg/storage/fs/registry"
    44  )
    45  
    46  func init() {
    47  	registry.Register("nextcloud", New)
    48  }
    49  
    50  // StorageDriverConfig is the configuration struct for a NextcloudStorageDriver
    51  type StorageDriverConfig struct {
    52  	EndPoint     string `mapstructure:"endpoint"` // e.g. "http://nc/apps/sciencemesh/~alice/"
    53  	SharedSecret string `mapstructure:"shared_secret"`
    54  	MockHTTP     bool   `mapstructure:"mock_http"`
    55  }
    56  
    57  // StorageDriver implements the storage.FS interface
    58  // and connects with a StorageDriver server as its backend
    59  type StorageDriver struct {
    60  	endPoint     string
    61  	sharedSecret string
    62  	client       *http.Client
    63  }
    64  
    65  func parseConfig(m map[string]interface{}) (*StorageDriverConfig, error) {
    66  	c := &StorageDriverConfig{}
    67  	if err := mapstructure.Decode(m, c); err != nil {
    68  		err = errors.Wrap(err, "error decoding conf")
    69  		return nil, err
    70  	}
    71  	return c, nil
    72  }
    73  
    74  // New returns an implementation to of the storage.FS interface that talks to
    75  // a Nextcloud instance over http.
    76  func New(m map[string]interface{}, _ events.Stream, _ *zerolog.Logger) (storage.FS, error) {
    77  	conf, err := parseConfig(m)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	return NewStorageDriver(conf)
    83  }
    84  
    85  // NewStorageDriver returns a new NextcloudStorageDriver
    86  func NewStorageDriver(c *StorageDriverConfig) (*StorageDriver, error) {
    87  	var client *http.Client
    88  	if c.MockHTTP {
    89  		// called := make([]string, 0)
    90  		// nextcloudServerMock := GetNextcloudServerMock(&called)
    91  		// client, _ = TestingHTTPClient(nextcloudServerMock)
    92  
    93  		// This is only used by the integration tests:
    94  		// (unit tests will call SetHTTPClient later):
    95  		called := make([]string, 0)
    96  		h := GetNextcloudServerMock(&called)
    97  		client, _ = TestingHTTPClient(h)
    98  		// FIXME: defer teardown()
    99  	} else {
   100  		if len(c.EndPoint) == 0 {
   101  			return nil, errors.New("Please specify 'endpoint' in '[grpc.services.storageprovider.drivers.nextcloud]'")
   102  		}
   103  		client = &http.Client{}
   104  	}
   105  	return &StorageDriver{
   106  		endPoint:     c.EndPoint, // e.g. "http://nc/apps/sciencemesh/"
   107  		sharedSecret: c.SharedSecret,
   108  		client:       client,
   109  	}, nil
   110  }
   111  
   112  // Action describes a REST request to forward to the Nextcloud backend
   113  type Action struct {
   114  	verb string
   115  	argS string
   116  }
   117  
   118  func getUser(ctx context.Context) (*user.User, error) {
   119  	u, ok := ctxpkg.ContextGetUser(ctx)
   120  	if !ok {
   121  		err := errors.Wrap(errtypes.UserRequired(""), "nextcloud storage driver: error getting user from ctx")
   122  		return nil, err
   123  	}
   124  	return u, nil
   125  }
   126  
   127  // SetHTTPClient sets the HTTP client
   128  func (nc *StorageDriver) SetHTTPClient(c *http.Client) {
   129  	nc.client = c
   130  }
   131  
   132  func (nc *StorageDriver) doUpload(ctx context.Context, filePath string, r io.ReadCloser) error {
   133  	// log := appctx.GetLogger(ctx)
   134  	user, err := getUser(ctx)
   135  	if err != nil {
   136  		return err
   137  	}
   138  	// See https://github.com/pondersource/nc-sciencemesh/issues/5
   139  	// url := nc.endPoint + "~" + user.Username + "/files/" + filePath
   140  	url := nc.endPoint + "~" + user.Username + "/api/storage/Upload/" + filePath
   141  	req, err := http.NewRequest(http.MethodPut, url, r)
   142  	if err != nil {
   143  		panic(err)
   144  	}
   145  
   146  	req.Header.Set("X-Reva-Secret", nc.sharedSecret)
   147  	// set the request header Content-Type for the upload
   148  	// FIXME: get the actual content type from somewhere
   149  	req.Header.Set("Content-Type", "text/plain")
   150  	resp, err := nc.client.Do(req)
   151  	if err != nil {
   152  		panic(err)
   153  	}
   154  
   155  	defer resp.Body.Close()
   156  	_, err = io.ReadAll(resp.Body)
   157  	return err
   158  }
   159  
   160  func (nc *StorageDriver) doDownload(ctx context.Context, filePath string) (io.ReadCloser, error) {
   161  	user, err := getUser(ctx)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	// See https://github.com/pondersource/nc-sciencemesh/issues/5
   166  	// url := nc.endPoint + "~" + user.Username + "/files/" + filePath
   167  	url := nc.endPoint + "~" + user.Username + "/api/storage/Download/" + filePath
   168  	req, err := http.NewRequest(http.MethodGet, url, strings.NewReader(""))
   169  	if err != nil {
   170  		panic(err)
   171  	}
   172  
   173  	resp, err := nc.client.Do(req)
   174  	if err != nil {
   175  		panic(err)
   176  	}
   177  	if resp.StatusCode != 200 {
   178  		panic("No 200 response code in download request")
   179  	}
   180  
   181  	return resp.Body, err
   182  }
   183  
   184  func (nc *StorageDriver) doDownloadRevision(ctx context.Context, filePath string, key string) (io.ReadCloser, error) {
   185  	user, err := getUser(ctx)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	// See https://github.com/pondersource/nc-sciencemesh/issues/5
   190  	url := nc.endPoint + "~" + user.Username + "/api/storage/DownloadRevision/" + url.QueryEscape(key) + "/" + filePath
   191  	req, err := http.NewRequest(http.MethodGet, url, strings.NewReader(""))
   192  	if err != nil {
   193  		panic(err)
   194  	}
   195  	req.Header.Set("X-Reva-Secret", nc.sharedSecret)
   196  
   197  	resp, err := nc.client.Do(req)
   198  	if err != nil {
   199  		panic(err)
   200  	}
   201  	if resp.StatusCode != 200 {
   202  		panic("No 200 response code in download request")
   203  	}
   204  
   205  	return resp.Body, err
   206  }
   207  
   208  func (nc *StorageDriver) do(ctx context.Context, a Action) (int, []byte, error) {
   209  	log := appctx.GetLogger(ctx)
   210  	user, err := getUser(ctx)
   211  	if err != nil {
   212  		return 0, nil, err
   213  	}
   214  	// See https://github.com/cs3org/reva/issues/2377
   215  	// for discussion of user.Username vs user.Id.OpaqueId
   216  	url := nc.endPoint + "~" + user.Id.OpaqueId + "/api/storage/" + a.verb
   217  	log.Info().Msgf("nc.do req %s %s", url, a.argS)
   218  	req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(a.argS))
   219  	if err != nil {
   220  		return 0, nil, err
   221  	}
   222  	req.Header.Set("X-Reva-Secret", nc.sharedSecret)
   223  
   224  	req.Header.Set("Content-Type", "application/json")
   225  	resp, err := nc.client.Do(req)
   226  	if err != nil {
   227  		return 0, nil, err
   228  	}
   229  
   230  	defer resp.Body.Close()
   231  	body, err := io.ReadAll(resp.Body)
   232  
   233  	if err != nil {
   234  		return 0, nil, err
   235  	}
   236  	log.Info().Msgf("nc.do res %s %s", url, string(body))
   237  
   238  	return resp.StatusCode, body, nil
   239  }
   240  
   241  // GetHome as defined in the storage.FS interface
   242  func (nc *StorageDriver) GetHome(ctx context.Context) (string, error) {
   243  	log := appctx.GetLogger(ctx)
   244  	log.Info().Msg("GetHome")
   245  
   246  	_, respBody, err := nc.do(ctx, Action{"GetHome", ""})
   247  	return string(respBody), err
   248  }
   249  
   250  // CreateHome as defined in the storage.FS interface
   251  func (nc *StorageDriver) CreateHome(ctx context.Context) error {
   252  	log := appctx.GetLogger(ctx)
   253  	log.Info().Msg("CreateHome")
   254  
   255  	_, _, err := nc.do(ctx, Action{"CreateHome", ""})
   256  	return err
   257  }
   258  
   259  // CreateDir as defined in the storage.FS interface
   260  func (nc *StorageDriver) CreateDir(ctx context.Context, ref *provider.Reference) error {
   261  	bodyStr, err := json.Marshal(ref)
   262  	if err != nil {
   263  		return err
   264  	}
   265  	log := appctx.GetLogger(ctx)
   266  	log.Info().Msgf("CreateDir %s", bodyStr)
   267  
   268  	_, _, err = nc.do(ctx, Action{"CreateDir", string(bodyStr)})
   269  	return err
   270  }
   271  
   272  // TouchFile as defined in the storage.FS interface
   273  func (nc *StorageDriver) TouchFile(ctx context.Context, ref *provider.Reference, markprocessing bool, mtime string) error {
   274  	return fmt.Errorf("unimplemented: TouchFile")
   275  }
   276  
   277  // Delete as defined in the storage.FS interface
   278  func (nc *StorageDriver) Delete(ctx context.Context, ref *provider.Reference) error {
   279  	bodyStr, err := json.Marshal(ref)
   280  	if err != nil {
   281  		return err
   282  	}
   283  	log := appctx.GetLogger(ctx)
   284  	log.Info().Msgf("Delete %s", bodyStr)
   285  
   286  	_, _, err = nc.do(ctx, Action{"Delete", string(bodyStr)})
   287  	return err
   288  }
   289  
   290  // Move as defined in the storage.FS interface
   291  func (nc *StorageDriver) Move(ctx context.Context, oldRef, newRef *provider.Reference) error {
   292  	type paramsObj struct {
   293  		OldRef *provider.Reference `json:"oldRef"`
   294  		NewRef *provider.Reference `json:"newRef"`
   295  	}
   296  	bodyObj := &paramsObj{
   297  		OldRef: oldRef,
   298  		NewRef: newRef,
   299  	}
   300  	bodyStr, _ := json.Marshal(bodyObj)
   301  	log := appctx.GetLogger(ctx)
   302  	log.Info().Msgf("Move %s", bodyStr)
   303  
   304  	_, _, err := nc.do(ctx, Action{"Move", string(bodyStr)})
   305  	return err
   306  }
   307  
   308  // GetMD as defined in the storage.FS interface
   309  // TODO forward fieldMask
   310  func (nc *StorageDriver) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string, fieldMask []string) (*provider.ResourceInfo, error) {
   311  	type paramsObj struct {
   312  		Ref    *provider.Reference `json:"ref"`
   313  		MdKeys []string            `json:"mdKeys"`
   314  		// MetaData provider.ResourceInfo `json:"metaData"`
   315  	}
   316  	bodyObj := &paramsObj{
   317  		Ref:    ref,
   318  		MdKeys: mdKeys,
   319  	}
   320  	bodyStr, _ := json.Marshal(bodyObj)
   321  	log := appctx.GetLogger(ctx)
   322  	log.Info().Msgf("GetMD %s", bodyStr)
   323  
   324  	status, body, err := nc.do(ctx, Action{"GetMD", string(bodyStr)})
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  	if status == 404 {
   329  		return nil, errtypes.NotFound("")
   330  	}
   331  	var respObj provider.ResourceInfo
   332  	err = json.Unmarshal(body, &respObj)
   333  	if err != nil {
   334  		return nil, err
   335  	}
   336  	return &respObj, nil
   337  }
   338  
   339  // ListFolder as defined in the storage.FS interface
   340  func (nc *StorageDriver) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error) {
   341  	type paramsObj struct {
   342  		Ref    *provider.Reference `json:"ref"`
   343  		MdKeys []string            `json:"mdKeys"`
   344  	}
   345  	bodyObj := &paramsObj{
   346  		Ref:    ref,
   347  		MdKeys: mdKeys,
   348  	}
   349  	bodyStr, err := json.Marshal(bodyObj)
   350  	log := appctx.GetLogger(ctx)
   351  	log.Info().Msgf("ListFolder %s", bodyStr)
   352  	if err != nil {
   353  		return nil, err
   354  	}
   355  	status, body, err := nc.do(ctx, Action{"ListFolder", string(bodyStr)})
   356  	if err != nil {
   357  		return nil, err
   358  	}
   359  	if status == 404 {
   360  		return nil, errtypes.NotFound("")
   361  	}
   362  
   363  	var respMapArr []provider.ResourceInfo
   364  	err = json.Unmarshal(body, &respMapArr)
   365  	if err != nil {
   366  		return nil, err
   367  	}
   368  	var pointers = make([]*provider.ResourceInfo, len(respMapArr))
   369  	for i := 0; i < len(respMapArr); i++ {
   370  		pointers[i] = &respMapArr[i]
   371  	}
   372  	return pointers, err
   373  }
   374  
   375  // InitiateUpload as defined in the storage.FS interface
   376  func (nc *StorageDriver) InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error) {
   377  	type paramsObj struct {
   378  		Ref          *provider.Reference `json:"ref"`
   379  		UploadLength int64               `json:"uploadLength"`
   380  		Metadata     map[string]string   `json:"metadata"`
   381  	}
   382  	bodyObj := &paramsObj{
   383  		Ref:          ref,
   384  		UploadLength: uploadLength,
   385  		Metadata:     metadata,
   386  	}
   387  	bodyStr, _ := json.Marshal(bodyObj)
   388  	log := appctx.GetLogger(ctx)
   389  	log.Info().Msgf("InitiateUpload %s", bodyStr)
   390  
   391  	_, respBody, err := nc.do(ctx, Action{"InitiateUpload", string(bodyStr)})
   392  	if err != nil {
   393  		return nil, err
   394  	}
   395  	respMap := make(map[string]string)
   396  	err = json.Unmarshal(respBody, &respMap)
   397  	if err != nil {
   398  		return nil, err
   399  	}
   400  	return respMap, err
   401  }
   402  
   403  // Upload as defined in the storage.FS interface
   404  func (nc *StorageDriver) Upload(ctx context.Context, req storage.UploadRequest, _ storage.UploadFinishedFunc) (*provider.ResourceInfo, error) {
   405  	err := nc.doUpload(ctx, req.Ref.Path, req.Body)
   406  	if err != nil {
   407  		return &provider.ResourceInfo{}, err
   408  	}
   409  
   410  	// return id, etag and mtime
   411  	ri, err := nc.GetMD(ctx, req.Ref, []string{}, []string{"id", "etag", "mtime"})
   412  	if err != nil {
   413  		return &provider.ResourceInfo{}, err
   414  	}
   415  
   416  	return ri, nil
   417  }
   418  
   419  // Download as defined in the storage.FS interface
   420  func (nc *StorageDriver) Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
   421  	md, err := nc.GetMD(ctx, ref, []string{}, nil)
   422  	if err != nil {
   423  		return nil, nil, err
   424  	}
   425  	if !openReaderfunc(md) {
   426  		return md, nil, nil
   427  	}
   428  
   429  	reader, err := nc.doDownload(ctx, ref.Path)
   430  	return md, reader, err
   431  }
   432  
   433  // ListRevisions as defined in the storage.FS interface
   434  func (nc *StorageDriver) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) {
   435  	bodyStr, _ := json.Marshal(ref)
   436  	log := appctx.GetLogger(ctx)
   437  	log.Info().Msgf("ListRevisions %s", bodyStr)
   438  
   439  	_, respBody, err := nc.do(ctx, Action{"ListRevisions", string(bodyStr)})
   440  
   441  	if err != nil {
   442  		return nil, err
   443  	}
   444  	var respMapArr []provider.FileVersion
   445  	err = json.Unmarshal(respBody, &respMapArr)
   446  	if err != nil {
   447  		return nil, err
   448  	}
   449  	revs := make([]*provider.FileVersion, len(respMapArr))
   450  	for i := 0; i < len(respMapArr); i++ {
   451  		revs[i] = &respMapArr[i]
   452  	}
   453  	return revs, err
   454  }
   455  
   456  // DownloadRevision as defined in the storage.FS interface
   457  func (nc *StorageDriver) DownloadRevision(ctx context.Context, ref *provider.Reference, key string, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
   458  	log := appctx.GetLogger(ctx)
   459  	log.Info().Msgf("DownloadRevision %s %s", ref.Path, key)
   460  
   461  	md, err := nc.GetMD(ctx, ref, []string{}, nil)
   462  	if err != nil {
   463  		return nil, nil, err
   464  	}
   465  	revs, err := nc.ListRevisions(ctx, ref)
   466  	if err != nil {
   467  		return nil, nil, err
   468  	}
   469  	var ri *provider.ResourceInfo
   470  	for _, rev := range revs {
   471  		if rev.Key == key {
   472  
   473  			ri = &provider.ResourceInfo{
   474  				// TODO(jfd) we cannot access version content, this will be a problem when trying to fetch version thumbnails
   475  				// Opaque
   476  				Type: provider.ResourceType_RESOURCE_TYPE_FILE,
   477  				Id: &provider.ResourceId{
   478  					StorageId: "versions",
   479  					OpaqueId:  md.Id.OpaqueId + "@" + rev.GetKey(),
   480  				},
   481  				// Checksum
   482  				Etag: rev.Etag,
   483  				// MimeType
   484  				Mtime: &types.Timestamp{
   485  					Seconds: rev.Mtime,
   486  					// TODO cs3apis FileVersion should use types.Timestamp instead of uint64
   487  				},
   488  				Path: path.Join("v", rev.Key),
   489  				// PermissionSet
   490  				Size:  rev.Size,
   491  				Owner: md.Owner,
   492  			}
   493  		}
   494  	}
   495  	if ri == nil {
   496  		return nil, nil, errtypes.NotFound("revision not found")
   497  	}
   498  
   499  	if !openReaderfunc(ri) {
   500  		return ri, nil, nil
   501  	}
   502  
   503  	readCloser, err := nc.doDownloadRevision(ctx, ref.Path, key)
   504  	return ri, readCloser, err
   505  }
   506  
   507  // RestoreRevision as defined in the storage.FS interface
   508  func (nc *StorageDriver) RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error {
   509  	type paramsObj struct {
   510  		Ref *provider.Reference `json:"ref"`
   511  		Key string              `json:"key"`
   512  	}
   513  	bodyObj := &paramsObj{
   514  		Ref: ref,
   515  		Key: key,
   516  	}
   517  	bodyStr, _ := json.Marshal(bodyObj)
   518  	log := appctx.GetLogger(ctx)
   519  	log.Info().Msgf("RestoreRevision %s", bodyStr)
   520  
   521  	_, _, err := nc.do(ctx, Action{"RestoreRevision", string(bodyStr)})
   522  	return err
   523  }
   524  
   525  // ListRecycle as defined in the storage.FS interface
   526  func (nc *StorageDriver) ListRecycle(ctx context.Context, ref *provider.Reference, key string, relativePath string) ([]*provider.RecycleItem, error) {
   527  	log := appctx.GetLogger(ctx)
   528  	log.Info().Msg("ListRecycle")
   529  	type paramsObj struct {
   530  		Key  string `json:"key"`
   531  		Path string `json:"path"`
   532  	}
   533  	bodyObj := &paramsObj{
   534  		Key:  key,
   535  		Path: relativePath,
   536  	}
   537  	bodyStr, _ := json.Marshal(bodyObj)
   538  
   539  	_, respBody, err := nc.do(ctx, Action{"ListRecycle", string(bodyStr)})
   540  
   541  	if err != nil {
   542  		return nil, err
   543  	}
   544  	var respMapArr []provider.RecycleItem
   545  	err = json.Unmarshal(respBody, &respMapArr)
   546  	if err != nil {
   547  		return nil, err
   548  	}
   549  	items := make([]*provider.RecycleItem, len(respMapArr))
   550  	for i := 0; i < len(respMapArr); i++ {
   551  		items[i] = &respMapArr[i]
   552  	}
   553  	return items, err
   554  }
   555  
   556  // RestoreRecycleItem as defined in the storage.FS interface
   557  func (nc *StorageDriver) RestoreRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string, restoreRef *provider.Reference) error {
   558  	type paramsObj struct {
   559  		Key        string              `json:"key"`
   560  		Path       string              `json:"path"`
   561  		RestoreRef *provider.Reference `json:"restoreRef"`
   562  	}
   563  	bodyObj := &paramsObj{
   564  		Key:        key,
   565  		Path:       relativePath,
   566  		RestoreRef: restoreRef,
   567  	}
   568  	bodyStr, _ := json.Marshal(bodyObj)
   569  
   570  	log := appctx.GetLogger(ctx)
   571  	log.Info().Msgf("RestoreRecycleItem %s", bodyStr)
   572  
   573  	_, _, err := nc.do(ctx, Action{"RestoreRecycleItem", string(bodyStr)})
   574  
   575  	return err
   576  }
   577  
   578  // PurgeRecycleItem as defined in the storage.FS interface
   579  func (nc *StorageDriver) PurgeRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string) error {
   580  	type paramsObj struct {
   581  		Key  string `json:"key"`
   582  		Path string `json:"path"`
   583  	}
   584  	bodyObj := &paramsObj{
   585  		Key:  key,
   586  		Path: relativePath,
   587  	}
   588  	bodyStr, _ := json.Marshal(bodyObj)
   589  	log := appctx.GetLogger(ctx)
   590  	log.Info().Msgf("PurgeRecycleItem %s", bodyStr)
   591  
   592  	_, _, err := nc.do(ctx, Action{"PurgeRecycleItem", string(bodyStr)})
   593  	return err
   594  }
   595  
   596  // EmptyRecycle as defined in the storage.FS interface
   597  func (nc *StorageDriver) EmptyRecycle(ctx context.Context, ref *provider.Reference) error {
   598  	log := appctx.GetLogger(ctx)
   599  	log.Info().Msg("EmptyRecycle")
   600  
   601  	_, _, err := nc.do(ctx, Action{"EmptyRecycle", ""})
   602  	return err
   603  }
   604  
   605  // GetPathByID as defined in the storage.FS interface
   606  func (nc *StorageDriver) GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) {
   607  	bodyStr, _ := json.Marshal(id)
   608  	_, respBody, err := nc.do(ctx, Action{"GetPathByID", string(bodyStr)})
   609  	return string(respBody), err
   610  }
   611  
   612  // AddGrant as defined in the storage.FS interface
   613  func (nc *StorageDriver) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
   614  	type paramsObj struct {
   615  		Ref *provider.Reference `json:"ref"`
   616  		G   *provider.Grant     `json:"g"`
   617  	}
   618  	bodyObj := &paramsObj{
   619  		Ref: ref,
   620  		G:   g,
   621  	}
   622  	bodyStr, _ := json.Marshal(bodyObj)
   623  	log := appctx.GetLogger(ctx)
   624  	log.Info().Msgf("AddGrant %s", bodyStr)
   625  
   626  	_, _, err := nc.do(ctx, Action{"AddGrant", string(bodyStr)})
   627  	return err
   628  }
   629  
   630  // DenyGrant as defined in the storage.FS interface
   631  func (nc *StorageDriver) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error {
   632  	type paramsObj struct {
   633  		Ref *provider.Reference `json:"ref"`
   634  		G   *provider.Grantee   `json:"g"`
   635  	}
   636  	bodyObj := &paramsObj{
   637  		Ref: ref,
   638  		G:   g,
   639  	}
   640  	bodyStr, _ := json.Marshal(bodyObj)
   641  	log := appctx.GetLogger(ctx)
   642  	log.Info().Msgf("DenyGrant %s", bodyStr)
   643  
   644  	_, _, err := nc.do(ctx, Action{"DenyGrant", string(bodyStr)})
   645  	return err
   646  }
   647  
   648  // RemoveGrant as defined in the storage.FS interface
   649  func (nc *StorageDriver) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
   650  	type paramsObj struct {
   651  		Ref *provider.Reference `json:"ref"`
   652  		G   *provider.Grant     `json:"g"`
   653  	}
   654  	bodyObj := &paramsObj{
   655  		Ref: ref,
   656  		G:   g,
   657  	}
   658  	bodyStr, _ := json.Marshal(bodyObj)
   659  	log := appctx.GetLogger(ctx)
   660  	log.Info().Msgf("RemoveGrant %s", bodyStr)
   661  
   662  	_, _, err := nc.do(ctx, Action{"RemoveGrant", string(bodyStr)})
   663  	return err
   664  }
   665  
   666  // UpdateGrant as defined in the storage.FS interface
   667  func (nc *StorageDriver) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
   668  	type paramsObj struct {
   669  		Ref *provider.Reference `json:"ref"`
   670  		G   *provider.Grant     `json:"g"`
   671  	}
   672  	bodyObj := &paramsObj{
   673  		Ref: ref,
   674  		G:   g,
   675  	}
   676  	bodyStr, _ := json.Marshal(bodyObj)
   677  	log := appctx.GetLogger(ctx)
   678  	log.Info().Msgf("UpdateGrant %s", bodyStr)
   679  
   680  	_, _, err := nc.do(ctx, Action{"UpdateGrant", string(bodyStr)})
   681  	return err
   682  }
   683  
   684  // ListGrants as defined in the storage.FS interface
   685  func (nc *StorageDriver) ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) {
   686  	bodyStr, _ := json.Marshal(ref)
   687  	log := appctx.GetLogger(ctx)
   688  	log.Info().Msgf("ListGrants %s", bodyStr)
   689  
   690  	_, respBody, err := nc.do(ctx, Action{"ListGrants", string(bodyStr)})
   691  	if err != nil {
   692  		return nil, err
   693  	}
   694  
   695  	// To avoid this error:
   696  	// json: cannot unmarshal object into Go struct field Grantee.grantee.Id of type providerv1beta1.isGrantee_Id
   697  	// To test:
   698  	// bodyStr, _ := json.Marshal(provider.Grant{
   699  	// 	 Grantee: &provider.Grantee{
   700  	// 		 Type: provider.GranteeType_GRANTEE_TYPE_USER,
   701  	// 		 Id: &provider.Grantee_UserId{
   702  	// 			 UserId: &user.UserId{
   703  	// 				 Idp:      "some-idp",
   704  	// 				 OpaqueId: "some-opaque-id",
   705  	// 				 Type:     user.UserType_USER_TYPE_PRIMARY,
   706  	// 			 },
   707  	// 		 },
   708  	// 	 },
   709  	// 	 Permissions: &provider.ResourcePermissions{},
   710  	// })
   711  	// JSON example:
   712  	// [{"grantee":{"Id":{"UserId":{"idp":"some-idp","opaque_id":"some-opaque-id","type":1}}},"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true}}]
   713  	var respMapArr []map[string]interface{}
   714  	err = json.Unmarshal(respBody, &respMapArr)
   715  	if err != nil {
   716  		return nil, err
   717  	}
   718  	grants := make([]*provider.Grant, len(respMapArr))
   719  	for i := 0; i < len(respMapArr); i++ {
   720  		granteeMap := respMapArr[i]["grantee"].(map[string]interface{})
   721  		granteeIDMap := granteeMap["Id"].(map[string]interface{})
   722  		granteeIDUserIDMap := granteeIDMap["UserId"].(map[string]interface{})
   723  
   724  		// if (granteeMap["Id"])
   725  		permsMap := respMapArr[i]["permissions"].(map[string]interface{})
   726  		grants[i] = &provider.Grant{
   727  			Grantee: &provider.Grantee{
   728  				Type: provider.GranteeType_GRANTEE_TYPE_USER, // FIXME: support groups too
   729  				Id: &provider.Grantee_UserId{
   730  					UserId: &user.UserId{
   731  						Idp:      granteeIDUserIDMap["idp"].(string),
   732  						OpaqueId: granteeIDUserIDMap["opaque_id"].(string),
   733  						Type:     user.UserType(granteeIDUserIDMap["type"].(float64)),
   734  					},
   735  				},
   736  			},
   737  			Permissions: &provider.ResourcePermissions{
   738  				AddGrant:             permsMap["add_grant"].(bool),
   739  				CreateContainer:      permsMap["create_container"].(bool),
   740  				Delete:               permsMap["delete"].(bool),
   741  				GetPath:              permsMap["get_path"].(bool),
   742  				GetQuota:             permsMap["get_quota"].(bool),
   743  				InitiateFileDownload: permsMap["initiate_file_download"].(bool),
   744  				InitiateFileUpload:   permsMap["initiate_file_upload"].(bool),
   745  				ListGrants:           permsMap["list_grants"].(bool),
   746  				ListContainer:        permsMap["list_container"].(bool),
   747  				ListFileVersions:     permsMap["list_file_versions"].(bool),
   748  				ListRecycle:          permsMap["list_recycle"].(bool),
   749  				Move:                 permsMap["move"].(bool),
   750  				RemoveGrant:          permsMap["remove_grant"].(bool),
   751  				PurgeRecycle:         permsMap["purge_recycle"].(bool),
   752  				RestoreFileVersion:   permsMap["restore_file_version"].(bool),
   753  				RestoreRecycleItem:   permsMap["restore_recycle_item"].(bool),
   754  				Stat:                 permsMap["stat"].(bool),
   755  				UpdateGrant:          permsMap["update_grant"].(bool),
   756  			},
   757  		}
   758  	}
   759  	return grants, err
   760  }
   761  
   762  // GetQuota as defined in the storage.FS interface
   763  func (nc *StorageDriver) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, uint64, error) {
   764  	log := appctx.GetLogger(ctx)
   765  	log.Info().Msg("GetQuota")
   766  
   767  	_, respBody, err := nc.do(ctx, Action{"GetQuota", ""})
   768  	if err != nil {
   769  		return 0, 0, 0, err
   770  	}
   771  
   772  	var respMap map[string]interface{}
   773  	err = json.Unmarshal(respBody, &respMap)
   774  	if err != nil {
   775  		return 0, 0, 0, err
   776  	}
   777  
   778  	total := uint64(respMap["totalBytes"].(float64))
   779  	used := uint64(respMap["usedBytes"].(float64))
   780  	remaining := total - used
   781  	return total, used, remaining, err
   782  }
   783  
   784  // CreateReference as defined in the storage.FS interface
   785  func (nc *StorageDriver) CreateReference(ctx context.Context, path string, targetURI *url.URL) error {
   786  	type paramsObj struct {
   787  		Path string `json:"path"`
   788  		URL  string `json:"url"`
   789  	}
   790  	bodyObj := &paramsObj{
   791  		Path: path,
   792  		URL:  targetURI.String(),
   793  	}
   794  	bodyStr, _ := json.Marshal(bodyObj)
   795  
   796  	_, _, err := nc.do(ctx, Action{"CreateReference", string(bodyStr)})
   797  	return err
   798  }
   799  
   800  // Shutdown as defined in the storage.FS interface
   801  func (nc *StorageDriver) Shutdown(ctx context.Context) error {
   802  	log := appctx.GetLogger(ctx)
   803  	log.Info().Msg("Shutdown")
   804  
   805  	_, _, err := nc.do(ctx, Action{"Shutdown", ""})
   806  	return err
   807  }
   808  
   809  // SetArbitraryMetadata as defined in the storage.FS interface
   810  func (nc *StorageDriver) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error {
   811  	type paramsObj struct {
   812  		Ref *provider.Reference         `json:"ref"`
   813  		Md  *provider.ArbitraryMetadata `json:"md"`
   814  	}
   815  	bodyObj := &paramsObj{
   816  		Ref: ref,
   817  		Md:  md,
   818  	}
   819  	bodyStr, _ := json.Marshal(bodyObj)
   820  	log := appctx.GetLogger(ctx)
   821  	log.Info().Msgf("SetArbitraryMetadata %s", bodyStr)
   822  
   823  	_, _, err := nc.do(ctx, Action{"SetArbitraryMetadata", string(bodyStr)})
   824  	return err
   825  }
   826  
   827  // UnsetArbitraryMetadata as defined in the storage.FS interface
   828  func (nc *StorageDriver) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error {
   829  	type paramsObj struct {
   830  		Ref  *provider.Reference `json:"ref"`
   831  		Keys []string            `json:"keys"`
   832  	}
   833  	bodyObj := &paramsObj{
   834  		Ref:  ref,
   835  		Keys: keys,
   836  	}
   837  	bodyStr, _ := json.Marshal(bodyObj)
   838  	log := appctx.GetLogger(ctx)
   839  	log.Info().Msgf("UnsetArbitraryMetadata %s", bodyStr)
   840  
   841  	_, _, err := nc.do(ctx, Action{"UnsetArbitraryMetadata", string(bodyStr)})
   842  	return err
   843  }
   844  
   845  // GetLock returns an existing lock on the given reference
   846  func (nc *StorageDriver) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) {
   847  	return nil, errtypes.NotSupported("unimplemented")
   848  }
   849  
   850  // SetLock puts a lock on the given reference
   851  func (nc *StorageDriver) SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error {
   852  	return errtypes.NotSupported("unimplemented")
   853  }
   854  
   855  // RefreshLock refreshes an existing lock on the given reference
   856  func (nc *StorageDriver) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock, existingLockID string) error {
   857  	return errtypes.NotSupported("unimplemented")
   858  }
   859  
   860  // Unlock removes an existing lock from the given reference
   861  func (nc *StorageDriver) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error {
   862  	return errtypes.NotSupported("unimplemented")
   863  }
   864  
   865  // ListStorageSpaces as defined in the storage.FS interface
   866  func (nc *StorageDriver) ListStorageSpaces(ctx context.Context, f []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error) {
   867  	bodyStr, _ := json.Marshal(f)
   868  	_, respBody, err := nc.do(ctx, Action{"ListStorageSpaces", string(bodyStr)})
   869  	if err != nil {
   870  		return nil, err
   871  	}
   872  
   873  	// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L1341-L1366
   874  	var respMapArr []provider.StorageSpace
   875  	err = json.Unmarshal(respBody, &respMapArr)
   876  	if err != nil {
   877  		return nil, err
   878  	}
   879  	var spaces = make([]*provider.StorageSpace, len(respMapArr))
   880  	for i := 0; i < len(respMapArr); i++ {
   881  		spaces[i] = &respMapArr[i]
   882  	}
   883  	return spaces, err
   884  }
   885  
   886  // CreateStorageSpace creates a storage space
   887  func (nc *StorageDriver) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) {
   888  	bodyStr, _ := json.Marshal(req)
   889  	_, respBody, err := nc.do(ctx, Action{"CreateStorageSpace", string(bodyStr)})
   890  	if err != nil {
   891  		return nil, err
   892  	}
   893  	var respObj provider.CreateStorageSpaceResponse
   894  	err = json.Unmarshal(respBody, &respObj)
   895  	if err != nil {
   896  		return nil, err
   897  	}
   898  	return &respObj, nil
   899  }
   900  
   901  // UpdateStorageSpace updates a storage space
   902  func (nc *StorageDriver) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) {
   903  	bodyStr, _ := json.Marshal(req)
   904  	_, respBody, err := nc.do(ctx, Action{"UpdateStorageSpace", string(bodyStr)})
   905  	if err != nil {
   906  		return nil, err
   907  	}
   908  	var respObj provider.UpdateStorageSpaceResponse
   909  	err = json.Unmarshal(respBody, &respObj)
   910  	if err != nil {
   911  		return nil, err
   912  	}
   913  	return &respObj, nil
   914  }
   915  
   916  // DeleteStorageSpace deletes a storage space
   917  func (nc *StorageDriver) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error {
   918  	bodyStr, _ := json.Marshal(req)
   919  	_, respBody, err := nc.do(ctx, Action{"DeleteStorageSpace", string(bodyStr)})
   920  	if err != nil {
   921  		return err
   922  	}
   923  	var respObj provider.DeleteStorageSpaceResponse
   924  	err = json.Unmarshal(respBody, &respObj)
   925  	if err != nil {
   926  		return err
   927  	}
   928  	return nil
   929  }