github.com/cs3org/reva/v2@v2.27.7/internal/grpc/services/publicshareprovider/publicshareprovider.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 publicshareprovider 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "regexp" 26 "strconv" 27 "time" 28 29 gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 30 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 31 link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" 32 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 33 "github.com/cs3org/reva/v2/pkg/password" 34 "github.com/cs3org/reva/v2/pkg/permission" 35 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 36 "github.com/cs3org/reva/v2/pkg/sharedconf" 37 "github.com/cs3org/reva/v2/pkg/storage/utils/grants" 38 "github.com/cs3org/reva/v2/pkg/utils" 39 "github.com/mitchellh/mapstructure" 40 "github.com/pkg/errors" 41 "github.com/rs/zerolog" 42 "google.golang.org/grpc" 43 44 "github.com/cs3org/reva/v2/pkg/appctx" 45 "github.com/cs3org/reva/v2/pkg/conversions" 46 ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" 47 "github.com/cs3org/reva/v2/pkg/errtypes" 48 "github.com/cs3org/reva/v2/pkg/publicshare" 49 "github.com/cs3org/reva/v2/pkg/publicshare/manager/registry" 50 "github.com/cs3org/reva/v2/pkg/rgrpc" 51 "github.com/cs3org/reva/v2/pkg/rgrpc/status" 52 ) 53 54 const getUserCtxErrMsg = "error getting user from context" 55 56 func init() { 57 rgrpc.Register("publicshareprovider", NewDefault) 58 } 59 60 type config struct { 61 Driver string `mapstructure:"driver"` 62 Drivers map[string]map[string]interface{} `mapstructure:"drivers"` 63 GatewayAddr string `mapstructure:"gateway_addr"` 64 AllowedPathsForShares []string `mapstructure:"allowed_paths_for_shares"` 65 EnableExpiredSharesCleanup bool `mapstructure:"enable_expired_shares_cleanup"` 66 WriteableShareMustHavePassword bool `mapstructure:"writeable_share_must_have_password"` 67 PublicShareMustHavePassword bool `mapstructure:"public_share_must_have_password"` 68 PasswordPolicy map[string]interface{} `mapstructure:"password_policy"` 69 } 70 71 type passwordPolicy struct { 72 MinCharacters int `mapstructure:"min_characters"` 73 MinLowerCaseCharacters int `mapstructure:"min_lowercase_characters"` 74 MinUpperCaseCharacters int `mapstructure:"min_uppercase_characters"` 75 MinDigits int `mapstructure:"min_digits"` 76 MinSpecialCharacters int `mapstructure:"min_special_characters"` 77 BannedPasswordsList map[string]struct{} `mapstructure:"banned_passwords_list"` 78 } 79 80 func (c *config) init() { 81 if c.Driver == "" { 82 c.Driver = "json" 83 } 84 } 85 86 type service struct { 87 conf *config 88 sm publicshare.Manager 89 gatewaySelector pool.Selectable[gateway.GatewayAPIClient] 90 allowedPathsForShares []*regexp.Regexp 91 passwordValidator password.Validator 92 } 93 94 func getShareManager(c *config) (publicshare.Manager, error) { 95 if f, ok := registry.NewFuncs[c.Driver]; ok { 96 return f(c.Drivers[c.Driver]) 97 } 98 return nil, errtypes.NotFound("driver not found: " + c.Driver) 99 } 100 101 // TODO(labkode): add ctx to Close. 102 func (s *service) Close() error { 103 return nil 104 } 105 func (s *service) UnprotectedEndpoints() []string { 106 return []string{"/cs3.sharing.link.v1beta1.LinkAPI/GetPublicShareByToken"} 107 } 108 109 func (s *service) Register(ss *grpc.Server) { 110 link.RegisterLinkAPIServer(ss, s) 111 } 112 113 func parseConfig(m map[string]interface{}) (*config, error) { 114 c := &config{} 115 if err := mapstructure.Decode(m, c); err != nil { 116 err = errors.Wrap(err, "error decoding config") 117 return nil, err 118 } 119 return c, nil 120 } 121 122 func parsePasswordPolicy(m map[string]interface{}) (*passwordPolicy, error) { 123 p := &passwordPolicy{} 124 if err := mapstructure.Decode(m, p); err != nil { 125 err = errors.Wrap(err, "error decoding password policy config") 126 return nil, err 127 } 128 return p, nil 129 } 130 131 // New creates a new public share provider svc initialized from defaults 132 func NewDefault(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) { 133 c, err := parseConfig(m) 134 if err != nil { 135 return nil, err 136 } 137 p, err := parsePasswordPolicy(c.PasswordPolicy) 138 if err != nil { 139 return nil, err 140 } 141 142 c.init() 143 144 sm, err := getShareManager(c) 145 if err != nil { 146 return nil, err 147 } 148 149 gatewaySelector, err := pool.GatewaySelector(sharedconf.GetGatewaySVC(c.GatewayAddr)) 150 if err != nil { 151 return nil, err 152 } 153 return New(gatewaySelector, sm, c, p) 154 } 155 156 // New creates a new user share provider svc 157 func New(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], sm publicshare.Manager, c *config, p *passwordPolicy) (rgrpc.Service, error) { 158 allowedPathsForShares := make([]*regexp.Regexp, 0, len(c.AllowedPathsForShares)) 159 for _, s := range c.AllowedPathsForShares { 160 regex, err := regexp.Compile(s) 161 if err != nil { 162 return nil, err 163 } 164 allowedPathsForShares = append(allowedPathsForShares, regex) 165 } 166 167 service := &service{ 168 conf: c, 169 sm: sm, 170 gatewaySelector: gatewaySelector, 171 allowedPathsForShares: allowedPathsForShares, 172 passwordValidator: newPasswordPolicy(p), 173 } 174 175 return service, nil 176 } 177 178 func newPasswordPolicy(c *passwordPolicy) password.Validator { 179 if c == nil { 180 return password.NewPasswordPolicy(0, 0, 0, 0, 0, nil) 181 } 182 return password.NewPasswordPolicy( 183 c.MinCharacters, 184 c.MinLowerCaseCharacters, 185 c.MinUpperCaseCharacters, 186 c.MinDigits, 187 c.MinSpecialCharacters, 188 c.BannedPasswordsList, 189 ) 190 } 191 192 func (s *service) isPathAllowed(path string) bool { 193 if len(s.allowedPathsForShares) == 0 { 194 return true 195 } 196 for _, reg := range s.allowedPathsForShares { 197 if reg.MatchString(path) { 198 return true 199 } 200 } 201 return false 202 } 203 204 func (s *service) CreatePublicShare(ctx context.Context, req *link.CreatePublicShareRequest) (*link.CreatePublicShareResponse, error) { 205 log := appctx.GetLogger(ctx) 206 log.Info().Str("publicshareprovider", "create").Msg("create public share") 207 208 gatewayClient, err := s.gatewaySelector.Next() 209 if err != nil { 210 return nil, err 211 } 212 213 isInternalLink := grants.PermissionsEqual(req.GetGrant().GetPermissions().GetPermissions(), &provider.ResourcePermissions{}) 214 215 sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: req.GetResourceInfo().GetId()}}) 216 if err != nil { 217 log.Err(err).Interface("resource_id", req.GetResourceInfo().GetId()).Msg("failed to stat resource to share") 218 return &link.CreatePublicShareResponse{ 219 Status: status.NewInternal(ctx, "failed to stat resource to share"), 220 }, err 221 } 222 223 // all users can create internal links 224 if !isInternalLink { 225 // check if the user has the permission in the user role 226 ok, err := utils.CheckPermission(ctx, permission.WritePublicLink, gatewayClient) 227 if err != nil { 228 return &link.CreatePublicShareResponse{ 229 Status: status.NewInternal(ctx, "failed check user permission to write public link"), 230 }, err 231 } 232 if !ok { 233 return &link.CreatePublicShareResponse{ 234 Status: status.NewPermissionDenied(ctx, nil, "no permission to create public links"), 235 }, nil 236 } 237 } 238 239 // check that user has share permissions 240 if !isInternalLink && !sRes.GetInfo().GetPermissionSet().AddGrant { 241 return &link.CreatePublicShareResponse{ 242 Status: status.NewInvalidArg(ctx, "no share permission"), 243 }, nil 244 } 245 246 // check if the user can share with the desired permissions. For internal links this is skipped, 247 // users can always create internal links provided they have the AddGrant permission, which was already 248 // checked above 249 if !isInternalLink && !conversions.SufficientCS3Permissions(sRes.GetInfo().GetPermissionSet(), req.GetGrant().GetPermissions().GetPermissions()) { 250 return &link.CreatePublicShareResponse{ 251 Status: status.NewInvalidArg(ctx, "insufficient permissions to create that kind of share"), 252 }, nil 253 } 254 255 // validate path 256 if !s.isPathAllowed(req.GetResourceInfo().GetPath()) { 257 return &link.CreatePublicShareResponse{ 258 Status: status.NewFailedPrecondition(ctx, nil, "share creation is not allowed for the specified path"), 259 }, nil 260 } 261 262 // check that this is a not a personal space root 263 if req.GetResourceInfo().GetId().GetOpaqueId() == req.GetResourceInfo().GetId().GetSpaceId() && 264 req.GetResourceInfo().GetSpace().GetSpaceType() == "personal" { 265 return &link.CreatePublicShareResponse{ 266 Status: status.NewInvalidArg(ctx, "cannot create link on personal space root"), 267 }, nil 268 } 269 270 // quick link returns the existing one if already present 271 quickLink, err := checkQuicklink(req.GetResourceInfo()) 272 if err != nil { 273 return &link.CreatePublicShareResponse{ 274 Status: status.NewInvalidArg(ctx, "invalid quicklink value"), 275 }, nil 276 } 277 if quickLink { 278 f := []*link.ListPublicSharesRequest_Filter{publicshare.ResourceIDFilter(req.GetResourceInfo().GetId())} 279 req := link.ListPublicSharesRequest{Filters: f} 280 res, err := s.ListPublicShares(ctx, &req) 281 if err != nil || res.GetStatus().GetCode() != rpc.Code_CODE_OK { 282 return &link.CreatePublicShareResponse{ 283 Status: status.NewInternal(ctx, "could not list public links"), 284 }, nil 285 } 286 for _, l := range res.GetShare() { 287 if l.Quicklink { 288 return &link.CreatePublicShareResponse{ 289 Status: status.NewOK(ctx), 290 Share: l, 291 }, nil 292 } 293 } 294 } 295 296 grant := req.GetGrant() 297 298 // validate expiration date 299 if grant.GetExpiration() != nil { 300 expirationDateTime := utils.TSToTime(grant.GetExpiration()).UTC() 301 if expirationDateTime.Before(time.Now().UTC()) { 302 msg := fmt.Sprintf("expiration date is in the past: %s", expirationDateTime.Format(time.RFC3339)) 303 return &link.CreatePublicShareResponse{ 304 Status: status.NewInvalidArg(ctx, msg), 305 }, nil 306 } 307 } 308 309 // enforce password if needed 310 setPassword := grant.GetPassword() 311 if !isInternalLink && enforcePassword(false, grant.GetPermissions().GetPermissions(), s.conf) && len(setPassword) == 0 { 312 return &link.CreatePublicShareResponse{ 313 Status: status.NewInvalidArg(ctx, "password protection is enforced"), 314 }, nil 315 } 316 317 // validate password policy 318 if len(setPassword) > 0 { 319 if err := s.passwordValidator.Validate(setPassword); err != nil { 320 return &link.CreatePublicShareResponse{ 321 Status: status.NewInvalidArg(ctx, err.Error()), 322 }, nil 323 } 324 } 325 326 user := ctxpkg.ContextMustGetUser(ctx) 327 res := &link.CreatePublicShareResponse{} 328 share, err := s.sm.CreatePublicShare(ctx, user, req.GetResourceInfo(), req.GetGrant()) 329 switch { 330 case err != nil: 331 log.Error().Err(err).Interface("request", req).Msg("could not write public share") 332 res.Status = status.NewInternal(ctx, "error persisting public share:"+err.Error()) 333 default: 334 res.Status = status.NewOK(ctx) 335 res.Share = share 336 res.Opaque = utils.AppendPlainToOpaque(nil, "resourcename", sRes.GetInfo().GetName()) 337 } 338 339 return res, nil 340 } 341 342 func (s *service) RemovePublicShare(ctx context.Context, req *link.RemovePublicShareRequest) (*link.RemovePublicShareResponse, error) { 343 gatewayClient, err := s.gatewaySelector.Next() 344 if err != nil { 345 return nil, err 346 } 347 348 log := appctx.GetLogger(ctx) 349 log.Info().Str("publicshareprovider", "remove").Msg("remove public share") 350 351 user := ctxpkg.ContextMustGetUser(ctx) 352 ps, err := s.sm.GetPublicShare(ctx, user, req.GetRef(), false) 353 if err != nil { 354 return &link.RemovePublicShareResponse{ 355 Status: status.NewInternal(ctx, "error loading public share"), 356 }, err 357 } 358 359 sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: ps.ResourceId}}) 360 if err != nil { 361 log.Err(err).Interface("resource_id", ps.ResourceId).Msg("failed to stat shared resource") 362 return &link.RemovePublicShareResponse{ 363 Status: status.NewInternal(ctx, "failed to stat shared resource"), 364 }, err 365 } 366 if !publicshare.IsCreatedByUser(ps, user) { 367 if !sRes.GetInfo().GetPermissionSet().RemoveGrant { 368 return &link.RemovePublicShareResponse{ 369 Status: status.NewPermissionDenied(ctx, nil, "no permission to delete public share"), 370 }, err 371 } 372 } 373 err = s.sm.RevokePublicShare(ctx, user, req.Ref) 374 if err != nil { 375 return &link.RemovePublicShareResponse{ 376 Status: status.NewInternal(ctx, "error deleting public share"), 377 }, err 378 } 379 o := utils.AppendJSONToOpaque(nil, "resourceid", ps.GetResourceId()) 380 o = utils.AppendPlainToOpaque(o, "resourcename", sRes.GetInfo().GetName()) 381 return &link.RemovePublicShareResponse{ 382 Opaque: o, 383 Status: status.NewOK(ctx), 384 }, nil 385 } 386 387 func (s *service) GetPublicShareByToken(ctx context.Context, req *link.GetPublicShareByTokenRequest) (*link.GetPublicShareByTokenResponse, error) { 388 log := appctx.GetLogger(ctx) 389 log.Debug().Msg("getting public share by token") 390 391 // there are 2 passes here, and the second request has no password 392 found, err := s.sm.GetPublicShareByToken(ctx, req.GetToken(), req.GetAuthentication(), req.GetSign()) 393 switch v := err.(type) { 394 case nil: 395 return &link.GetPublicShareByTokenResponse{ 396 Status: status.NewOK(ctx), 397 Share: found, 398 }, nil 399 case errtypes.InvalidCredentials: 400 return &link.GetPublicShareByTokenResponse{ 401 Status: status.NewPermissionDenied(ctx, v, "wrong password"), 402 }, nil 403 case errtypes.NotFound: 404 return &link.GetPublicShareByTokenResponse{ 405 Status: status.NewNotFound(ctx, "unknown token"), 406 }, nil 407 default: 408 return &link.GetPublicShareByTokenResponse{ 409 Status: status.NewInternal(ctx, "unexpected error"), 410 }, nil 411 } 412 } 413 414 func (s *service) GetPublicShare(ctx context.Context, req *link.GetPublicShareRequest) (*link.GetPublicShareResponse, error) { 415 log := appctx.GetLogger(ctx) 416 log.Info().Str("publicshareprovider", "get").Msg("get public share") 417 418 u, ok := ctxpkg.ContextGetUser(ctx) 419 if !ok { 420 log.Error().Msg(getUserCtxErrMsg) 421 } 422 423 ps, err := s.sm.GetPublicShare(ctx, u, req.Ref, req.GetSign()) 424 switch { 425 case err != nil: 426 var st *rpc.Status 427 switch err.(type) { 428 case errtypes.IsNotFound: 429 st = status.NewNotFound(ctx, err.Error()) 430 default: 431 st = status.NewInternal(ctx, err.Error()) 432 } 433 return &link.GetPublicShareResponse{ 434 Status: st, 435 }, nil 436 case ps == nil: 437 return &link.GetPublicShareResponse{ 438 Status: status.NewNotFound(ctx, "not found"), 439 }, nil 440 default: 441 return &link.GetPublicShareResponse{ 442 Status: status.NewOK(ctx), 443 Share: ps, 444 }, nil 445 } 446 } 447 448 func (s *service) ListPublicShares(ctx context.Context, req *link.ListPublicSharesRequest) (*link.ListPublicSharesResponse, error) { 449 log := appctx.GetLogger(ctx) 450 log.Info().Str("publicshareprovider", "list").Msg("list public share") 451 user, _ := ctxpkg.ContextGetUser(ctx) 452 453 shares, err := s.sm.ListPublicShares(ctx, user, req.Filters, req.GetSign()) 454 if err != nil { 455 log.Err(err).Msg("error listing shares") 456 return &link.ListPublicSharesResponse{ 457 Status: status.NewInternal(ctx, "error listing public shares"), 458 }, nil 459 } 460 461 res := &link.ListPublicSharesResponse{ 462 Status: status.NewOK(ctx), 463 Share: shares, 464 } 465 return res, nil 466 } 467 468 func (s *service) UpdatePublicShare(ctx context.Context, req *link.UpdatePublicShareRequest) (*link.UpdatePublicShareResponse, error) { 469 log := appctx.GetLogger(ctx) 470 log.Info().Str("publicshareprovider", "update").Msg("update public share") 471 472 gatewayClient, err := s.gatewaySelector.Next() 473 if err != nil { 474 return nil, err 475 } 476 477 user := ctxpkg.ContextMustGetUser(ctx) 478 ps, err := s.sm.GetPublicShare(ctx, user, req.GetRef(), false) 479 if err != nil { 480 return &link.UpdatePublicShareResponse{ 481 Status: status.NewInternal(ctx, "error loading public share"), 482 }, err 483 } 484 485 isInternalLink := isInternalLink(req, ps) 486 487 // check if the user has the permission in the user role 488 if !publicshare.IsCreatedByUser(ps, user) { 489 canWriteLink, err := utils.CheckPermission(ctx, permission.WritePublicLink, gatewayClient) 490 if err != nil { 491 return &link.UpdatePublicShareResponse{ 492 Status: status.NewInternal(ctx, "error checking permission to write public share"), 493 }, err 494 } 495 if !canWriteLink { 496 return &link.UpdatePublicShareResponse{ 497 Status: status.NewPermissionDenied(ctx, nil, "no permission to update public share"), 498 }, nil 499 } 500 } 501 502 sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: ps.ResourceId}}) 503 if err != nil { 504 log.Err(err).Interface("resource_id", ps.ResourceId).Msg("failed to stat shared resource") 505 return &link.UpdatePublicShareResponse{ 506 Status: status.NewInternal(ctx, "failed to stat shared resource"), 507 }, err 508 } 509 if sRes.Status.Code != rpc.Code_CODE_OK { 510 return &link.UpdatePublicShareResponse{ 511 Status: sRes.GetStatus(), 512 }, nil 513 514 } 515 516 if !isInternalLink && !publicshare.IsCreatedByUser(ps, user) { 517 if !sRes.GetInfo().GetPermissionSet().UpdateGrant { 518 return &link.UpdatePublicShareResponse{ 519 Status: status.NewPermissionDenied(ctx, nil, "no permission to update public share"), 520 }, err 521 } 522 } 523 524 // check if the user can change the permissions to the desired permissions 525 updatePermissions := req.GetUpdate().GetType() == link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS 526 if updatePermissions && 527 !isInternalLink && 528 !conversions.SufficientCS3Permissions( 529 sRes.GetInfo().GetPermissionSet(), 530 req.GetUpdate().GetGrant().GetPermissions().GetPermissions(), 531 ) { 532 return &link.UpdatePublicShareResponse{ 533 Status: status.NewInvalidArg(ctx, "insufficient permissions to update that kind of share"), 534 }, nil 535 } 536 if updatePermissions { 537 beforePerm, _ := json.Marshal(sRes.GetInfo().GetPermissionSet()) 538 afterPerm, _ := json.Marshal(req.GetUpdate().GetGrant().GetPermissions()) 539 log.Info(). 540 Str("shares", "update"). 541 Msgf("updating permissions from %v to: %v", 542 string(beforePerm), 543 string(afterPerm), 544 ) 545 } 546 547 grant := req.GetUpdate().GetGrant() 548 549 // validate expiration date 550 if grant.GetExpiration() != nil { 551 expirationDateTime := utils.TSToTime(grant.GetExpiration()).UTC() 552 if expirationDateTime.Before(time.Now().UTC()) { 553 msg := fmt.Sprintf("expiration date is in the past: %s", expirationDateTime.Format(time.RFC3339)) 554 return &link.UpdatePublicShareResponse{ 555 Status: status.NewInvalidArg(ctx, msg), 556 }, nil 557 } 558 } 559 560 // enforce password if needed 561 var canOptOut bool 562 if !isInternalLink { 563 canOptOut, err = utils.CheckPermission(ctx, permission.DeleteReadOnlyPassword, gatewayClient) 564 if err != nil { 565 return &link.UpdatePublicShareResponse{ 566 Status: status.NewInternal(ctx, err.Error()), 567 }, nil 568 } 569 } 570 571 updatePassword := req.GetUpdate().GetType() == link.UpdatePublicShareRequest_Update_TYPE_PASSWORD 572 setPassword := grant.GetPassword() 573 574 // we update permissions with an empty password and password is not set on the public share 575 emptyPasswordInPermissionUpdate := len(setPassword) == 0 && updatePermissions && !ps.PasswordProtected 576 577 // password is updated, we use the current permissions to check if the user can opt out 578 if updatePassword && !isInternalLink && enforcePassword(canOptOut, ps.GetPermissions().GetPermissions(), s.conf) && len(setPassword) == 0 { 579 return &link.UpdatePublicShareResponse{ 580 Status: status.NewInvalidArg(ctx, "password protection is enforced"), 581 }, nil 582 } 583 584 // permissions are updated, we use the new permissions to check if the user can opt out 585 if emptyPasswordInPermissionUpdate && !isInternalLink && enforcePassword(canOptOut, grant.GetPermissions().GetPermissions(), s.conf) && len(setPassword) == 0 { 586 return &link.UpdatePublicShareResponse{ 587 Status: status.NewInvalidArg(ctx, "password protection is enforced"), 588 }, nil 589 } 590 591 // validate password policy 592 if updatePassword && len(setPassword) > 0 { 593 if err := s.passwordValidator.Validate(setPassword); err != nil { 594 return &link.UpdatePublicShareResponse{ 595 Status: status.NewInvalidArg(ctx, err.Error()), 596 }, nil 597 } 598 } 599 600 updateR, err := s.sm.UpdatePublicShare(ctx, user, req) 601 if err != nil { 602 return &link.UpdatePublicShareResponse{ 603 Status: status.NewInternal(ctx, err.Error()), 604 }, nil 605 } 606 607 res := &link.UpdatePublicShareResponse{ 608 Status: status.NewOK(ctx), 609 Share: updateR, 610 Opaque: utils.AppendPlainToOpaque(nil, "resourcename", sRes.GetInfo().GetName()), 611 } 612 return res, nil 613 } 614 615 func isInternalLink(req *link.UpdatePublicShareRequest, ps *link.PublicShare) bool { 616 switch { 617 case req.GetUpdate().GetType() == link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS: 618 return grants.PermissionsEqual(req.GetUpdate().GetGrant().GetPermissions().GetPermissions(), &provider.ResourcePermissions{}) 619 default: 620 return grants.PermissionsEqual(ps.GetPermissions().GetPermissions(), &provider.ResourcePermissions{}) 621 } 622 } 623 624 func enforcePassword(canOptOut bool, permissions *provider.ResourcePermissions, conf *config) bool { 625 isReadOnly := conversions.SufficientCS3Permissions(conversions.NewViewerRole().CS3ResourcePermissions(), permissions) 626 if isReadOnly && canOptOut { 627 return false 628 } 629 630 if conf.PublicShareMustHavePassword { 631 return true 632 } 633 634 return !isReadOnly && conf.WriteableShareMustHavePassword 635 } 636 637 func checkQuicklink(info *provider.ResourceInfo) (bool, error) { 638 if info == nil { 639 return false, nil 640 } 641 if m := info.GetArbitraryMetadata().GetMetadata(); m != nil { 642 q, ok := m["quicklink"] 643 // empty string would trigger an error in ParseBool() 644 if !ok || q == "" { 645 return false, nil 646 } 647 quickLink, err := strconv.ParseBool(q) 648 if err != nil { 649 return false, err 650 } 651 return quickLink, nil 652 } 653 return false, nil 654 }