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  }