github.com/cs3org/reva/v2@v2.27.7/pkg/share/manager/json/json.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 json 20 21 import ( 22 "context" 23 "encoding/json" 24 "io" 25 "io/fs" 26 "os" 27 "strings" 28 "sync" 29 "time" 30 31 userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 32 rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 33 collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" 34 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 35 typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 36 "github.com/cs3org/reva/v2/pkg/appctx" 37 ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" 38 "github.com/cs3org/reva/v2/pkg/errtypes" 39 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 40 "github.com/cs3org/reva/v2/pkg/share" 41 "github.com/golang/protobuf/proto" // nolint:staticcheck // we need the legacy package to convert V1 to V2 messages 42 "github.com/google/uuid" 43 "github.com/mitchellh/mapstructure" 44 "github.com/pkg/errors" 45 "google.golang.org/genproto/protobuf/field_mask" 46 "google.golang.org/protobuf/encoding/prototext" 47 48 "github.com/cs3org/reva/v2/pkg/share/manager/registry" 49 "github.com/cs3org/reva/v2/pkg/utils" 50 ) 51 52 func init() { 53 registry.Register("json", New) 54 } 55 56 // New returns a new mgr. 57 func New(m map[string]interface{}) (share.Manager, error) { 58 c, err := parseConfig(m) 59 if err != nil { 60 err = errors.Wrap(err, "error creating a new manager") 61 return nil, err 62 } 63 64 if c.GatewayAddr == "" { 65 return nil, errors.New("share manager config is missing gateway address") 66 } 67 68 c.init() 69 70 // load or create file 71 model, err := loadOrCreate(c.File) 72 if err != nil { 73 err = errors.Wrap(err, "error loading the file containing the shares") 74 return nil, err 75 } 76 77 return &mgr{ 78 c: c, 79 model: model, 80 }, nil 81 } 82 83 func loadOrCreate(file string) (*shareModel, error) { 84 if info, err := os.Stat(file); errors.Is(err, fs.ErrNotExist) || info.Size() == 0 { 85 if err := os.WriteFile(file, []byte("{}"), 0700); err != nil { 86 err = errors.Wrap(err, "error opening/creating the file: "+file) 87 return nil, err 88 } 89 } 90 91 fd, err := os.OpenFile(file, os.O_CREATE, 0644) 92 if err != nil { 93 err = errors.Wrap(err, "error opening/creating the file: "+file) 94 return nil, err 95 } 96 defer fd.Close() 97 98 data, err := io.ReadAll(fd) 99 if err != nil { 100 err = errors.Wrap(err, "error reading the data") 101 return nil, err 102 } 103 104 j := &jsonEncoding{} 105 if err := json.Unmarshal(data, j); err != nil { 106 err = errors.Wrap(err, "error decoding data from json") 107 return nil, err 108 } 109 110 m := &shareModel{State: j.State, MountPoint: j.MountPoint} 111 for _, s := range j.Shares { 112 var decShare collaboration.Share 113 if err = utils.UnmarshalJSONToProtoV1([]byte(s), &decShare); err != nil { 114 return nil, errors.Wrap(err, "error decoding share from json") 115 } 116 m.Shares = append(m.Shares, &decShare) 117 } 118 119 if m.State == nil { 120 m.State = map[string]map[string]collaboration.ShareState{} 121 } 122 if m.MountPoint == nil { 123 m.MountPoint = map[string]map[string]*provider.Reference{} 124 } 125 126 m.file = file 127 return m, nil 128 } 129 130 type shareModel struct { 131 file string 132 State map[string]map[string]collaboration.ShareState `json:"state"` // map[username]map[share_id]ShareState 133 MountPoint map[string]map[string]*provider.Reference `json:"mount_point"` // map[username]map[share_id]MountPoint 134 Shares []*collaboration.Share `json:"shares"` 135 } 136 137 type jsonEncoding struct { 138 State map[string]map[string]collaboration.ShareState `json:"state"` // map[username]map[share_id]ShareState 139 MountPoint map[string]map[string]*provider.Reference `json:"mount_point"` // map[username]map[share_id]MountPoint 140 Shares []string `json:"shares"` 141 } 142 143 func (m *shareModel) Save() error { 144 j := &jsonEncoding{State: m.State, MountPoint: m.MountPoint} 145 for _, s := range m.Shares { 146 encShare, err := utils.MarshalProtoV1ToJSON(s) 147 if err != nil { 148 return errors.Wrap(err, "error encoding to json") 149 } 150 j.Shares = append(j.Shares, string(encShare)) 151 } 152 153 data, err := json.Marshal(j) 154 if err != nil { 155 err = errors.Wrap(err, "error encoding to json") 156 return err 157 } 158 159 if err := os.WriteFile(m.file, data, 0644); err != nil { 160 err = errors.Wrap(err, "error writing to file: "+m.file) 161 return err 162 } 163 164 return nil 165 } 166 167 type mgr struct { 168 c *config 169 sync.Mutex // concurrent access to the file 170 model *shareModel 171 } 172 173 type config struct { 174 File string `mapstructure:"file"` 175 GatewayAddr string `mapstructure:"gateway_addr"` 176 } 177 178 func (c *config) init() { 179 if c.File == "" { 180 c.File = "/var/tmp/reva/shares.json" 181 } 182 } 183 184 func parseConfig(m map[string]interface{}) (*config, error) { 185 c := &config{} 186 if err := mapstructure.Decode(m, c); err != nil { 187 return nil, err 188 } 189 return c, nil 190 } 191 192 // Dump exports shares and received shares to channels (e.g. during migration) 193 func (m *mgr) Dump(ctx context.Context, shareChan chan<- *collaboration.Share, receivedShareChan chan<- share.ReceivedShareWithUser) error { 194 log := appctx.GetLogger(ctx) 195 for _, s := range m.model.Shares { 196 shareChan <- s 197 } 198 199 for userIDString, states := range m.model.State { 200 userMountPoints := m.model.MountPoint[userIDString] 201 id := &userv1beta1.UserId{} 202 mV2 := proto.MessageV2(id) 203 if err := prototext.Unmarshal([]byte(userIDString), mV2); err != nil { 204 log.Error().Err(err).Msg("error unmarshalling the user id") 205 continue 206 } 207 208 for shareIDString, state := range states { 209 sid := &collaboration.ShareId{} 210 mV2 := proto.MessageV2(sid) 211 if err := prototext.Unmarshal([]byte(shareIDString), mV2); err != nil { 212 log.Error().Err(err).Msg("error unmarshalling the user id") 213 continue 214 } 215 216 var s *collaboration.Share 217 for _, is := range m.model.Shares { 218 if is.Id.OpaqueId == sid.OpaqueId { 219 s = is 220 break 221 } 222 } 223 if s == nil { 224 log.Warn().Str("share id", sid.OpaqueId).Msg("Share not found") 225 continue 226 } 227 228 var mp *provider.Reference 229 if userMountPoints != nil { 230 mp = userMountPoints[shareIDString] 231 } 232 233 receivedShareChan <- share.ReceivedShareWithUser{ 234 UserID: id, 235 ReceivedShare: &collaboration.ReceivedShare{ 236 Share: s, 237 State: state, 238 MountPoint: mp, 239 }, 240 } 241 } 242 } 243 244 return nil 245 } 246 247 func (m *mgr) Share(ctx context.Context, md *provider.ResourceInfo, g *collaboration.ShareGrant) (*collaboration.Share, error) { 248 id := uuid.NewString() 249 user := ctxpkg.ContextMustGetUser(ctx) 250 now := time.Now().UnixNano() 251 ts := &typespb.Timestamp{ 252 Seconds: uint64(now / int64(time.Second)), 253 Nanos: uint32(now % int64(time.Second)), 254 } 255 256 // do not allow share to myself or the owner if share is for a user 257 // TODO(labkode): should not this be caught already at the gw level? 258 if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && 259 (utils.UserEqual(g.Grantee.GetUserId(), user.Id) || utils.UserEqual(g.Grantee.GetUserId(), md.Owner)) { 260 return nil, errtypes.BadRequest("json: owner/creator and grantee are the same") 261 } 262 263 // check if share already exists. 264 key := &collaboration.ShareKey{ 265 Owner: md.Owner, 266 ResourceId: md.Id, 267 Grantee: g.Grantee, 268 } 269 270 m.Lock() 271 defer m.Unlock() 272 _, _, err := m.getByKey(key) 273 if err == nil { 274 // share already exists 275 return nil, errtypes.AlreadyExists(key.String()) 276 } 277 278 s := &collaboration.Share{ 279 Id: &collaboration.ShareId{ 280 OpaqueId: id, 281 }, 282 ResourceId: md.Id, 283 Permissions: g.Permissions, 284 Grantee: g.Grantee, 285 Owner: md.Owner, 286 Creator: user.Id, 287 Ctime: ts, 288 Mtime: ts, 289 } 290 291 m.model.Shares = append(m.model.Shares, s) 292 if err := m.model.Save(); err != nil { 293 err = errors.Wrap(err, "error saving model") 294 return nil, err 295 } 296 297 return s, nil 298 } 299 300 // getByID must be called in a lock-controlled block. 301 func (m *mgr) getByID(id *collaboration.ShareId) (int, *collaboration.Share, error) { 302 for i, s := range m.model.Shares { 303 if s.GetId().OpaqueId == id.OpaqueId { 304 return i, s, nil 305 } 306 } 307 return -1, nil, errtypes.NotFound(id.String()) 308 } 309 310 // getByKey must be called in a lock-controlled block. 311 func (m *mgr) getByKey(key *collaboration.ShareKey) (int, *collaboration.Share, error) { 312 for i, s := range m.model.Shares { 313 if (utils.UserEqual(key.Owner, s.Owner) || utils.UserEqual(key.Owner, s.Creator)) && 314 utils.ResourceIDEqual(key.ResourceId, s.ResourceId) && utils.GranteeEqual(key.Grantee, s.Grantee) { 315 return i, s, nil 316 } 317 } 318 return -1, nil, errtypes.NotFound(key.String()) 319 } 320 321 // get must be called in a lock-controlled block. 322 func (m *mgr) get(ref *collaboration.ShareReference) (idx int, s *collaboration.Share, err error) { 323 switch { 324 case ref.GetId() != nil: 325 idx, s, err = m.getByID(ref.GetId()) 326 case ref.GetKey() != nil: 327 idx, s, err = m.getByKey(ref.GetKey()) 328 default: 329 err = errtypes.NotFound(ref.String()) 330 } 331 return 332 } 333 334 func (m *mgr) GetShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.Share, error) { 335 m.Lock() 336 defer m.Unlock() 337 _, s, err := m.get(ref) 338 if err != nil { 339 return nil, err 340 } 341 // check if we are the owner or the grantee 342 user := ctxpkg.ContextMustGetUser(ctx) 343 if share.IsCreatedByUser(s, user) || share.IsGrantedToUser(s, user) { 344 return s, nil 345 } 346 // we return not found to not disclose information 347 return nil, errtypes.NotFound(ref.String()) 348 } 349 350 func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) error { 351 m.Lock() 352 defer m.Unlock() 353 user := ctxpkg.ContextMustGetUser(ctx) 354 355 idx, s, err := m.get(ref) 356 if err != nil { 357 return err 358 } 359 if !share.IsCreatedByUser(s, user) { 360 return errtypes.NotFound(ref.String()) 361 } 362 363 last := len(m.model.Shares) - 1 364 m.model.Shares[idx] = m.model.Shares[last] 365 // explicitly nil the reference to prevent memory leaks 366 // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order 367 m.model.Shares[last] = nil 368 m.model.Shares = m.model.Shares[:last] 369 if err := m.model.Save(); err != nil { 370 err = errors.Wrap(err, "error saving model") 371 return err 372 } 373 return nil 374 } 375 376 func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions, updated *collaboration.Share, fieldMask *field_mask.FieldMask) (*collaboration.Share, error) { 377 m.Lock() 378 defer m.Unlock() 379 380 var ( 381 idx int 382 toUpdate *collaboration.Share 383 ) 384 385 if ref != nil { 386 var err error 387 idx, toUpdate, err = m.get(ref) 388 if err != nil { 389 return nil, err 390 } 391 } else if updated != nil { 392 var err error 393 idx, toUpdate, err = m.getByID(updated.Id) 394 if err != nil { 395 return nil, err 396 } 397 } 398 399 if fieldMask != nil { 400 for i := range fieldMask.Paths { 401 switch fieldMask.Paths[i] { 402 case "permissions": 403 m.model.Shares[idx].Permissions = updated.Permissions 404 case "expiration": 405 m.model.Shares[idx].Expiration = updated.Expiration 406 case "hidden": 407 continue 408 default: 409 return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") 410 } 411 } 412 } 413 414 user := ctxpkg.ContextMustGetUser(ctx) 415 if !share.IsCreatedByUser(toUpdate, user) { 416 return nil, errtypes.NotFound(ref.String()) 417 } 418 419 now := time.Now().UnixNano() 420 if p != nil { 421 m.model.Shares[idx].Permissions = p 422 } 423 m.model.Shares[idx].Mtime = &typespb.Timestamp{ 424 Seconds: uint64(now / int64(time.Second)), 425 Nanos: uint32(now % int64(time.Second)), 426 } 427 428 if err := m.model.Save(); err != nil { 429 err = errors.Wrap(err, "error saving model") 430 return nil, err 431 } 432 return m.model.Shares[idx], nil 433 } 434 435 func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { 436 m.Lock() 437 defer m.Unlock() 438 log := appctx.GetLogger(ctx) 439 user := ctxpkg.ContextMustGetUser(ctx) 440 441 client, err := pool.GetGatewayServiceClient(m.c.GatewayAddr) 442 if err != nil { 443 return nil, errors.Wrap(err, "failed to list shares") 444 } 445 cache := make(map[string]struct{}) 446 var ss []*collaboration.Share 447 for _, s := range m.model.Shares { 448 if share.MatchesFilters(s, filters) { 449 // Only add the share if the share was created by the user or if 450 // the user has ListGrants permissions on the shared resource. 451 // The ListGrants check is necessary when a space member wants 452 // to list shares in a space. 453 // We are using a cache here so that we don't have to stat a 454 // resource multiple times. 455 key := strings.Join([]string{s.ResourceId.StorageId, s.ResourceId.OpaqueId}, "!") 456 if _, hit := cache[key]; !hit && !share.IsCreatedByUser(s, user) { 457 sRes, err := client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: s.ResourceId}}) 458 if err != nil || sRes.Status.Code != rpcv1beta1.Code_CODE_OK { 459 log.Error(). 460 Err(err). 461 Interface("status", sRes.Status). 462 Interface("resource_id", s.ResourceId). 463 Msg("ListShares: could not stat resource") 464 continue 465 } 466 if !sRes.Info.PermissionSet.ListGrants { 467 continue 468 } 469 cache[key] = struct{}{} 470 } 471 ss = append(ss, s) 472 } 473 } 474 return ss, nil 475 } 476 477 // we list the shares that are targeted to the user in context or to the user groups. 478 func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.Filter, forUser *userv1beta1.UserId) ([]*collaboration.ReceivedShare, error) { 479 m.Lock() 480 defer m.Unlock() 481 482 user := ctxpkg.ContextMustGetUser(ctx) 483 if user.GetId().GetType() == userv1beta1.UserType_USER_TYPE_SERVICE { 484 gwc, err := pool.GetGatewayServiceClient(m.c.GatewayAddr) 485 if err != nil { 486 return nil, errors.Wrap(err, "failed to list shares") 487 } 488 u, err := utils.GetUser(forUser, gwc) 489 if err != nil { 490 return nil, errtypes.BadRequest("user not found") 491 } 492 user = u 493 } 494 mem := make(map[string]int) 495 var rss []*collaboration.ReceivedShare 496 for _, s := range m.model.Shares { 497 if !share.IsCreatedByUser(s, user) && 498 share.IsGrantedToUser(s, user) && 499 share.MatchesFilters(s, filters) { 500 501 rs := m.convert(user.Id, s) 502 idx, seen := mem[s.ResourceId.OpaqueId] 503 if !seen { 504 rss = append(rss, rs) 505 mem[s.ResourceId.OpaqueId] = len(rss) - 1 506 continue 507 } 508 509 // When we arrive here there was already a share for this resource. 510 // if there is a mix-up of shares of type group and shares of type user we need to deduplicate them, since it points 511 // to the same resource. Leave the more explicit and hide the less explicit. In this case we hide the group shares 512 // and return the user share to the user. 513 other := rss[idx] 514 if other.Share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP && s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { 515 if other.State == rs.State { 516 rss[idx] = rs 517 } else { 518 rss = append(rss, rs) 519 } 520 } 521 } 522 } 523 524 return rss, nil 525 } 526 527 // convert must be called in a lock-controlled block. 528 func (m *mgr) convert(currentUser *userv1beta1.UserId, s *collaboration.Share) *collaboration.ReceivedShare { 529 rs := &collaboration.ReceivedShare{ 530 Share: s, 531 State: collaboration.ShareState_SHARE_STATE_PENDING, 532 } 533 if v, ok := m.model.State[currentUser.String()]; ok { 534 if state, ok := v[s.Id.String()]; ok { 535 rs.State = state 536 } 537 } 538 if v, ok := m.model.MountPoint[currentUser.String()]; ok { 539 if mp, ok := v[s.Id.String()]; ok { 540 rs.MountPoint = mp 541 } 542 } 543 return rs 544 } 545 546 func (m *mgr) GetReceivedShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.ReceivedShare, error) { 547 return m.getReceived(ctx, ref) 548 } 549 550 func (m *mgr) getReceived(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.ReceivedShare, error) { 551 m.Lock() 552 defer m.Unlock() 553 _, s, err := m.get(ref) 554 if err != nil { 555 return nil, err 556 } 557 user := ctxpkg.ContextMustGetUser(ctx) 558 if user.GetId().GetType() != userv1beta1.UserType_USER_TYPE_SERVICE && !share.IsGrantedToUser(s, user) { 559 return nil, errtypes.NotFound(ref.String()) 560 } 561 return m.convert(user.Id, s), nil 562 } 563 564 func (m *mgr) UpdateReceivedShare(ctx context.Context, receivedShare *collaboration.ReceivedShare, fieldMask *field_mask.FieldMask, forUser *userv1beta1.UserId) (*collaboration.ReceivedShare, error) { 565 rs, err := m.getReceived(ctx, &collaboration.ShareReference{Spec: &collaboration.ShareReference_Id{Id: receivedShare.Share.Id}}) 566 if err != nil { 567 return nil, err 568 } 569 570 m.Lock() 571 defer m.Unlock() 572 573 for i := range fieldMask.Paths { 574 switch fieldMask.Paths[i] { 575 case "state": 576 rs.State = receivedShare.State 577 case "mount_point": 578 rs.MountPoint = receivedShare.MountPoint 579 default: 580 return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") 581 } 582 } 583 584 u := ctxpkg.ContextMustGetUser(ctx) 585 uid := u.GetId().String() 586 if u.GetId().GetType() == userv1beta1.UserType_USER_TYPE_SERVICE { 587 uid = forUser.String() 588 } 589 // Persist state 590 if v, ok := m.model.State[uid]; ok { 591 v[rs.Share.Id.String()] = rs.State 592 m.model.State[uid] = v 593 } else { 594 a := map[string]collaboration.ShareState{ 595 rs.Share.Id.String(): rs.State, 596 } 597 m.model.State[uid] = a 598 } 599 600 // Persist mount point 601 if v, ok := m.model.MountPoint[uid]; ok { 602 v[rs.Share.Id.String()] = rs.MountPoint 603 m.model.MountPoint[uid] = v 604 } else { 605 a := map[string]*provider.Reference{ 606 rs.Share.Id.String(): rs.MountPoint, 607 } 608 m.model.MountPoint[uid] = a 609 } 610 611 if err := m.model.Save(); err != nil { 612 err = errors.Wrap(err, "error saving model") 613 return nil, err 614 } 615 616 return rs, nil 617 }