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 }