github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/eosfs/spaces.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 eosfs
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"path"
    25  	"regexp"
    26  	"strconv"
    27  	"strings"
    28  	"time"
    29  
    30  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    31  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/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/cs3org/reva/v2/pkg/appctx"
    35  	"github.com/cs3org/reva/v2/pkg/eosclient"
    36  	"github.com/cs3org/reva/v2/pkg/errtypes"
    37  	"github.com/cs3org/reva/v2/pkg/storage/utils/templates"
    38  	"github.com/cs3org/reva/v2/pkg/storagespace"
    39  	"github.com/pkg/errors"
    40  )
    41  
    42  const (
    43  	spaceTypePersonal = "personal"
    44  	spaceTypeProject  = "project"
    45  	// spaceTypeShare    = "share"
    46  )
    47  
    48  // SpacesConfig specifies the required configuration parameters needed
    49  // to connect to the project spaces DB
    50  type SpacesConfig struct {
    51  	Enabled    bool   `mapstructure:"enabled"`
    52  	DbUsername string `mapstructure:"db_username"`
    53  	DbPassword string `mapstructure:"db_password"`
    54  	DbHost     string `mapstructure:"db_host"`
    55  	DbName     string `mapstructure:"db_name"`
    56  	DbTable    string `mapstructure:"db_table"`
    57  	DbPort     int    `mapstructure:"db_port"`
    58  }
    59  
    60  var (
    61  	egroupRegex = regexp.MustCompile(`^cernbox-project-(?P<Name>.+)-(?P<Permissions>admins|writers|readers)\z`)
    62  )
    63  
    64  func (fs *eosfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error) {
    65  	u, err := getUser(ctx)
    66  	if err != nil {
    67  		err = errors.Wrap(err, "eosfs: wrap: no user in ctx")
    68  		return nil, err
    69  	}
    70  
    71  	spaceID, spaceType, spacePath := "", "", ""
    72  
    73  	for i := range filter {
    74  		switch filter[i].Type {
    75  		case provider.ListStorageSpacesRequest_Filter_TYPE_ID:
    76  			_, spaceID, _, _ = storagespace.SplitID(filter[i].GetId().OpaqueId)
    77  		case provider.ListStorageSpacesRequest_Filter_TYPE_PATH:
    78  			spacePath = filter[i].GetPath()
    79  		case provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE:
    80  			spaceType = filter[i].GetSpaceType()
    81  		}
    82  	}
    83  
    84  	if spaceType != "" && spaceType != spaceTypePersonal && spaceType != spaceTypeProject {
    85  		spaceType = ""
    86  	}
    87  
    88  	cachedSpaces, err := fs.fetchCachedSpaces(ctx, u, spaceType, spaceID, spacePath)
    89  	if err == nil {
    90  		return cachedSpaces, nil
    91  	}
    92  
    93  	spaces := []*provider.StorageSpace{}
    94  
    95  	if !fs.conf.SpacesConfig.Enabled && (spaceType == "" || spaceType == spaceTypePersonal) {
    96  		personalSpaces, err := fs.listPersonalStorageSpaces(ctx, u, spaceID, spacePath)
    97  		if err != nil {
    98  			return nil, err
    99  		}
   100  		spaces = append(spaces, personalSpaces...)
   101  	}
   102  	if fs.conf.SpacesConfig.Enabled && (spaceType == "" || spaceType == spaceTypeProject) {
   103  		projectSpaces, err := fs.listProjectStorageSpaces(ctx, u, spaceID, spacePath)
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  		spaces = append(spaces, projectSpaces...)
   108  	}
   109  
   110  	fs.cacheSpaces(ctx, u, spaceType, spaceID, spacePath, spaces)
   111  	return spaces, nil
   112  }
   113  
   114  func (fs *eosfs) listPersonalStorageSpaces(ctx context.Context, u *userpb.User, spaceID, spacePath string) ([]*provider.StorageSpace, error) {
   115  	var eosFileInfo *eosclient.FileInfo
   116  	// if no spaceID and spacePath are provided, we just return the user home
   117  	switch {
   118  	case spaceID == "" && spacePath == "":
   119  		fn, err := fs.wrapUserHomeStorageSpaceID(ctx, u, "/")
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  
   124  		auth, err := fs.getUserAuth(ctx, u, fn)
   125  		if err != nil {
   126  			return nil, err
   127  		}
   128  		eosFileInfo, err = fs.c.GetFileInfoByPath(ctx, auth, fn)
   129  		if err != nil {
   130  			return nil, err
   131  		}
   132  	case spacePath == "":
   133  		// else, we'll stat the resource by inode
   134  		auth, err := fs.getUserAuth(ctx, u, "")
   135  		if err != nil {
   136  			return nil, err
   137  		}
   138  
   139  		inode, err := strconv.ParseUint(spaceID, 10, 64)
   140  		if err != nil {
   141  			return nil, err
   142  		}
   143  
   144  		eosFileInfo, err = fs.c.GetFileInfoByInode(ctx, auth, inode)
   145  		if err != nil {
   146  			return nil, err
   147  		}
   148  	default:
   149  		fn := fs.wrap(ctx, spacePath)
   150  		auth, err := fs.getUserAuth(ctx, u, fn)
   151  		if err != nil {
   152  			return nil, err
   153  		}
   154  		eosFileInfo, err = fs.c.GetFileInfoByPath(ctx, auth, fn)
   155  		if err != nil {
   156  			return nil, err
   157  		}
   158  	}
   159  
   160  	md, err := fs.convertToResourceInfo(ctx, eosFileInfo)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	// If the request was for a relative ref, return just the base path
   166  	if !strings.HasPrefix(spacePath, "/") {
   167  		md.Path = path.Base(md.Path)
   168  	}
   169  
   170  	return []*provider.StorageSpace{{
   171  		Id:        &provider.StorageSpaceId{OpaqueId: md.Id.OpaqueId},
   172  		Name:      md.Owner.OpaqueId,
   173  		SpaceType: "personal",
   174  		Owner:     &userpb.User{Id: md.Owner},
   175  		Root: &provider.ResourceId{
   176  			StorageId: md.Id.OpaqueId,
   177  			OpaqueId:  md.Id.OpaqueId,
   178  		},
   179  		Mtime: &types.Timestamp{
   180  			Seconds: eosFileInfo.MTimeSec,
   181  			Nanos:   eosFileInfo.MTimeNanos,
   182  		},
   183  		Quota: &provider.Quota{},
   184  		Opaque: &types.Opaque{
   185  			Map: map[string]*types.OpaqueEntry{
   186  				"path": {
   187  					Decoder: "plain",
   188  					Value:   []byte(md.Path),
   189  				},
   190  			},
   191  		},
   192  	}}, nil
   193  }
   194  
   195  func (fs *eosfs) listProjectStorageSpaces(ctx context.Context, user *userpb.User, spaceID, spacePath string) ([]*provider.StorageSpace, error) {
   196  	if !fs.conf.SpacesConfig.Enabled {
   197  		return nil, errtypes.NotSupported("list storage spaces")
   198  	}
   199  
   200  	log := appctx.GetLogger(ctx)
   201  
   202  	// Find all the project groups the user belongs to
   203  	userProjectGroupsMap := make(map[string]bool)
   204  	for _, group := range user.Groups {
   205  		match := egroupRegex.FindStringSubmatch(group)
   206  		if match != nil {
   207  			userProjectGroupsMap[match[1]] = true
   208  		}
   209  	}
   210  
   211  	if len(userProjectGroupsMap) == 0 {
   212  		return nil, nil
   213  	}
   214  
   215  	query := "SELECT project_name, eos_relative_path FROM " + fs.conf.SpacesConfig.DbTable + " WHERE project_name in (?" + strings.Repeat(",?", len(userProjectGroupsMap)-1) + ")"
   216  	params := []interface{}{}
   217  	for k := range userProjectGroupsMap {
   218  		params = append(params, k)
   219  	}
   220  
   221  	rows, err := fs.spacesDB.Query(query, params...)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	defer rows.Close()
   226  
   227  	var dbProjects []*provider.StorageSpace
   228  	for rows.Next() {
   229  		var name, relPath string
   230  		if err = rows.Scan(&name, &relPath); err == nil {
   231  			info, err := fs.GetMD(ctx, &provider.Reference{Path: relPath}, []string{}, nil)
   232  			if err == nil {
   233  				if (spaceID == "" || spaceID == info.Id.OpaqueId) && (spacePath == "" || spacePath == relPath) {
   234  					// If the request was for a relative ref, return just the base path
   235  					if !strings.HasPrefix(spacePath, "/") {
   236  						relPath = path.Base(relPath)
   237  					}
   238  
   239  					dbProjects = append(dbProjects, &provider.StorageSpace{
   240  						Id:        &provider.StorageSpaceId{OpaqueId: name},
   241  						Name:      name,
   242  						SpaceType: "project",
   243  						Owner: &userpb.User{
   244  							Id: info.Owner,
   245  						},
   246  						Root:  &provider.ResourceId{StorageId: info.Id.OpaqueId, OpaqueId: info.Id.OpaqueId},
   247  						Mtime: info.Mtime,
   248  						Quota: &provider.Quota{},
   249  						Opaque: &types.Opaque{
   250  							Map: map[string]*types.OpaqueEntry{
   251  								"path": {
   252  									Decoder: "plain",
   253  									Value:   []byte(relPath),
   254  								},
   255  							},
   256  						},
   257  					})
   258  				}
   259  
   260  			} else {
   261  				log.Error().Err(err).Str("path", relPath).Msgf("eosfs: error statting storage space")
   262  			}
   263  		}
   264  	}
   265  
   266  	return dbProjects, nil
   267  }
   268  
   269  func (fs *eosfs) fetchCachedSpaces(ctx context.Context, user *userpb.User, spaceType, spaceID, spacePath string) ([]*provider.StorageSpace, error) {
   270  	key := user.Id.OpaqueId + ":" + spaceType + ":" + spaceID + ":" + spacePath
   271  	if spacesIf, err := fs.spacesCache.Get(key); err == nil {
   272  		log := appctx.GetLogger(ctx)
   273  		log.Info().Msgf("found cached spaces %s", key)
   274  		return spacesIf.([]*provider.StorageSpace), nil
   275  	}
   276  	return nil, errtypes.NotFound("eosfs: spaces not found in cache")
   277  }
   278  
   279  func (fs *eosfs) cacheSpaces(ctx context.Context, user *userpb.User, spaceType, spaceID, spacePath string, spaces []*provider.StorageSpace) {
   280  	key := user.Id.OpaqueId + ":" + spaceType + ":" + spaceID + ":" + spacePath
   281  	_ = fs.spacesCache.SetWithExpire(key, spaces, time.Second*time.Duration(60))
   282  }
   283  
   284  func (fs *eosfs) wrapUserHomeStorageSpaceID(ctx context.Context, u *userpb.User, fn string) (string, error) {
   285  	layout := templates.WithUser(u, fs.conf.UserLayout)
   286  	internal := path.Join(fs.conf.Namespace, layout, fn)
   287  
   288  	appctx.GetLogger(ctx).Debug().Msg("eosfs: wrap storage space id=" + fn + " internal=" + internal)
   289  	return internal, nil
   290  }
   291  
   292  func (fs *eosfs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) {
   293  	// The request is to create a user home
   294  	if req.Type == spaceTypePersonal {
   295  		u, err := getUser(ctx)
   296  		if err != nil {
   297  			err = errors.Wrap(err, "eosfs: wrap: no user in ctx")
   298  			return nil, err
   299  		}
   300  
   301  		// We need the unique path corresponding to the user. We assume that the username is the ID, and determine the path based on a specified template
   302  		fn, err := fs.wrapUserHomeStorageSpaceID(ctx, u, "/")
   303  		if err != nil {
   304  			return nil, err
   305  		}
   306  
   307  		err = fs.createNominalHome(ctx, fn)
   308  		if err != nil {
   309  			return nil, err
   310  		}
   311  
   312  		auth, err := fs.getUserAuth(ctx, u, fn)
   313  		if err != nil {
   314  			return nil, err
   315  		}
   316  		eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, auth, fn)
   317  		if err != nil {
   318  			return nil, err
   319  		}
   320  		sid := fmt.Sprintf("%d", eosFileInfo.Inode)
   321  
   322  		space := &provider.StorageSpace{
   323  			Id:        &provider.StorageSpaceId{OpaqueId: sid},
   324  			Name:      u.Id.OpaqueId,
   325  			SpaceType: "personal",
   326  			Owner:     u,
   327  			Root: &provider.ResourceId{
   328  				StorageId: sid,
   329  				OpaqueId:  sid,
   330  			},
   331  			Mtime: &types.Timestamp{
   332  				Seconds: eosFileInfo.MTimeSec,
   333  				Nanos:   eosFileInfo.MTimeNanos,
   334  			},
   335  			Quota: &provider.Quota{},
   336  			Opaque: &types.Opaque{
   337  				Map: map[string]*types.OpaqueEntry{
   338  					"path": {
   339  						Decoder: "plain",
   340  						Value:   []byte(path.Base(fn)),
   341  					},
   342  				},
   343  			},
   344  		}
   345  
   346  		return &provider.CreateStorageSpaceResponse{
   347  			Status: &rpc.Status{
   348  				Code: rpc.Code_CODE_OK,
   349  			},
   350  			StorageSpace: space,
   351  		}, nil
   352  
   353  	}
   354  
   355  	// We don't support creating any other types of shares (projects or spaces)
   356  	return nil, errtypes.NotSupported("eosfs: creating storage spaces of specified type is not supported")
   357  }
   358  
   359  func (fs *eosfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) {
   360  	return nil, errtypes.NotSupported("update storage space")
   361  }
   362  
   363  func (fs *eosfs) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error {
   364  	return errtypes.NotSupported("delete storage spaces")
   365  }