Merge pull request #7877 from owncloud/public-provider-pw
[full-ci] Update reva to move validation to the public share provider
This commit is contained in:
+7
-6
@@ -1065,11 +1065,11 @@ def uiTestPipeline(ctx, filterTags, runPart = 1, numberOfParts = 1, storage = "o
|
|||||||
|
|
||||||
extra_server_environment = {
|
extra_server_environment = {
|
||||||
"OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD": False,
|
"OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD": False,
|
||||||
"FRONTEND_PASSWORD_POLICY_MIN_CHARACTERS": 1,
|
"OCIS_PASSWORD_POLICY_MIN_CHARACTERS": 1,
|
||||||
"FRONTEND_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS": 0,
|
"OCIS_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS": 0,
|
||||||
"FRONTEND_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS": 0,
|
"OCIS_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS": 0,
|
||||||
"FRONTEND_PASSWORD_POLICY_MIN_DIGITS": 0,
|
"OCIS_PASSWORD_POLICY_MIN_DIGITS": 0,
|
||||||
"FRONTEND_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS": 0,
|
"OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -1137,7 +1137,7 @@ def e2eTests(ctx):
|
|||||||
}
|
}
|
||||||
|
|
||||||
extra_server_environment = {
|
extra_server_environment = {
|
||||||
"FRONTEND_PASSWORD_POLICY_BANNED_PASSWORDS_LIST": "%s" % dirs["bannedPasswordList"],
|
"OCIS_PASSWORD_POLICY_BANNED_PASSWORDS_LIST": "%s" % dirs["bannedPasswordList"],
|
||||||
}
|
}
|
||||||
|
|
||||||
e2e_trigger = {
|
e2e_trigger = {
|
||||||
@@ -1929,6 +1929,7 @@ def ocisServer(storage, accounts_hash_difficulty = 4, volumes = [], depends_on =
|
|||||||
|
|
||||||
if deploy_type == "cs3api_validator":
|
if deploy_type == "cs3api_validator":
|
||||||
environment["GATEWAY_GRPC_ADDR"] = "0.0.0.0:9142" # make gateway available to cs3api-validator
|
environment["GATEWAY_GRPC_ADDR"] = "0.0.0.0:9142" # make gateway available to cs3api-validator
|
||||||
|
environment["OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD"] = False
|
||||||
|
|
||||||
if deploy_type == "wopi_validator":
|
if deploy_type == "wopi_validator":
|
||||||
environment["GATEWAY_GRPC_ADDR"] = "0.0.0.0:9142" # make gateway available to wopi server
|
environment["GATEWAY_GRPC_ADDR"] = "0.0.0.0:9142" # make gateway available to wopi server
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Enhancement: Add validation to public share provider
|
||||||
|
|
||||||
|
We changed the implementation of the public share provider in reva to do the validation on the CS3 Api side. This makes the implementation on the graph side smaller.
|
||||||
|
|
||||||
|
https://github.com/owncloud/ocis/pull/7877
|
||||||
|
https://github.com/owncloud/ocis/issues/6993
|
||||||
@@ -13,7 +13,7 @@ require (
|
|||||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||||
github.com/coreos/go-oidc/v3 v3.8.0
|
github.com/coreos/go-oidc/v3 v3.8.0
|
||||||
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781
|
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781
|
||||||
github.com/cs3org/reva/v2 v2.16.1-0.20231201122033-a389ddc645c4
|
github.com/cs3org/reva/v2 v2.16.1-0.20231206110211-7198abf507f6
|
||||||
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25
|
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
|
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
|
||||||
|
|||||||
@@ -1017,8 +1017,8 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
|
|||||||
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
|
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
|
||||||
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY=
|
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY=
|
||||||
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
|
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
|
||||||
github.com/cs3org/reva/v2 v2.16.1-0.20231201122033-a389ddc645c4 h1:61AwMfov2OxrUElWXXKHZfBsuxgNIVwZVQW4PlJoqnM=
|
github.com/cs3org/reva/v2 v2.16.1-0.20231206110211-7198abf507f6 h1:wR1XlTT8ilWd5Yd943yayvRPyz1GBaAt7vZ9SOxHzqI=
|
||||||
github.com/cs3org/reva/v2 v2.16.1-0.20231201122033-a389ddc645c4/go.mod h1:zcrrYVsBv/DwhpyO2/W5hoSZ/k6az6Z2EYQok65uqZY=
|
github.com/cs3org/reva/v2 v2.16.1-0.20231206110211-7198abf507f6/go.mod h1:zcrrYVsBv/DwhpyO2/W5hoSZ/k6az6Z2EYQok65uqZY=
|
||||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -1682,6 +1682,8 @@ github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b h1:Q53idHrTuQD
|
|||||||
github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b/go.mod h1:KirJrATYGbTyUwVR26xIkaipRqRcMRXBf8N5dacvGus=
|
github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b/go.mod h1:KirJrATYGbTyUwVR26xIkaipRqRcMRXBf8N5dacvGus=
|
||||||
github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 h1:Z/i1e+gTZrmcGeZyWckaLfucYG6KYOXLWo4co8pZYNY=
|
github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 h1:Z/i1e+gTZrmcGeZyWckaLfucYG6KYOXLWo4co8pZYNY=
|
||||||
github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103/go.mod h1:o9YPB5aGP8ob35Vy6+vyq3P3bWe7NQWzf+JLiXCiMaE=
|
github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103/go.mod h1:o9YPB5aGP8ob35Vy6+vyq3P3bWe7NQWzf+JLiXCiMaE=
|
||||||
|
github.com/micbar/reva/v2 v2.0.0-20231206102158-0fd5eba44b02 h1:J28bUNh/2K4OUFJdWoCt6zGQF2QALu4K/r6H3bTYodQ=
|
||||||
|
github.com/micbar/reva/v2 v2.0.0-20231206102158-0fd5eba44b02/go.mod h1:zcrrYVsBv/DwhpyO2/W5hoSZ/k6az6Z2EYQok65uqZY=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
|
|||||||
@@ -179,10 +179,10 @@ type ServiceAccount struct {
|
|||||||
|
|
||||||
// PasswordPolicy configures reva password policy
|
// PasswordPolicy configures reva password policy
|
||||||
type PasswordPolicy struct {
|
type PasswordPolicy struct {
|
||||||
MinCharacters int `yaml:"min_characters,omitempty" env:"FRONTEND_PASSWORD_POLICY_MIN_CHARACTERS" desc:"Define the minimum password length. Defaults to 0 if not set."`
|
MinCharacters int `yaml:"min_characters,omitempty" env:"OCIS_PASSWORD_POLICY_MIN_CHARACTERS;FRONTEND_PASSWORD_POLICY_MIN_CHARACTERS" desc:"Define the minimum password length. Defaults to 0 if not set."`
|
||||||
MinLowerCaseCharacters int `yaml:"min_lowercase_characters" env:"FRONTEND_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS" desc:"Define the minimum number of uppercase letters. Defaults to 0 if not set."`
|
MinLowerCaseCharacters int `yaml:"min_lowercase_characters" env:"OCIS_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS;FRONTEND_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS" desc:"Define the minimum number of uppercase letters. Defaults to 0 if not set."`
|
||||||
MinUpperCaseCharacters int `yaml:"min_uppercase_characters" env:"FRONTEND_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS" desc:"Define the minimum number of lowercase letters. Defaults to 0 if not set."`
|
MinUpperCaseCharacters int `yaml:"min_uppercase_characters" env:"OCIS_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS;FRONTEND_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS" desc:"Define the minimum number of lowercase letters. Defaults to 0 if not set."`
|
||||||
MinDigits int `yaml:"min_digits" env:"FRONTEND_PASSWORD_POLICY_MIN_DIGITS" desc:"Define the minimum number of digits. Defaults to 0 if not set."`
|
MinDigits int `yaml:"min_digits" env:"OCIS_PASSWORD_POLICY_MIN_DIGITS;FRONTEND_PASSWORD_POLICY_MIN_DIGITS" desc:"Define the minimum number of digits. Defaults to 0 if not set."`
|
||||||
MinSpecialCharacters int `yaml:"min_special_characters" env:"FRONTEND_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS" desc:"Define the minimum number of characters from the special characters list to be present. Defaults to 0 if not set."`
|
MinSpecialCharacters int `yaml:"min_special_characters" env:"OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS;FRONTEND_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS" desc:"Define the minimum number of characters from the special characters list to be present. Defaults to 0 if not set."`
|
||||||
BannedPasswordsList string `yaml:"banned_passwords_list" env:"FRONTEND_PASSWORD_POLICY_BANNED_PASSWORDS_LIST" desc:"Path to the 'banned passwords list' file. See the documentation for more details."`
|
BannedPasswordsList string `yaml:"banned_passwords_list" env:"OCIS_PASSWORD_POLICY_BANNED_PASSWORDS_LIST;FRONTEND_PASSWORD_POLICY_BANNED_PASSWORDS_LIST" desc:"Path to the 'banned passwords list' file. See the documentation for more details."`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -618,10 +618,9 @@ func (g Graph) DeletePermission(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
permissionID, err := url.PathUnescape(chi.URLParam(r, "permissionID"))
|
permissionID, err := url.PathUnescape(chi.URLParam(r, "permissionID"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Debug().Err(err).Msg("could not parse driveID")
|
g.logger.Debug().Err(err).Msg("could not parse permissionID")
|
||||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid driveID")
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid permissionID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,7 +631,7 @@ func (g Graph) DeletePermission(w http.ResponseWriter, r *http.Request) {
|
|||||||
sharedResourceId, err := g.getUserPermissionResourceID(ctx, permissionID)
|
sharedResourceId, err := g.getUserPermissionResourceID(ctx, permissionID)
|
||||||
var errcode *errorcode.Error
|
var errcode *errorcode.Error
|
||||||
if err != nil && errors.As(err, &errcode) && errcode.GetCode() == errorcode.ItemNotFound {
|
if err != nil && errors.As(err, &errcode) && errcode.GetCode() == errorcode.ItemNotFound {
|
||||||
// there is no user share with that ID, so lets check if it is refering to a public link
|
// there is no user share with that ID, so lets check if it is referring to a public link
|
||||||
isUserPermission = false
|
isUserPermission = false
|
||||||
sharedResourceId, err = g.getLinkPermissionResourceID(ctx, permissionID)
|
sharedResourceId, err = g.getLinkPermissionResourceID(ctx, permissionID)
|
||||||
}
|
}
|
||||||
@@ -644,9 +643,7 @@ func (g Graph) DeletePermission(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// The resourceID of the shared resource need to match the item ID from the Request Path
|
// The resourceID of the shared resource need to match the item ID from the Request Path
|
||||||
// otherwise this is an invalid Request.
|
// otherwise this is an invalid Request.
|
||||||
if sharedResourceId.GetStorageId() != itemID.GetStorageId() ||
|
if !utils.ResourceIDEqual(sharedResourceId, &itemID) {
|
||||||
sharedResourceId.GetSpaceId() != itemID.GetSpaceId() ||
|
|
||||||
sharedResourceId.GetOpaqueId() != itemID.GetOpaqueId() {
|
|
||||||
g.logger.Debug().Msg("resourceID of shared does not match itemID")
|
g.logger.Debug().Msg("resourceID of shared does not match itemID")
|
||||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "permissionID and itemID do not match")
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "permissionID and itemID do not match")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||||
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||||
|
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||||
|
|
||||||
"github.com/cs3org/reva/v2/pkg/utils"
|
|
||||||
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
|
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
|
||||||
"github.com/owncloud/ocis/v2/services/graph/pkg/linktype"
|
"github.com/owncloud/ocis/v2/services/graph/pkg/linktype"
|
||||||
)
|
)
|
||||||
@@ -94,7 +94,12 @@ func (g Graph) createLink(ctx context.Context, driveItemID *providerv1beta1.Reso
|
|||||||
}
|
}
|
||||||
expirationDate, isSet := createLink.GetExpirationDateTimeOk()
|
expirationDate, isSet := createLink.GetExpirationDateTimeOk()
|
||||||
if isSet {
|
if isSet {
|
||||||
req.GetGrant().Expiration = utils.TimeToTS(*expirationDate)
|
expireTime := parseAndFillUpTime(expirationDate)
|
||||||
|
if expireTime == nil {
|
||||||
|
g.logger.Debug().Interface("createLink", createLink).Msg(err.Error())
|
||||||
|
return nil, errorcode.New(errorcode.InvalidRequest, "invalid expiration date")
|
||||||
|
}
|
||||||
|
req.GetGrant().Expiration = expireTime
|
||||||
}
|
}
|
||||||
|
|
||||||
// set displayname and password protected as arbitrary metadata
|
// set displayname and password protected as arbitrary metadata
|
||||||
@@ -139,7 +144,24 @@ func (g Graph) libreGraphPermissionFromCS3PublicShare(createdLink *link.PublicSh
|
|||||||
|
|
||||||
// set expiration date
|
// set expiration date
|
||||||
if createdLink.GetExpiration() != nil {
|
if createdLink.GetExpiration() != nil {
|
||||||
perm.SetExpirationDateTime(cs3TimestampToTime(createdLink.GetExpiration()))
|
perm.SetExpirationDateTime(cs3TimestampToTime(createdLink.GetExpiration()).UTC())
|
||||||
}
|
}
|
||||||
return perm, nil
|
return perm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseAndFillUpTime(t *time.Time) *types.Timestamp {
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// the link needs to be valid for the whole day
|
||||||
|
tLink := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
||||||
|
tLink = tLink.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
|
||||||
|
|
||||||
|
final := tLink.UnixNano()
|
||||||
|
|
||||||
|
return &types.Timestamp{
|
||||||
|
Seconds: uint64(final / 1000000000),
|
||||||
|
Nanos: uint32(final % 1000000000),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -219,6 +219,10 @@ func cs3StatusToErrCode(code rpc.Code) (errcode errorcode.ErrorCode) {
|
|||||||
errcode = errorcode.ItemNotFound
|
errcode = errorcode.ItemNotFound
|
||||||
case rpc.Code_CODE_LOCKED:
|
case rpc.Code_CODE_LOCKED:
|
||||||
errcode = errorcode.ItemIsLocked
|
errcode = errorcode.ItemIsLocked
|
||||||
|
case rpc.Code_CODE_INVALID_ARGUMENT:
|
||||||
|
errcode = errorcode.InvalidRequest
|
||||||
|
case rpc.Code_CODE_FAILED_PRECONDITION:
|
||||||
|
errcode = errorcode.InvalidRequest
|
||||||
default:
|
default:
|
||||||
errcode = errorcode.GeneralException
|
errcode = errorcode.GeneralException
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ func Server(cfg *config.Config) *cli.Command {
|
|||||||
|
|
||||||
gr.Add(func() error {
|
gr.Add(func() error {
|
||||||
pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid")
|
pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid")
|
||||||
rCfg := revaconfig.SharingConfigFromStruct(cfg)
|
rCfg, err := revaconfig.SharingConfigFromStruct(cfg, logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
reg := registry.GetRegistry()
|
reg := registry.GetRegistry()
|
||||||
|
|
||||||
runtime.RunWithOptions(rCfg, pidFile,
|
runtime.RunWithOptions(rCfg, pidFile,
|
||||||
|
|||||||
@@ -26,9 +26,12 @@ type Config struct {
|
|||||||
PublicSharingDriver string `yaml:"public_sharing_driver" env:"SHARING_PUBLIC_DRIVER" desc:"Driver to be used to persist public shares. Supported values are 'jsoncs3', 'json' and 'cs3'."`
|
PublicSharingDriver string `yaml:"public_sharing_driver" env:"SHARING_PUBLIC_DRIVER" desc:"Driver to be used to persist public shares. Supported values are 'jsoncs3', 'json' and 'cs3'."`
|
||||||
PublicSharingDrivers PublicSharingDrivers `yaml:"public_sharing_drivers"`
|
PublicSharingDrivers PublicSharingDrivers `yaml:"public_sharing_drivers"`
|
||||||
WriteableShareMustHavePassword bool `yaml:"public_sharing_writeableshare_must_have_password" env:"OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD;SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords on Uploader, Editor or Contributor shares. If not using the global OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD, you must define the FRONTEND_OCS_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD in the frontend service."`
|
WriteableShareMustHavePassword bool `yaml:"public_sharing_writeableshare_must_have_password" env:"OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD;SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords on Uploader, Editor or Contributor shares. If not using the global OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD, you must define the FRONTEND_OCS_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD in the frontend service."`
|
||||||
|
PublicShareMustHavePassword bool `yaml:"public_sharing_share_must_have_password" env:"OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD;SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords on all public shares."`
|
||||||
EnableExpiredSharesCleanup bool `yaml:"enable_expired_shares_cleanup"`
|
EnableExpiredSharesCleanup bool `yaml:"enable_expired_shares_cleanup"`
|
||||||
Supervised bool `yaml:"-"`
|
Supervised bool `yaml:"-"`
|
||||||
Context context.Context `yaml:"-"`
|
Context context.Context `yaml:"-"`
|
||||||
|
|
||||||
|
PasswordPolicy PasswordPolicy `yaml:"password_policy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Log struct {
|
type Log struct {
|
||||||
@@ -151,3 +154,13 @@ type Events struct {
|
|||||||
TLSRootCaCertPath string `yaml:"tls_root_ca_cert_path" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;SHARING_EVENTS_TLS_ROOT_CA_CERTIFICATE;SHARING_EVENTS_TLS_ROOT_CA_CERT" desc:"The root CA certificate used to validate the server's TLS certificate. If provided SHARING_EVENTS_TLS_INSECURE will be seen as false." deprecationVersion:"4.0.3" removalVersion:"5.0.0" deprecationInfo:"SHARING_EVENTS_TLS_ROOT_CA_CERT changing name for consistency" deprecationReplacement:"SHARING_EVENTS_TLS_ROOT_CA_CERTIFICATE"`
|
TLSRootCaCertPath string `yaml:"tls_root_ca_cert_path" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;SHARING_EVENTS_TLS_ROOT_CA_CERTIFICATE;SHARING_EVENTS_TLS_ROOT_CA_CERT" desc:"The root CA certificate used to validate the server's TLS certificate. If provided SHARING_EVENTS_TLS_INSECURE will be seen as false." deprecationVersion:"4.0.3" removalVersion:"5.0.0" deprecationInfo:"SHARING_EVENTS_TLS_ROOT_CA_CERT changing name for consistency" deprecationReplacement:"SHARING_EVENTS_TLS_ROOT_CA_CERTIFICATE"`
|
||||||
EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;SHARING_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."`
|
EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;SHARING_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PasswordPolicy configures reva password policy
|
||||||
|
type PasswordPolicy struct {
|
||||||
|
MinCharacters int `yaml:"min_characters,omitempty" env:"OCIS_PASSWORD_POLICY_MIN_CHARACTERS;SHARING_PASSWORD_POLICY_MIN_CHARACTERS" desc:"Define the minimum password length. Defaults to 0 if not set."`
|
||||||
|
MinLowerCaseCharacters int `yaml:"min_lowercase_characters" env:"OCIS_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS;SHARING_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS" desc:"Define the minimum number of uppercase letters. Defaults to 0 if not set."`
|
||||||
|
MinUpperCaseCharacters int `yaml:"min_uppercase_characters" env:"OCIS_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS;SHARING_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS" desc:"Define the minimum number of lowercase letters. Defaults to 0 if not set."`
|
||||||
|
MinDigits int `yaml:"min_digits" env:"OCIS_PASSWORD_POLICY_MIN_DIGITS;SHARING_PASSWORD_POLICY_MIN_DIGITS" desc:"Define the minimum number of digits. Defaults to 0 if not set."`
|
||||||
|
MinSpecialCharacters int `yaml:"min_special_characters" env:"OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS;SHARING_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS" desc:"Define the minimum number of characters from the special characters list to be present. Defaults to 0 if not set."`
|
||||||
|
BannedPasswordsList string `yaml:"banned_passwords_list" env:"OCIS_PASSWORD_POLICY_BANNED_PASSWORDS_LIST;SHARING_PASSWORD_POLICY_BANNED_PASSWORDS_LIST" desc:"Path to the 'banned passwords list' file. See the documentation for more details."`
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,7 +75,15 @@ func DefaultConfig() *config.Config {
|
|||||||
ClusterID: "ocis-cluster",
|
ClusterID: "ocis-cluster",
|
||||||
EnableTLS: false,
|
EnableTLS: false,
|
||||||
},
|
},
|
||||||
EnableExpiredSharesCleanup: true,
|
EnableExpiredSharesCleanup: true,
|
||||||
|
PublicShareMustHavePassword: true,
|
||||||
|
PasswordPolicy: config.PasswordPolicy{
|
||||||
|
MinCharacters: 8,
|
||||||
|
MinLowerCaseCharacters: 1,
|
||||||
|
MinUpperCaseCharacters: 1,
|
||||||
|
MinDigits: 1,
|
||||||
|
MinSpecialCharacters: 1,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,29 @@
|
|||||||
package revaconfig
|
package revaconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||||
|
|
||||||
|
"github.com/owncloud/ocis/v2/ocis-pkg/config/defaults"
|
||||||
"github.com/owncloud/ocis/v2/services/sharing/pkg/config"
|
"github.com/owncloud/ocis/v2/services/sharing/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SharingConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service.
|
// SharingConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service.
|
||||||
func SharingConfigFromStruct(cfg *config.Config) map[string]interface{} {
|
func SharingConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string]interface{}, error) {
|
||||||
|
var bannedPasswordsList map[string]struct{}
|
||||||
|
var err error
|
||||||
|
if cfg.PasswordPolicy.BannedPasswordsList != "" {
|
||||||
|
bannedPasswordsList, err = readMultilineFile(cfg.PasswordPolicy.BannedPasswordsList)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to load the banned passwords from a file %s: %w", cfg.PasswordPolicy.BannedPasswordsList, err)
|
||||||
|
logger.Err(err).Send()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
rcfg := map[string]interface{}{
|
rcfg := map[string]interface{}{
|
||||||
"shared": map[string]interface{}{
|
"shared": map[string]interface{}{
|
||||||
"jwt_secret": cfg.TokenManager.JWTSecret,
|
"jwt_secret": cfg.TokenManager.JWTSecret,
|
||||||
@@ -73,6 +91,17 @@ func SharingConfigFromStruct(cfg *config.Config) map[string]interface{} {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"publicshareprovider": map[string]interface{}{
|
"publicshareprovider": map[string]interface{}{
|
||||||
|
"gateway_addr": cfg.Reva.Address,
|
||||||
|
"writeable_share_must_have_password": cfg.WriteableShareMustHavePassword,
|
||||||
|
"public_share_must_have_password": cfg.PublicShareMustHavePassword,
|
||||||
|
"password_policy": map[string]interface{}{
|
||||||
|
"min_digits": cfg.PasswordPolicy.MinDigits,
|
||||||
|
"min_characters": cfg.PasswordPolicy.MinCharacters,
|
||||||
|
"min_lowercase_characters": cfg.PasswordPolicy.MinLowerCaseCharacters,
|
||||||
|
"min_uppercase_characters": cfg.PasswordPolicy.MinUpperCaseCharacters,
|
||||||
|
"min_special_characters": cfg.PasswordPolicy.MinSpecialCharacters,
|
||||||
|
"banned_passwords_list": bannedPasswordsList,
|
||||||
|
},
|
||||||
"driver": cfg.PublicSharingDriver,
|
"driver": cfg.PublicSharingDriver,
|
||||||
"drivers": map[string]interface{}{
|
"drivers": map[string]interface{}{
|
||||||
"json": map[string]interface{}{
|
"json": map[string]interface{}{
|
||||||
@@ -97,13 +126,12 @@ func SharingConfigFromStruct(cfg *config.Config) map[string]interface{} {
|
|||||||
"machine_auth_apikey": cfg.PublicSharingDrivers.CS3.SystemUserAPIKey,
|
"machine_auth_apikey": cfg.PublicSharingDrivers.CS3.SystemUserAPIKey,
|
||||||
},
|
},
|
||||||
"jsoncs3": map[string]interface{}{
|
"jsoncs3": map[string]interface{}{
|
||||||
"gateway_addr": cfg.Reva.Address,
|
"gateway_addr": cfg.Reva.Address,
|
||||||
"provider_addr": cfg.PublicSharingDrivers.JSONCS3.ProviderAddr,
|
"provider_addr": cfg.PublicSharingDrivers.JSONCS3.ProviderAddr,
|
||||||
"service_user_id": cfg.PublicSharingDrivers.JSONCS3.SystemUserID,
|
"service_user_id": cfg.PublicSharingDrivers.JSONCS3.SystemUserID,
|
||||||
"service_user_idp": cfg.PublicSharingDrivers.JSONCS3.SystemUserIDP,
|
"service_user_idp": cfg.PublicSharingDrivers.JSONCS3.SystemUserIDP,
|
||||||
"machine_auth_apikey": cfg.PublicSharingDrivers.JSONCS3.SystemUserAPIKey,
|
"machine_auth_apikey": cfg.PublicSharingDrivers.JSONCS3.SystemUserAPIKey,
|
||||||
"writeable_share_must_have_password": cfg.WriteableShareMustHavePassword,
|
"enable_expired_shares_cleanup": cfg.EnableExpiredSharesCleanup,
|
||||||
"enable_expired_shares_cleanup": cfg.EnableExpiredSharesCleanup,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -126,5 +154,33 @@ func SharingConfigFromStruct(cfg *config.Config) map[string]interface{} {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return rcfg
|
return rcfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readMultilineFile(path string) (map[string]struct{}, error) {
|
||||||
|
if !fileExists(path) {
|
||||||
|
path = filepath.Join(defaults.BaseConfigPath(), path)
|
||||||
|
}
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
data := make(map[string]struct{})
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if line != "" {
|
||||||
|
data[line] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileExists(path string) bool {
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !info.IsDir()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ Feature: enforce password on public link
|
|||||||
|
|
||||||
Password requirements. set by default:
|
Password requirements. set by default:
|
||||||
| OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | true |
|
| OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | true |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_CHARACTERS | 8 |
|
| OCIS_PASSWORD_POLICY_MIN_CHARACTERS | 8 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 1 |
|
| OCIS_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 1 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 1 |
|
| OCIS_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 1 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_DIGITS | 1 |
|
| OCIS_PASSWORD_POLICY_MIN_DIGITS | 1 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 1 |
|
| OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 1 |
|
||||||
|
|
||||||
|
|
||||||
Scenario Outline: create a public link with edit permission without a password when enforce-password is enabled
|
Scenario Outline: create a public link with edit permission without a password when enforce-password is enabled
|
||||||
@@ -82,11 +82,11 @@ Feature: enforce password on public link
|
|||||||
Given the following configs have been set:
|
Given the following configs have been set:
|
||||||
| config | value |
|
| config | value |
|
||||||
| OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
|
| OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
|
| OCIS_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
|
| OCIS_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
|
| OCIS_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_DIGITS | 2 |
|
| OCIS_PASSWORD_POLICY_MIN_DIGITS | 2 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
|
| OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
|
||||||
And user "Alice" has been created with default attributes and without skeleton files
|
And user "Alice" has been created with default attributes and without skeleton files
|
||||||
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
|
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
|
||||||
And using OCS API version "<ocs-api-version>"
|
And using OCS API version "<ocs-api-version>"
|
||||||
@@ -109,11 +109,11 @@ Feature: enforce password on public link
|
|||||||
Scenario Outline: try to create a public link with a password that does not comply with the password policy
|
Scenario Outline: try to create a public link with a password that does not comply with the password policy
|
||||||
Given the following configs have been set:
|
Given the following configs have been set:
|
||||||
| config | value |
|
| config | value |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
|
| OCIS_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
|
| OCIS_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
|
| OCIS_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_DIGITS | 2 |
|
| OCIS_PASSWORD_POLICY_MIN_DIGITS | 2 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
|
| OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
|
||||||
And user "Alice" has been created with default attributes and without skeleton files
|
And user "Alice" has been created with default attributes and without skeleton files
|
||||||
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
|
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
|
||||||
And using OCS API version "<ocs-api-version>"
|
And using OCS API version "<ocs-api-version>"
|
||||||
@@ -142,11 +142,11 @@ Feature: enforce password on public link
|
|||||||
| config | value |
|
| config | value |
|
||||||
| OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
|
| OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
|
||||||
| OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
|
| OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
|
| OCIS_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
|
| OCIS_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
|
| OCIS_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_DIGITS | 1 |
|
| OCIS_PASSWORD_POLICY_MIN_DIGITS | 1 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
|
| OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
|
||||||
And user "Alice" has been created with default attributes and without skeleton files
|
And user "Alice" has been created with default attributes and without skeleton files
|
||||||
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
|
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
|
||||||
And using OCS API version "<ocs-api-version>"
|
And using OCS API version "<ocs-api-version>"
|
||||||
@@ -173,11 +173,11 @@ Feature: enforce password on public link
|
|||||||
| config | value |
|
| config | value |
|
||||||
| OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
|
| OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
|
||||||
| OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
|
| OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
|
| OCIS_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
|
| OCIS_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
|
| OCIS_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_DIGITS | 1 |
|
| OCIS_PASSWORD_POLICY_MIN_DIGITS | 1 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
|
| OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
|
||||||
And user "Alice" has been created with default attributes and without skeleton files
|
And user "Alice" has been created with default attributes and without skeleton files
|
||||||
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
|
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
|
||||||
And using OCS API version "<ocs-api-version>"
|
And using OCS API version "<ocs-api-version>"
|
||||||
@@ -220,14 +220,14 @@ Feature: enforce password on public link
|
|||||||
But the public should be able to download file "/textfile.txt" from inside the last public link shared folder using the new public WebDAV API with password "<password>"
|
But the public should be able to download file "/textfile.txt" from inside the last public link shared folder using the new public WebDAV API with password "<password>"
|
||||||
Examples:
|
Examples:
|
||||||
| config | config-value | password |
|
| config | config-value | password |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_CHARACTERS | 4 | Ps-1 |
|
| OCIS_PASSWORD_POLICY_MIN_CHARACTERS | 4 | Ps-1 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_CHARACTERS | 14 | Ps1:with space |
|
| OCIS_PASSWORD_POLICY_MIN_CHARACTERS | 14 | Ps1:with space |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 4 | PS1:test |
|
| OCIS_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 4 | PS1:test |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 3 | PS1:TeƒsT |
|
| OCIS_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 3 | PS1:TeƒsT |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_DIGITS | 2 | PS1:test2 |
|
| OCIS_PASSWORD_POLICY_MIN_DIGITS | 2 | PS1:test2 |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 | PS1:test pass |
|
| OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 | PS1:test pass |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 33 | pS1! #$%&'()*+,-./:;<=>?@[\]^_`{ }~ |
|
| OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 33 | pS1! #$%&'()*+,-./:;<=>?@[\]^_`{ }~ |
|
||||||
| FRONTEND_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 5 | 1sameCharacterShouldWork!!!!! |
|
| OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 5 | 1sameCharacterShouldWork!!!!! |
|
||||||
|
|
||||||
|
|
||||||
Scenario Outline: try to create a public link with a password that does not comply with the password policy (invalid cases)
|
Scenario Outline: try to create a public link with a password that does not comply with the password policy (invalid cases)
|
||||||
@@ -250,7 +250,7 @@ Feature: enforce password on public link
|
|||||||
|
|
||||||
|
|
||||||
Scenario Outline: update a public link with a password that is listed in the Banned-Password-List
|
Scenario Outline: update a public link with a password that is listed in the Banned-Password-List
|
||||||
Given the config "FRONTEND_PASSWORD_POLICY_BANNED_PASSWORDS_LIST" has been set to path "config/drone/banned-password-list.txt"
|
Given the config "OCIS_PASSWORD_POLICY_BANNED_PASSWORDS_LIST" has been set to path "config/drone/banned-password-list.txt"
|
||||||
And using OCS API version "2"
|
And using OCS API version "2"
|
||||||
And user "Alice" has been created with default attributes and without skeleton files
|
And user "Alice" has been created with default attributes and without skeleton files
|
||||||
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
|
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
|
||||||
@@ -268,10 +268,10 @@ Feature: enforce password on public link
|
|||||||
| 123 | 400 | 400 | Unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
| 123 | 400 | 400 | Unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
||||||
| password | 400 | 400 | Unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
| password | 400 | 400 | Unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
||||||
| ownCloud | 400 | 400 | Unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
| ownCloud | 400 | 400 | Unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
||||||
|
|
||||||
|
|
||||||
Scenario Outline: create a public link with a password that is listed in the Banned-Password-List
|
Scenario Outline: create a public link with a password that is listed in the Banned-Password-List
|
||||||
Given the config "FRONTEND_PASSWORD_POLICY_BANNED_PASSWORDS_LIST" has been set to path "config/drone/banned-password-list.txt"
|
Given the config "OCIS_PASSWORD_POLICY_BANNED_PASSWORDS_LIST" has been set to path "config/drone/banned-password-list.txt"
|
||||||
And using OCS API version "2"
|
And using OCS API version "2"
|
||||||
And user "Alice" has been created with default attributes and without skeleton files
|
And user "Alice" has been created with default attributes and without skeleton files
|
||||||
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
|
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
|
||||||
@@ -287,4 +287,3 @@ Feature: enforce password on public link
|
|||||||
| 123 | 400 | 400 | Unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
| 123 | 400 | 400 | Unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
||||||
| password | 400 | 400 | Unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
| password | 400 | 400 | Unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
||||||
| ownCloud | 400 | 400 | Unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
| ownCloud | 400 | 400 | Unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
||||||
|
|
||||||
Generated
Vendored
+3
-3
@@ -123,9 +123,9 @@ func (s *svc) updateShare(ctx context.Context, req *collaboration.UpdateShareReq
|
|||||||
if s.c.CommitShareToStorageGrant {
|
if s.c.CommitShareToStorageGrant {
|
||||||
creator := ctxpkg.ContextMustGetUser(ctx)
|
creator := ctxpkg.ContextMustGetUser(ctx)
|
||||||
grant := &provider.Grant{
|
grant := &provider.Grant{
|
||||||
Grantee: req.GetShare().GetGrantee(),
|
Grantee: res.GetShare().GetGrantee(),
|
||||||
Permissions: req.GetShare().GetPermissions().GetPermissions(),
|
Permissions: res.GetShare().GetPermissions().GetPermissions(),
|
||||||
Expiration: req.GetShare().GetExpiration(),
|
Expiration: res.GetShare().GetExpiration(),
|
||||||
Creator: creator.GetId(),
|
Creator: creator.GetId(),
|
||||||
}
|
}
|
||||||
updateGrantStatus, err := s.updateGrant(ctx, res.GetShare().GetResourceId(), grant, nil)
|
updateGrantStatus, err := s.updateGrant(ctx, res.GetShare().GetResourceId(), grant, nil)
|
||||||
|
|||||||
Generated
Vendored
+223
-18
@@ -20,10 +20,21 @@ package publicshareprovider
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||||
|
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||||
|
"github.com/cs3org/reva/v2/pkg/password"
|
||||||
|
"github.com/cs3org/reva/v2/pkg/permission"
|
||||||
|
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||||
|
"github.com/cs3org/reva/v2/pkg/sharedconf"
|
||||||
|
"github.com/cs3org/reva/v2/pkg/storage/utils/grants"
|
||||||
|
"github.com/cs3org/reva/v2/pkg/utils"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@@ -38,6 +49,8 @@ import (
|
|||||||
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const getUserCtxErrMsg = "error getting user from context"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rgrpc.Register("publicshareprovider", New)
|
rgrpc.Register("publicshareprovider", New)
|
||||||
}
|
}
|
||||||
@@ -45,9 +58,21 @@ func init() {
|
|||||||
type config struct {
|
type config struct {
|
||||||
Driver string `mapstructure:"driver"`
|
Driver string `mapstructure:"driver"`
|
||||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||||
|
GatewayAddr string `mapstructure:"gateway_addr"`
|
||||||
AllowedPathsForShares []string `mapstructure:"allowed_paths_for_shares"`
|
AllowedPathsForShares []string `mapstructure:"allowed_paths_for_shares"`
|
||||||
EnableExpiredSharesCleanup bool `mapstructure:"enable_expired_shares_cleanup"`
|
EnableExpiredSharesCleanup bool `mapstructure:"enable_expired_shares_cleanup"`
|
||||||
WriteableShareMustHavePassword bool `mapstructure:"writeable_share_must_have_password"`
|
WriteableShareMustHavePassword bool `mapstructure:"writeable_share_must_have_password"`
|
||||||
|
PublicShareMustHavePassword bool `mapstructure:"public_share_must_have_password"`
|
||||||
|
PasswordPolicy map[string]interface{} `mapstructure:"password_policy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type passwordPolicy struct {
|
||||||
|
MinCharacters int `mapstructure:"min_characters"`
|
||||||
|
MinLowerCaseCharacters int `mapstructure:"min_lowercase_characters"`
|
||||||
|
MinUpperCaseCharacters int `mapstructure:"min_uppercase_characters"`
|
||||||
|
MinDigits int `mapstructure:"min_digits"`
|
||||||
|
MinSpecialCharacters int `mapstructure:"min_special_characters"`
|
||||||
|
BannedPasswordsList map[string]struct{} `mapstructure:"banned_passwords_list"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *config) init() {
|
func (c *config) init() {
|
||||||
@@ -59,7 +84,9 @@ func (c *config) init() {
|
|||||||
type service struct {
|
type service struct {
|
||||||
conf *config
|
conf *config
|
||||||
sm publicshare.Manager
|
sm publicshare.Manager
|
||||||
|
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||||
allowedPathsForShares []*regexp.Regexp
|
allowedPathsForShares []*regexp.Regexp
|
||||||
|
passwordValidator password.Validator
|
||||||
}
|
}
|
||||||
|
|
||||||
func getShareManager(c *config) (publicshare.Manager, error) {
|
func getShareManager(c *config) (publicshare.Manager, error) {
|
||||||
@@ -84,12 +111,21 @@ func (s *service) Register(ss *grpc.Server) {
|
|||||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||||
c := &config{}
|
c := &config{}
|
||||||
if err := mapstructure.Decode(m, c); err != nil {
|
if err := mapstructure.Decode(m, c); err != nil {
|
||||||
err = errors.Wrap(err, "error decoding conf")
|
err = errors.Wrap(err, "error decoding config")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parsePasswordPolicy(m map[string]interface{}) (*passwordPolicy, error) {
|
||||||
|
p := &passwordPolicy{}
|
||||||
|
if err := mapstructure.Decode(m, p); err != nil {
|
||||||
|
err = errors.Wrap(err, "error decoding password policy config")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
// New creates a new user share provider svc
|
// New creates a new user share provider svc
|
||||||
func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) {
|
func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) {
|
||||||
|
|
||||||
@@ -97,6 +133,10 @@ func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
p, err := parsePasswordPolicy(c.PasswordPolicy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
c.init()
|
c.init()
|
||||||
|
|
||||||
@@ -114,15 +154,36 @@ func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) {
|
|||||||
allowedPathsForShares = append(allowedPathsForShares, regex)
|
allowedPathsForShares = append(allowedPathsForShares, regex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gatewaySelector, err := pool.GatewaySelector(sharedconf.GetGatewaySVC(c.GatewayAddr))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
service := &service{
|
service := &service{
|
||||||
conf: c,
|
conf: c,
|
||||||
sm: sm,
|
sm: sm,
|
||||||
|
gatewaySelector: gatewaySelector,
|
||||||
allowedPathsForShares: allowedPathsForShares,
|
allowedPathsForShares: allowedPathsForShares,
|
||||||
|
passwordValidator: newPasswordPolicy(p),
|
||||||
}
|
}
|
||||||
|
|
||||||
return service, nil
|
return service, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newPasswordPolicy(c *passwordPolicy) password.Validator {
|
||||||
|
if c == nil {
|
||||||
|
return password.NewPasswordPolicy(0, 0, 0, 0, 0, nil)
|
||||||
|
}
|
||||||
|
return password.NewPasswordPolicy(
|
||||||
|
c.MinCharacters,
|
||||||
|
c.MinLowerCaseCharacters,
|
||||||
|
c.MinUpperCaseCharacters,
|
||||||
|
c.MinDigits,
|
||||||
|
c.MinSpecialCharacters,
|
||||||
|
c.BannedPasswordsList,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *service) isPathAllowed(path string) bool {
|
func (s *service) isPathAllowed(path string) bool {
|
||||||
if len(s.allowedPathsForShares) == 0 {
|
if len(s.allowedPathsForShares) == 0 {
|
||||||
return true
|
return true
|
||||||
@@ -139,33 +200,129 @@ func (s *service) CreatePublicShare(ctx context.Context, req *link.CreatePublicS
|
|||||||
log := appctx.GetLogger(ctx)
|
log := appctx.GetLogger(ctx)
|
||||||
log.Info().Str("publicshareprovider", "create").Msg("create public share")
|
log.Info().Str("publicshareprovider", "create").Msg("create public share")
|
||||||
|
|
||||||
if !conversions.SufficientCS3Permissions(req.GetResourceInfo().GetPermissionSet(), req.GetGrant().GetPermissions().GetPermissions()) {
|
gatewayClient, err := s.gatewaySelector.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isInternalLink := grants.PermissionsEqual(req.GetGrant().GetPermissions().GetPermissions(), &provider.ResourcePermissions{})
|
||||||
|
|
||||||
|
sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: req.GetResourceInfo().GetId()}})
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Interface("resource_id", req.GetResourceInfo().GetId()).Msg("failed to stat resource to share")
|
||||||
return &link.CreatePublicShareResponse{
|
return &link.CreatePublicShareResponse{
|
||||||
Status: status.NewInvalid(ctx, "insufficient permissions to create that kind of share"),
|
Status: status.NewInternal(ctx, "failed to stat resource to share"),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// all users can create internal links
|
||||||
|
if !isInternalLink {
|
||||||
|
// check if the user has the permission in the user role
|
||||||
|
ok, err := utils.CheckPermission(ctx, permission.WritePublicLink, gatewayClient)
|
||||||
|
if err != nil {
|
||||||
|
return &link.CreatePublicShareResponse{
|
||||||
|
Status: status.NewInternal(ctx, "failed check user permission to write public link"),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return &link.CreatePublicShareResponse{
|
||||||
|
Status: status.NewPermissionDenied(ctx, nil, "no permission to create public links"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that user has share permissions
|
||||||
|
if !sRes.GetInfo().GetPermissionSet().AddGrant {
|
||||||
|
return &link.CreatePublicShareResponse{
|
||||||
|
Status: status.NewInvalidArg(ctx, "no share permission"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.isPathAllowed(req.ResourceInfo.Path) {
|
// check if the user can share with the desired permissions
|
||||||
|
if !conversions.SufficientCS3Permissions(sRes.GetInfo().GetPermissionSet(), req.GetGrant().GetPermissions().GetPermissions()) {
|
||||||
return &link.CreatePublicShareResponse{
|
return &link.CreatePublicShareResponse{
|
||||||
Status: status.NewInvalid(ctx, "share creation is not allowed for the specified path"),
|
Status: status.NewInvalidArg(ctx, "insufficient permissions to create that kind of share"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate path
|
||||||
|
if !s.isPathAllowed(req.GetResourceInfo().GetPath()) {
|
||||||
|
return &link.CreatePublicShareResponse{
|
||||||
|
Status: status.NewFailedPrecondition(ctx, nil, "share creation is not allowed for the specified path"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that this is a not a personal space root
|
||||||
|
if req.GetResourceInfo().GetId().GetOpaqueId() == req.GetResourceInfo().GetId().GetSpaceId() &&
|
||||||
|
req.GetResourceInfo().GetSpace().GetSpaceType() == "personal" {
|
||||||
|
return &link.CreatePublicShareResponse{
|
||||||
|
Status: status.NewInvalidArg(ctx, "cannot create link on personal space root"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// quick link returns the existing one if already present
|
||||||
|
quickLink, err := checkQuicklink(req.GetResourceInfo())
|
||||||
|
if err != nil {
|
||||||
|
return &link.CreatePublicShareResponse{
|
||||||
|
Status: status.NewInvalidArg(ctx, "invalid quicklink value"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if quickLink {
|
||||||
|
f := []*link.ListPublicSharesRequest_Filter{publicshare.ResourceIDFilter(req.GetResourceInfo().GetId())}
|
||||||
|
req := link.ListPublicSharesRequest{Filters: f}
|
||||||
|
res, err := s.ListPublicShares(ctx, &req)
|
||||||
|
if err != nil || res.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||||
|
return &link.CreatePublicShareResponse{
|
||||||
|
Status: status.NewInternal(ctx, "could not list public links"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
for _, l := range res.GetShare() {
|
||||||
|
if l.Quicklink {
|
||||||
|
return &link.CreatePublicShareResponse{
|
||||||
|
Status: status.NewOK(ctx),
|
||||||
|
Share: l,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
grant := req.GetGrant()
|
grant := req.GetGrant()
|
||||||
if grant != nil && s.conf.WriteableShareMustHavePassword &&
|
|
||||||
publicshare.IsWriteable(grant.GetPermissions()) && grant.Password == "" {
|
// validate expiration date
|
||||||
|
if grant.GetExpiration() != nil {
|
||||||
|
expirationDateTime := utils.TSToTime(grant.GetExpiration()).UTC()
|
||||||
|
if expirationDateTime.Before(time.Now().UTC()) {
|
||||||
|
msg := fmt.Sprintf("expiration date is in the past: %s", expirationDateTime.Format(time.RFC3339))
|
||||||
|
return &link.CreatePublicShareResponse{
|
||||||
|
Status: status.NewInvalidArg(ctx, msg),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// enforce password if needed
|
||||||
|
setPassword := grant.GetPassword()
|
||||||
|
if !isInternalLink && enforcePassword(grant, s.conf) && len(setPassword) == 0 {
|
||||||
return &link.CreatePublicShareResponse{
|
return &link.CreatePublicShareResponse{
|
||||||
Status: status.NewInvalid(ctx, "writeable shares must have a password protection"),
|
Status: status.NewInvalidArg(ctx, "password protection is enforced"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate password policy
|
||||||
|
if len(setPassword) > 0 {
|
||||||
|
if err := s.passwordValidator.Validate(setPassword); err != nil {
|
||||||
|
return &link.CreatePublicShareResponse{
|
||||||
|
Status: status.NewInvalidArg(ctx, err.Error()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
u, ok := ctxpkg.ContextGetUser(ctx)
|
u, ok := ctxpkg.ContextGetUser(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error().Msg("error getting user from context")
|
log.Error().Msg(getUserCtxErrMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := &link.CreatePublicShareResponse{}
|
res := &link.CreatePublicShareResponse{}
|
||||||
share, err := s.sm.CreatePublicShare(ctx, u, req.ResourceInfo, req.Grant)
|
share, err := s.sm.CreatePublicShare(ctx, u, req.GetResourceInfo(), req.GetGrant())
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
log.Error().Err(err).Interface("request", req).Msg("could not write public share")
|
log.Error().Err(err).Interface("request", req).Msg("could not write public share")
|
||||||
@@ -179,11 +336,37 @@ func (s *service) CreatePublicShare(ctx context.Context, req *link.CreatePublicS
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) RemovePublicShare(ctx context.Context, req *link.RemovePublicShareRequest) (*link.RemovePublicShareResponse, error) {
|
func (s *service) RemovePublicShare(ctx context.Context, req *link.RemovePublicShareRequest) (*link.RemovePublicShareResponse, error) {
|
||||||
|
gatewayClient, err := s.gatewaySelector.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
log := appctx.GetLogger(ctx)
|
log := appctx.GetLogger(ctx)
|
||||||
log.Info().Str("publicshareprovider", "remove").Msg("remove public share")
|
log.Info().Str("publicshareprovider", "remove").Msg("remove public share")
|
||||||
|
|
||||||
user := ctxpkg.ContextMustGetUser(ctx)
|
user := ctxpkg.ContextMustGetUser(ctx)
|
||||||
err := s.sm.RevokePublicShare(ctx, user, req.Ref)
|
ps, err := s.sm.GetPublicShare(ctx, user, req.GetRef(), false)
|
||||||
|
if err != nil {
|
||||||
|
return &link.RemovePublicShareResponse{
|
||||||
|
Status: status.NewInternal(ctx, "error loading public share"),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
if !publicshare.IsCreatedByUser(*ps, user) {
|
||||||
|
sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: ps.ResourceId}})
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Interface("resource_id", ps.ResourceId).Msg("failed to stat shared resource")
|
||||||
|
return &link.RemovePublicShareResponse{
|
||||||
|
Status: status.NewInternal(ctx, "failed to stat shared resource"),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sRes.GetInfo().GetPermissionSet().RemoveGrant {
|
||||||
|
return &link.RemovePublicShareResponse{
|
||||||
|
Status: status.NewPermissionDenied(ctx, nil, "no permission to delete public share"),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = s.sm.RevokePublicShare(ctx, user, req.Ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &link.RemovePublicShareResponse{
|
return &link.RemovePublicShareResponse{
|
||||||
Status: status.NewInternal(ctx, "error deleting public share"),
|
Status: status.NewInternal(ctx, "error deleting public share"),
|
||||||
@@ -227,7 +410,7 @@ func (s *service) GetPublicShare(ctx context.Context, req *link.GetPublicShareRe
|
|||||||
|
|
||||||
u, ok := ctxpkg.ContextGetUser(ctx)
|
u, ok := ctxpkg.ContextGetUser(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error().Msg("error getting user from context")
|
log.Error().Msg(getUserCtxErrMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
ps, err := s.sm.GetPublicShare(ctx, u, req.Ref, req.GetSign())
|
ps, err := s.sm.GetPublicShare(ctx, u, req.Ref, req.GetSign())
|
||||||
@@ -281,16 +464,11 @@ func (s *service) UpdatePublicShare(ctx context.Context, req *link.UpdatePublicS
|
|||||||
|
|
||||||
u, ok := ctxpkg.ContextGetUser(ctx)
|
u, ok := ctxpkg.ContextGetUser(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error().Msg("error getting user from context")
|
log.Error().Msg(getUserCtxErrMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateR, err := s.sm.UpdatePublicShare(ctx, u, req)
|
updateR, err := s.sm.UpdatePublicShare(ctx, u, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, publicshare.ErrShareNeedsPassword) {
|
|
||||||
return &link.UpdatePublicShareResponse{
|
|
||||||
Status: status.NewInvalid(ctx, err.Error()),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return &link.UpdatePublicShareResponse{
|
return &link.UpdatePublicShareResponse{
|
||||||
Status: status.NewInternal(ctx, err.Error()),
|
Status: status.NewInternal(ctx, err.Error()),
|
||||||
}, nil
|
}, nil
|
||||||
@@ -302,3 +480,30 @@ func (s *service) UpdatePublicShare(ctx context.Context, req *link.UpdatePublicS
|
|||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func enforcePassword(grant *link.Grant, conf *config) bool {
|
||||||
|
if conf.PublicShareMustHavePassword {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
isReadOnly := conversions.SufficientCS3Permissions(conversions.NewViewerRole(true).CS3ResourcePermissions(), grant.GetPermissions().GetPermissions())
|
||||||
|
return !isReadOnly && conf.WriteableShareMustHavePassword
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkQuicklink(info *provider.ResourceInfo) (bool, error) {
|
||||||
|
if info == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if m := info.GetArbitraryMetadata().GetMetadata(); m != nil {
|
||||||
|
q, ok := m["quicklink"]
|
||||||
|
// empty string would trigger an error in ParseBool()
|
||||||
|
if !ok || q == "" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
quickLink, err := strconv.ParseBool(q)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return quickLink, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|||||||
Generated
Vendored
+2
-2
@@ -156,13 +156,13 @@ func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShar
|
|||||||
req.GetGrant().GetPermissions().GetPermissions(),
|
req.GetGrant().GetPermissions().GetPermissions(),
|
||||||
); !shareCreationAllowed {
|
); !shareCreationAllowed {
|
||||||
return &collaboration.CreateShareResponse{
|
return &collaboration.CreateShareResponse{
|
||||||
Status: status.NewInvalid(ctx, "insufficient permissions to create that kind of share"),
|
Status: status.NewPermissionDenied(ctx, nil, "insufficient permissions to create that kind of share"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.isPathAllowed(req.GetResourceInfo().GetPath()) {
|
if !s.isPathAllowed(req.GetResourceInfo().GetPath()) {
|
||||||
return &collaboration.CreateShareResponse{
|
return &collaboration.CreateShareResponse{
|
||||||
Status: status.NewInvalid(ctx, "share creation is not allowed for the specified path"),
|
Status: status.NewFailedPrecondition(ctx, nil, "share creation is not allowed for the specified path"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-30
@@ -76,7 +76,7 @@ func NewFile(c map[string]interface{}) (publicshare.Manager, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return New(conf.GatewayAddr, conf.SharePasswordHashCost, conf.JanitorRunInterval, conf.EnableExpiredSharesCleanup, p, conf.WriteableShareMustHavePassword)
|
return New(conf.GatewayAddr, conf.SharePasswordHashCost, conf.JanitorRunInterval, conf.EnableExpiredSharesCleanup, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMemory returns a new in-memory public shares manager.
|
// NewMemory returns a new in-memory public shares manager.
|
||||||
@@ -93,7 +93,7 @@ func NewMemory(c map[string]interface{}) (publicshare.Manager, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return New(conf.GatewayAddr, conf.SharePasswordHashCost, conf.JanitorRunInterval, conf.EnableExpiredSharesCleanup, p, conf.WriteableShareMustHavePassword)
|
return New(conf.GatewayAddr, conf.SharePasswordHashCost, conf.JanitorRunInterval, conf.EnableExpiredSharesCleanup, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCS3 returns a new cs3 public shares manager.
|
// NewCS3 returns a new cs3 public shares manager.
|
||||||
@@ -115,19 +115,18 @@ func NewCS3(c map[string]interface{}) (publicshare.Manager, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return New(conf.GatewayAddr, conf.SharePasswordHashCost, conf.JanitorRunInterval, conf.EnableExpiredSharesCleanup, p, conf.WriteableShareMustHavePassword)
|
return New(conf.GatewayAddr, conf.SharePasswordHashCost, conf.JanitorRunInterval, conf.EnableExpiredSharesCleanup, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new public share manager instance
|
// New returns a new public share manager instance
|
||||||
func New(gwAddr string, pwHashCost, janitorRunInterval int, enableCleanup bool, p persistence.Persistence, writeableShareMustHavePassword bool) (publicshare.Manager, error) {
|
func New(gwAddr string, pwHashCost, janitorRunInterval int, enableCleanup bool, p persistence.Persistence) (publicshare.Manager, error) {
|
||||||
m := &manager{
|
m := &manager{
|
||||||
gatewayAddr: gwAddr,
|
gatewayAddr: gwAddr,
|
||||||
mutex: &sync.Mutex{},
|
mutex: &sync.Mutex{},
|
||||||
passwordHashCost: pwHashCost,
|
passwordHashCost: pwHashCost,
|
||||||
janitorRunInterval: janitorRunInterval,
|
janitorRunInterval: janitorRunInterval,
|
||||||
enableExpiredSharesCleanup: enableCleanup,
|
enableExpiredSharesCleanup: enableCleanup,
|
||||||
persistence: p,
|
persistence: p,
|
||||||
writeableShareMustHavePassword: writeableShareMustHavePassword,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go m.startJanitorRun()
|
go m.startJanitorRun()
|
||||||
@@ -135,11 +134,10 @@ func New(gwAddr string, pwHashCost, janitorRunInterval int, enableCleanup bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
type commonConfig struct {
|
type commonConfig struct {
|
||||||
GatewayAddr string `mapstructure:"gateway_addr"`
|
GatewayAddr string `mapstructure:"gateway_addr"`
|
||||||
SharePasswordHashCost int `mapstructure:"password_hash_cost"`
|
SharePasswordHashCost int `mapstructure:"password_hash_cost"`
|
||||||
JanitorRunInterval int `mapstructure:"janitor_run_interval"`
|
JanitorRunInterval int `mapstructure:"janitor_run_interval"`
|
||||||
EnableExpiredSharesCleanup bool `mapstructure:"enable_expired_shares_cleanup"`
|
EnableExpiredSharesCleanup bool `mapstructure:"enable_expired_shares_cleanup"`
|
||||||
WriteableShareMustHavePassword bool `mapstructure:"writeable_share_must_have_password"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type fileConfig struct {
|
type fileConfig struct {
|
||||||
@@ -171,10 +169,9 @@ type manager struct {
|
|||||||
mutex *sync.Mutex
|
mutex *sync.Mutex
|
||||||
persistence persistence.Persistence
|
persistence persistence.Persistence
|
||||||
|
|
||||||
passwordHashCost int
|
passwordHashCost int
|
||||||
janitorRunInterval int
|
janitorRunInterval int
|
||||||
enableExpiredSharesCleanup bool
|
enableExpiredSharesCleanup bool
|
||||||
writeableShareMustHavePassword bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *manager) startJanitorRun() {
|
func (m *manager) startJanitorRun() {
|
||||||
@@ -343,12 +340,6 @@ func (m *manager) UpdatePublicShare(ctx context.Context, u *user.User, req *link
|
|||||||
old, _ := json.Marshal(share.Permissions)
|
old, _ := json.Marshal(share.Permissions)
|
||||||
new, _ := json.Marshal(req.Update.GetGrant().Permissions)
|
new, _ := json.Marshal(req.Update.GetGrant().Permissions)
|
||||||
|
|
||||||
if m.writeableShareMustHavePassword &&
|
|
||||||
publicshare.IsWriteable(req.GetUpdate().GetGrant().GetPermissions()) &&
|
|
||||||
(!share.PasswordProtected && req.GetUpdate().GetGrant().GetPassword() == "") {
|
|
||||||
return nil, publicshare.ErrShareNeedsPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.GetUpdate().GetGrant().GetPassword() != "" {
|
if req.GetUpdate().GetGrant().GetPassword() != "" {
|
||||||
passwordChanged = true
|
passwordChanged = true
|
||||||
h, err := bcrypt.GenerateFromPassword([]byte(req.Update.GetGrant().Password), m.passwordHashCost)
|
h, err := bcrypt.GenerateFromPassword([]byte(req.Update.GetGrant().Password), m.passwordHashCost)
|
||||||
@@ -369,10 +360,6 @@ func (m *manager) UpdatePublicShare(ctx context.Context, u *user.User, req *link
|
|||||||
case link.UpdatePublicShareRequest_Update_TYPE_PASSWORD:
|
case link.UpdatePublicShareRequest_Update_TYPE_PASSWORD:
|
||||||
passwordChanged = true
|
passwordChanged = true
|
||||||
if req.Update.GetGrant().Password == "" {
|
if req.Update.GetGrant().Password == "" {
|
||||||
if m.writeableShareMustHavePassword && publicshare.IsWriteable(share.Permissions) {
|
|
||||||
return nil, publicshare.ErrShareNeedsPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
share.PasswordProtected = false
|
share.PasswordProtected = false
|
||||||
newPasswordEncoded = ""
|
newPasswordEncoded = ""
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
-6
@@ -24,7 +24,6 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||||
@@ -41,11 +40,6 @@ const (
|
|||||||
StorageIDFilterType link.ListPublicSharesRequest_Filter_Type = 4
|
StorageIDFilterType link.ListPublicSharesRequest_Filter_Type = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrShareNeedsPassword is an error which is returned when a public share must have a password.
|
|
||||||
ErrShareNeedsPassword = errors.New("the public share needs to have a password")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Manager manipulates public shares.
|
// Manager manipulates public shares.
|
||||||
type Manager interface {
|
type Manager interface {
|
||||||
CreatePublicShare(ctx context.Context, u *user.User, md *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error)
|
CreatePublicShare(ctx context.Context, u *user.User, md *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error)
|
||||||
|
|||||||
Vendored
+1
-1
@@ -359,7 +359,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1
|
|||||||
github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1
|
github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1
|
||||||
github.com/cs3org/go-cs3apis/cs3/tx/v1beta1
|
github.com/cs3org/go-cs3apis/cs3/tx/v1beta1
|
||||||
github.com/cs3org/go-cs3apis/cs3/types/v1beta1
|
github.com/cs3org/go-cs3apis/cs3/types/v1beta1
|
||||||
# github.com/cs3org/reva/v2 v2.16.1-0.20231201122033-a389ddc645c4
|
# github.com/cs3org/reva/v2 v2.16.1-0.20231206110211-7198abf507f6
|
||||||
## explicit; go 1.20
|
## explicit; go 1.20
|
||||||
github.com/cs3org/reva/v2/cmd/revad/internal/grace
|
github.com/cs3org/reva/v2/cmd/revad/internal/grace
|
||||||
github.com/cs3org/reva/v2/cmd/revad/runtime
|
github.com/cs3org/reva/v2/cmd/revad/runtime
|
||||||
|
|||||||
Reference in New Issue
Block a user