storj/cmd/uplink/access_permissions.go
Jeff Wendling f2fdd6ca33 cmd/uplink: fix some issues with share
Because --readonly is default true, passing something like
--disallow-deletes=false would not actually update that
value because the readonly flag would override. this makes it
so that the --disallow-* flags override the --readonly and
--writeonly flags.

Also fixes some minor formatting issues with share like an
extra space after the "Public Access:" entry.

Simplifies the handling of the explicit "none" by making the
flags for the dates optional and using nil to signify that
the value was left unset.

Bump the go.mod to go1.18 to enable the use of generics and
add a small generic function. This can easily be backed out
if it causes problems.

Change-Id: I1c5f1321ad17b8ace778ce55561cbbfc24321a68
2022-12-08 17:46:02 +00:00

129 lines
4.3 KiB
Go

// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"strconv"
"time"
"github.com/zeebo/clingy"
"github.com/zeebo/errs"
"storj.io/storj/cmd/uplink/ulloc"
"storj.io/uplink"
)
// accessPermissions holds flags and provides a Setup method for commands that
// have to modify permissions on access grants.
type accessPermissions struct {
prefixes []uplink.SharePrefix // prefixes is the set of path prefixes that the grant will be limited to
readonly bool
writeonly bool
disallowDeletes *bool
disallowLists *bool
disallowReads *bool
disallowWrites *bool
notBefore *time.Time
notAfter *time.Time
}
func (ap *accessPermissions) Setup(params clingy.Parameters, prefixFlags bool) {
if prefixFlags {
ap.prefixes = params.Flag("prefix", "Key prefix access will be restricted to", []uplink.SharePrefix{},
clingy.Transform(ulloc.Parse),
clingy.Transform(transformSharePrefix),
clingy.Repeated,
).([]uplink.SharePrefix)
}
ap.readonly = params.Flag("readonly", "Implies --disallow-writes and --disallow-deletes", true,
clingy.Transform(strconv.ParseBool), clingy.Boolean).(bool)
ap.writeonly = params.Flag("writeonly", "Implies --disallow-reads and --disallow-lists", false,
clingy.Transform(strconv.ParseBool), clingy.Boolean).(bool)
params.Break()
ap.disallowDeletes = params.Flag("disallow-deletes", "Disallow deletes with the access", nil,
clingy.Transform(strconv.ParseBool), clingy.Boolean, clingy.Optional).(*bool)
ap.disallowLists = params.Flag("disallow-lists", "Disallow lists with the access", nil,
clingy.Transform(strconv.ParseBool), clingy.Boolean, clingy.Optional).(*bool)
ap.disallowReads = params.Flag("disallow-reads", "Disallow reads with the access", nil,
clingy.Transform(strconv.ParseBool), clingy.Boolean, clingy.Optional).(*bool)
ap.disallowWrites = params.Flag("disallow-writes", "Disallow writes with the access", nil,
clingy.Transform(strconv.ParseBool), clingy.Boolean, clingy.Optional).(*bool)
params.Break()
ap.notBefore = params.Flag("not-before",
"Disallow access before this time (e.g. '+2h', 'now', '2020-01-02T15:04:05Z0700', 'none')",
nil, clingy.Transform(parseHumanDate), clingy.Type("relative_date"), clingy.Optional).(*time.Time)
ap.notAfter = params.Flag("not-after",
"Disallow access after this time (e.g. '+2h', 'now', '2020-01-02T15:04:05Z0700', 'none')",
nil, clingy.Transform(parseHumanDate), clingy.Type("relative_date"), clingy.Optional).(*time.Time)
if !prefixFlags {
ap.prefixes = params.Arg("prefix", "Key prefix access will be restricted to",
clingy.Transform(ulloc.Parse),
clingy.Transform(transformSharePrefix),
clingy.Repeated,
).([]uplink.SharePrefix)
}
}
func transformSharePrefix(loc ulloc.Location) (uplink.SharePrefix, error) {
bucket, key, ok := loc.RemoteParts()
if !ok {
return uplink.SharePrefix{}, errs.New("invalid prefix: must be remote: %q", loc)
}
return uplink.SharePrefix{
Bucket: bucket,
Prefix: key,
}, nil
}
func (ap *accessPermissions) Apply(access *uplink.Access) (*uplink.Access, error) {
permission := uplink.Permission{
AllowDelete: ap.AllowDelete(),
AllowList: ap.AllowList(),
AllowDownload: ap.AllowDownload(),
AllowUpload: ap.AllowUpload(),
NotBefore: ap.NotBefore(),
NotAfter: ap.NotAfter(),
}
// if we aren't actually restricting anything, then we don't need to Share.
if permission == (uplink.Permission{
AllowDelete: true,
AllowList: true,
AllowDownload: true,
AllowUpload: true,
}) && len(ap.prefixes) == 0 {
return access, nil
}
access, err := access.Share(permission, ap.prefixes...)
if err != nil {
return nil, errs.Wrap(err)
}
return access, nil
}
func defaulted[T any](val *T, def T) T {
if val != nil {
return *val
}
return def
}
func (ap *accessPermissions) NotBefore() time.Time { return defaulted(ap.notBefore, time.Time{}) }
func (ap *accessPermissions) NotAfter() time.Time { return defaulted(ap.notAfter, time.Time{}) }
func (ap *accessPermissions) AllowDelete() bool { return !defaulted(ap.disallowDeletes, ap.readonly) }
func (ap *accessPermissions) AllowList() bool { return !defaulted(ap.disallowLists, ap.writeonly) }
func (ap *accessPermissions) AllowDownload() bool { return !defaulted(ap.disallowReads, ap.writeonly) }
func (ap *accessPermissions) AllowUpload() bool { return !defaulted(ap.disallowWrites, ap.readonly) }