diff --git a/docs/eventmanager.md b/docs/eventmanager.md index 2b854b31..3f86439f 100644 --- a/docs/eventmanager.md +++ b/docs/eventmanager.md @@ -40,7 +40,7 @@ The following trigger events are supported: - `Filesystem events`, for example `upload`, `download` etc. - `Provider events`, for example `add`, `update`, `delete` user or other resources. -- `Schedules`. +- `Schedules`. The scheduler uses UTC time. - `IP Blocked`, this event can be generated if you enable the [defender](./defender.md). - `Certificate`, this event is generated when a certificate is renewed using the built-in ACME protocol. Both successful and failed renewals are notified. diff --git a/docs/groups.md b/docs/groups.md index f03ab94f..cbfcf282 100644 --- a/docs/groups.md +++ b/docs/groups.md @@ -15,7 +15,7 @@ The following settings are inherited from the primary group: - home dir, if set for the group will replace the one defined for the user. The `%username%` placeholder is replaced with the username - filesystem config, if the provider set for the group is different from the "local provider" will replace the one defined for the user. The `%username%` placeholder is replaced with the username within the defined "prefix", for any vfs, and the "username" for the SFTP filesystem config -- max sessions, quota size/files, upload/download bandwidth, upload/download/total data transfer, max upload size, external auth cache time, ftp_security: if they are set to `0` for the user they are replaced with the value set for the group, if different from `0` +- max sessions, quota size/files, upload/download bandwidth, upload/download/total data transfer, max upload size, external auth cache time, ftp_security, default share expiration: if they are set to `0` for the user they are replaced with the value set for the group, if different from `0` - TLS username, check password hook disabled, pre-login hook disabled, external auth hook disabled, filesystem checks disabled, allow API key authentication, anonymous user: if they are not set for the user they are replaced with the value set for the group - starting directory, if the user does not have a starting directory set, the value set for the group is used, if any. The `%username%` placeholder is replaced with the username diff --git a/go.mod b/go.mod index f0a7abfc..d571be84 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/aws/aws-sdk-go-v2/credentials v1.12.14 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.27 - github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.12 + github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.13 github.com/aws/aws-sdk-go-v2/service/s3 v1.27.5 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.18 github.com/aws/aws-sdk-go-v2/service/sts v1.16.13 @@ -51,7 +51,7 @@ require ( github.com/rs/cors v1.8.3-0.20220619195839-da52b0701de5 github.com/rs/xid v1.4.0 github.com/rs/zerolog v1.27.0 - github.com/sftpgo/sdk v0.1.2-0.20220821164353-a9b95497604e + github.com/sftpgo/sdk v0.1.2-0.20220828084006-f9e2fffac657 github.com/shirou/gopsutil/v3 v3.22.7 github.com/spf13/afero v1.9.2 github.com/spf13/cobra v1.5.0 @@ -65,20 +65,20 @@ require ( go.etcd.io/bbolt v1.3.6 go.uber.org/automaxprocs v1.5.1 gocloud.dev v0.26.0 - golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 - golang.org/x/net v0.0.0-20220812174116-3211cb980234 - golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7 - golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 + golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d + golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b + golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 + golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 - google.golang.org/api v0.93.0 + google.golang.org/api v0.94.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) require ( - cloud.google.com/go v0.103.0 // indirect + cloud.google.com/go v0.104.0 // indirect cloud.google.com/go/compute v1.9.0 // indirect cloud.google.com/go/iam v0.3.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1 // indirect github.com/ajg/form v1.5.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.4 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18 // indirect @@ -115,7 +115,7 @@ require ( github.com/hashicorp/yamux v0.1.1 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/klauspost/cpuid/v2 v2.1.0 // indirect + github.com/klauspost/cpuid/v2 v2.1.1 // indirect github.com/kr/fs v0.1.0 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect github.com/lestrrat-go/blackmagic v1.0.1 // indirect @@ -133,7 +133,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/oklog/run v1.1.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.3 // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect @@ -144,7 +144,7 @@ require ( github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.4.0 // indirect + github.com/subosito/gotenv v1.4.1 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.5.0 // indirect github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect @@ -156,7 +156,7 @@ require ( golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect - google.golang.org/grpc v1.48.0 // indirect + google.golang.org/grpc v1.49.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect @@ -167,6 +167,6 @@ require ( replace ( github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 github.com/pkg/sftp => github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d - golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20220820120743-96e237d06b82 - golang.org/x/net => github.com/drakkan/net v0.0.0-20220820120527-aa746bf1d738 + golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20220828084757-61f5262cc94f + golang.org/x/net => github.com/drakkan/net v0.0.0-20220828084259-1562d1fb0fc5 ) diff --git a/go.sum b/go.sum index dc04be0f..a259ef97 100644 --- a/go.sum +++ b/go.sum @@ -32,9 +32,8 @@ cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2Z cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= -cloud.google.com/go v0.103.0 h1:YXtxp9ymmZjlGzxV7VrYQ8aaQuAgcqxSy6YhDX4I458= -cloud.google.com/go v0.103.0/go.mod h1:vwLx1nqLrzLX/fpwSMOXmFIqBOyHsvHbnAdbGSJ+mKk= +cloud.google.com/go v0.104.0 h1:gSmWO7DY1vOm0MVU6DNXM11BWHHsTUmsC5cv1fuW5X8= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -76,7 +75,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.25.0 h1:D2Dn0PslpK7Z3B2AvuUHyIC762bDbGJdlmQlCBR71os= cloud.google.com/go/storage v1.25.0/go.mod h1:Qys4JU+jeup3QnuKKAosWuxrD95C4MSqxfVDnSirDsI= cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A= @@ -97,8 +95,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2/go.mod h1:uGG2W01BaETf0Ozp+Q github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0 h1:Yoicul8bnVdQrhDMTHxdEckRGX01XvwXDHUT9zYZ3k0= github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 h1:jp0dGvZ7ZK0mgqnTSClMxa5xuRL7NZgHameVYF6BurY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1 h1:XUNQ4mw+zJmaA2KXzP9JlQiecy1SI+Eog7xVkPiqIbg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1 h1:QSdcrd/UFJv6Bp/CfoVf2SrENpFn9P6Yh8yb+xNhYMM= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1/go.mod h1:eZ4g6GUvXiGulfIbbhh1Xr4XwUYaYaWMqzGD/284wCA= github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU= @@ -185,8 +183,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3/go.mod h1:Bm/v2Ia github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.12 h1:QFjSOmHSb77qRTv7KI9UFon9X5wLWY5/M+6la3dTcZc= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.12/go.mod h1:MADjAN0GHFDuc5lRa5Y5ki+oIO/w7X4qczHy+OUx0IA= github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYYAWpQlx3PKmohy+rE2F+o5g= -github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.12 h1:KgwQKIp/yb9xCXVb+lZdPwoPLG621v+0bGm7pBJyhIQ= -github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.12/go.mod h1:ZlZaygKJuKAxT4OUuoKCVPWil0+QALcb8fZxsMVO1b4= +github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.13 h1:2YvJo1vi8WH2kSbHP+knO/7oXH2fIAwmwb0MoreQI/g= +github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.13/go.mod h1:ZlZaygKJuKAxT4OUuoKCVPWil0+QALcb8fZxsMVO1b4= github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak= github.com/aws/aws-sdk-go-v2/service/s3 v1.27.5 h1:h9qqTedYnA9JcWjKyLV6UYIMSdp91ExLCUbjbpDLH7A= github.com/aws/aws-sdk-go-v2/service/s3 v1.27.5/go.mod h1:J8SS5Tp/zeLxaubB0xGfKnVrvssNBNLwTipreTKLhjQ= @@ -262,12 +260,12 @@ github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQ github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/drakkan/crypto v0.0.0-20220820120743-96e237d06b82 h1:TezoLY9GhuhvionRxoU1FyfIbbC2lOm+OipXzuXAC2A= -github.com/drakkan/crypto v0.0.0-20220820120743-96e237d06b82/go.mod h1:SiM6ypd8Xu1xldObYtbDztuUU7xUzMnUULfphXFZmro= +github.com/drakkan/crypto v0.0.0-20220828084757-61f5262cc94f h1:fFnBNoP0CQJZcdDSV35Wjf7aeZE6AOW0WoC3XqIgzVY= +github.com/drakkan/crypto v0.0.0-20220828084757-61f5262cc94f/go.mod h1:SiM6ypd8Xu1xldObYtbDztuUU7xUzMnUULfphXFZmro= github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHPMtBLXhQmjaga91/DDjWk9jWA= github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU= -github.com/drakkan/net v0.0.0-20220820120527-aa746bf1d738 h1:y++pz0G+bwPzCJyHiqslCAXzAWj4Azk5FbGTwZ5nq0g= -github.com/drakkan/net v0.0.0-20220820120527-aa746bf1d738/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +github.com/drakkan/net v0.0.0-20220828084259-1562d1fb0fc5 h1:+sVMXrU1DiQLNDgz1KvybqHEzRf8KuX5xQW8fpii6rI= +github.com/drakkan/net v0.0.0-20220828084259-1562d1fb0fc5/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d h1:kNk/KRhszPJASp7WvjagNW254aKK643Lu8/fr4/ukiM= github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg= github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 h1:/ZshrfQzayqRSBDodmp3rhNCHJCff+utvgBuWRbiqu4= @@ -550,8 +548,8 @@ github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0= -github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0= +github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -652,8 +650,8 @@ github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI= github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.3 h1:h9JoA60e1dVEOpp0PFwJSmt1Htu057NUq9/bUwaO61s= -github.com/pelletier/go-toml/v2 v2.0.3/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8= github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= @@ -714,8 +712,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo= -github.com/sftpgo/sdk v0.1.2-0.20220821164353-a9b95497604e h1:Up8iLVu+PPd5ejyG8fi8910IC4JO+A1/COJf+sJWHI8= -github.com/sftpgo/sdk v0.1.2-0.20220821164353-a9b95497604e/go.mod h1:fxFs5FP9bhi3ObH+7qdxZF+2QOk8J/u4GAR5yuX5jMg= +github.com/sftpgo/sdk v0.1.2-0.20220828084006-f9e2fffac657 h1:UXTpae6d+G/VI3sVITl+58SK0F3ZULn9dlEPMXcyNKY= +github.com/sftpgo/sdk v0.1.2-0.20220828084006-f9e2fffac657/go.mod h1:PTp1TfXa+95wHw9yuZu7BA3vmzLqbRkz3gBmMNnwFQg= github.com/shirou/gopsutil/v3 v3.22.7 h1:flKnuCMfUUrO+oAvwAd6GKZgnPzr098VA/UJ14nhJd4= github.com/shirou/gopsutil/v3 v3.22.7/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= @@ -754,8 +752,8 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62 h1:b2nJXyPCa9HY7giGM+kYcnQ71m14JnGdQabMPmyt++8= github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= -github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs= -github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= @@ -874,9 +872,8 @@ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7 h1:dtndE8FcEta75/4kHF3AbpuWzV6f1LjnLrM4pe2SZrw= -golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -972,13 +969,12 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U= -golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM= +golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1119,10 +1115,8 @@ google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69 google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= -google.golang.org/api v0.86.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.93.0 h1:T2xt9gi0gHdxdnRkVQhT8mIvPaXKNsDNWz+L696M66M= -google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.94.0 h1:KtKM9ru3nzQioV1HLlUf1cR7vMYJIpgls5VhAYQXIwA= +google.golang.org/api v0.94.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1226,9 +1220,7 @@ google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc h1:Nf+EdcTLHR8qDNN/KfkQL0u0ssxt9OhbaWCl5C0ucEI= google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1262,8 +1254,8 @@ google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11 google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/internal/command/command.go b/internal/command/command.go index 73927b9c..78c2d739 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -12,6 +12,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +// Package command provides command configuration for SFTPGo hooks package command import ( diff --git a/internal/dataprovider/dataprovider.go b/internal/dataprovider/dataprovider.go index 15e28243..ef81a86d 100644 --- a/internal/dataprovider/dataprovider.go +++ b/internal/dataprovider/dataprovider.go @@ -2194,6 +2194,7 @@ func copyBaseUserFilters(in sdk.BaseUserFilters) sdk.BaseUserFilters { filters.IsAnonymous = in.IsAnonymous filters.AllowAPIKeyAuth = in.AllowAPIKeyAuth filters.ExternalAuthCacheTime = in.ExternalAuthCacheTime + filters.DefaultSharesExpiration = in.DefaultSharesExpiration filters.WebClient = make([]string, len(in.WebClient)) copy(filters.WebClient, in.WebClient) filters.BandwidthLimits = make([]sdk.BandwidthLimit, 0, len(in.BandwidthLimits)) diff --git a/internal/dataprovider/user.go b/internal/dataprovider/user.go index 0ef635ee..4cc026d0 100644 --- a/internal/dataprovider/user.go +++ b/internal/dataprovider/user.go @@ -1729,6 +1729,9 @@ func (u *User) mergePrimaryGroupFilters(filters sdk.BaseUserFilters, replacer *s if u.Filters.StartDirectory == "" { u.Filters.StartDirectory = u.replacePlaceholder(filters.StartDirectory, replacer) } + if u.Filters.DefaultSharesExpiration == 0 { + u.Filters.DefaultSharesExpiration = filters.DefaultSharesExpiration + } } func (u *User) mergeAdditiveProperties(group Group, groupType int, replacer *strings.Replacer) { diff --git a/internal/httpclient/httpclient.go b/internal/httpclient/httpclient.go index 90d3b142..593443e3 100644 --- a/internal/httpclient/httpclient.go +++ b/internal/httpclient/httpclient.go @@ -12,6 +12,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +// Package httpclient provides HTTP client configuration for SFTPGo hooks package httpclient import ( diff --git a/internal/httpd/api_shares.go b/internal/httpd/api_shares.go index 233c1a69..042d59bf 100644 --- a/internal/httpd/api_shares.go +++ b/internal/httpd/api_shares.go @@ -22,6 +22,7 @@ import ( "os" "path" "strings" + "time" "github.com/go-chi/render" "github.com/rs/xid" @@ -78,7 +79,15 @@ func addShare(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest) return } + user, err := dataprovider.GetUserWithGroupSettings(claims.Username) + if err != nil { + sendAPIResponse(w, r, err, "Unable to retrieve your user", getRespStatus(err)) + return + } var share dataprovider.Share + if user.Filters.DefaultSharesExpiration > 0 { + share.ExpiresAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(24 * time.Hour * time.Duration(user.Filters.DefaultSharesExpiration))) + } err = render.DecodeJSON(r.Body, &share) if err != nil { sendAPIResponse(w, r, err, "", http.StatusBadRequest) diff --git a/internal/httpd/httpd_test.go b/internal/httpd/httpd_test.go index e1cf284b..7f2edf43 100644 --- a/internal/httpd/httpd_test.go +++ b/internal/httpd/httpd_test.go @@ -5676,7 +5676,11 @@ func TestProviderErrors(t *testing.T) { setBearerForReq(req, userAPIToken) rr := executeRequest(req) checkResponseCode(t, http.StatusInternalServerError, rr) - + req, err = http.NewRequest(http.MethodPost, userSharesPath, nil) + assert.NoError(t, err) + setBearerForReq(req, userAPIToken) + rr = executeRequest(req) + checkResponseCode(t, http.StatusInternalServerError, rr) // password reset errors csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath) assert.NoError(t, err) @@ -5696,6 +5700,11 @@ func TestProviderErrors(t *testing.T) { setJWTCookieForReq(req, userWebToken) rr = executeRequest(req) checkResponseCode(t, http.StatusInternalServerError, rr) + req, err = http.NewRequest(http.MethodGet, webClientSharePath, nil) + assert.NoError(t, err) + setJWTCookieForReq(req, userWebToken) + rr = executeRequest(req) + checkResponseCode(t, http.StatusInternalServerError, rr) req, err = http.NewRequest(http.MethodGet, webClientSharePath+"/shareID", nil) assert.NoError(t, err) @@ -11616,6 +11625,9 @@ func TestShareUncompressed(t *testing.T) { checkResponseCode(t, http.StatusCreated, rr) objectID := rr.Header().Get("X-Object-ID") assert.NotEmpty(t, objectID) + s, err := dataprovider.ShareExists(objectID, defaultUsername) + assert.NoError(t, err) + assert.Equal(t, int64(0), s.ExpiresAt) req, err = http.NewRequest(http.MethodGet, webClientPubSharesPath+"/"+objectID, nil) assert.NoError(t, err) @@ -11702,6 +11714,7 @@ func TestShareUncompressed(t *testing.T) { func TestDownloadFromShareError(t *testing.T) { u := getTestUser() u.DownloadDataTransfer = 1 + u.Filters.DefaultSharesExpiration = 10 user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) user.UsedDownloadDataTransfer = 1024*1024 - 32768 @@ -11733,6 +11746,9 @@ func TestDownloadFromShareError(t *testing.T) { checkResponseCode(t, http.StatusCreated, rr) objectID := rr.Header().Get("X-Object-ID") assert.NotEmpty(t, objectID) + s, err := dataprovider.ShareExists(objectID, defaultUsername) + assert.NoError(t, err) + assert.Greater(t, s.ExpiresAt, int64(0)) defer func() { rcv := recover() @@ -15078,8 +15094,12 @@ func TestWebUserShare(t *testing.T) { func TestWebUserShareNoPasswordDisabled(t *testing.T) { u := getTestUser() u.Filters.WebClient = []string{sdk.WebClientShareNoPasswordDisabled} + u.Filters.DefaultSharesExpiration = 15 user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) + user.Filters.DefaultSharesExpiration = 30 + user, _, err = httpdtest.UpdateUser(u, http.StatusOK, "") + assert.NoError(t, err) csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath) assert.NoError(t, err) token, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword) @@ -15116,6 +15136,12 @@ func TestWebUserShareNoPasswordDisabled(t *testing.T) { rr = executeRequest(req) checkResponseCode(t, http.StatusSeeOther, rr) + req, err = http.NewRequest(http.MethodGet, webClientSharePath, nil) + assert.NoError(t, err) + setJWTCookieForReq(req, token) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr) + req, err = http.NewRequest(http.MethodGet, userSharesPath, nil) assert.NoError(t, err) setBearerForReq(req, userAPItoken) @@ -16585,6 +16611,16 @@ func TestWebUserAddMock(t *testing.T) { rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr) form.Set("max_upload_file_size", "1000") + // test invalid default shares expiration + form.Set("default_shares_expiration", "a") + b, contentType, _ = getMultipartFormData(form, "", "") + req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) + setJWTCookieForReq(req, webToken) + req.Header.Set("Content-Type", contentType) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr) + assert.Contains(t, rr.Body.String(), "invalid default shares expiration") + form.Set("default_shares_expiration", "10") // test invalid tls username form.Set("tls_username", "username") b, contentType, _ = getMultipartFormData(form, "", "") @@ -16732,6 +16768,7 @@ func TestWebUserAddMock(t *testing.T) { assert.Equal(t, user.Email, newUser.Email) assert.Equal(t, "/start/dir", newUser.Filters.StartDirectory) assert.Equal(t, 0, newUser.Filters.FTPSecurity) + assert.Equal(t, 10, newUser.Filters.DefaultSharesExpiration) assert.True(t, util.Contains(newUser.PublicKeys, testPubKey)) if val, ok := newUser.Permissions["/subdir"]; ok { assert.True(t, util.Contains(val, dataprovider.PermListItems)) @@ -16929,6 +16966,7 @@ func TestWebUserUpdateMock(t *testing.T) { form.Set("denied_login_methods", dataprovider.SSHLoginMethodKeyboardInteractive) form.Set("denied_protocols", common.ProtocolFTP) form.Set("max_upload_file_size", "100") + form.Set("default_shares_expiration", "30") form.Set("disconnect", "1") form.Set("additional_info", user.AdditionalInfo) form.Set("description", user.Description) @@ -17006,6 +17044,7 @@ func TestWebUserUpdateMock(t *testing.T) { assert.Equal(t, int64(0), updateUser.DownloadDataTransfer) assert.Equal(t, int64(0), updateUser.UploadDataTransfer) assert.Equal(t, int64(0), updateUser.Filters.ExternalAuthCacheTime) + assert.Equal(t, 30, updateUser.Filters.DefaultSharesExpiration) if val, ok := updateUser.Permissions["/otherdir"]; ok { assert.True(t, util.Contains(val, dataprovider.PermListItems)) assert.True(t, util.Contains(val, dataprovider.PermUpload)) @@ -17116,6 +17155,7 @@ func TestUserTemplateWithFoldersMock(t *testing.T) { form.Set("expiration_date", "2020-01-01 00:00:00") form.Set("fs_provider", "0") form.Set("max_upload_file_size", "0") + form.Set("default_shares_expiration", "0") form.Set("ftp_security", "1") form.Set("external_auth_cache_time", "0") form.Set("description", "desc %username% %password%") @@ -17177,6 +17217,7 @@ func TestUserTemplateWithFoldersMock(t *testing.T) { assert.Equal(t, filepath.Join(os.TempDir(), user2.Username), user2.HomeDir) assert.Equal(t, path.Join("/base", user1.Username), user1.Filters.StartDirectory) assert.Equal(t, path.Join("/base", user2.Username), user2.Filters.StartDirectory) + assert.Equal(t, 0, user2.Filters.DefaultSharesExpiration) assert.Equal(t, folder.Name, folder1.Name) assert.Equal(t, folder.MappedPath, folder1.MappedPath) assert.Equal(t, folder.Description, folder1.Description) @@ -17218,6 +17259,7 @@ func TestUserSaveFromTemplateMock(t *testing.T) { form.Set("expiration_date", "") form.Set("fs_provider", "0") form.Set("max_upload_file_size", "0") + form.Set("default_shares_expiration", "0") form.Set("external_auth_cache_time", "0") form.Add("tpl_username", user1) form.Add("tpl_password", "password1") @@ -17306,6 +17348,7 @@ func TestUserTemplateMock(t *testing.T) { form.Set("allowed_extensions", "/dir1::.jpg,.png") form.Set("denied_extensions", "/dir2::.zip") form.Set("max_upload_file_size", "0") + form.Set("default_shares_expiration", "0") form.Add("hooks", "external_auth_disabled") form.Add("hooks", "check_password_disabled") form.Set("disable_fs_checks", "checked") @@ -17436,6 +17479,7 @@ func TestUserPlaceholders(t *testing.T) { form.Set("download_data_transfer", "0") form.Set("external_auth_cache_time", "0") form.Set("max_upload_file_size", "0") + form.Set("default_shares_expiration", "0") b, contentType, _ := getMultipartFormData(form, "", "") req, _ := http.NewRequest(http.MethodPost, webUserPath, &b) setJWTCookieForReq(req, token) @@ -17775,6 +17819,7 @@ func TestWebUserS3Mock(t *testing.T) { form.Set("pattern_type1", "denied") form.Set("pattern_policy1", "1") form.Set("max_upload_file_size", "0") + form.Set("default_shares_expiration", "0") form.Set("ftp_security", "1") form.Set("s3_force_path_style", "checked") form.Set("description", user.Description) @@ -17983,6 +18028,7 @@ func TestWebUserGCSMock(t *testing.T) { form.Set("patterns0", "*.jpg,*.png") form.Set("pattern_type0", "allowed") form.Set("max_upload_file_size", "0") + form.Set("default_shares_expiration", "0") form.Set("ftp_security", "1") b, contentType, _ := getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b) @@ -18107,6 +18153,7 @@ func TestWebUserHTTPFsMock(t *testing.T) { form.Set("patterns1", "*.zip") form.Set("pattern_type1", "denied") form.Set("max_upload_file_size", "0") + form.Set("default_shares_expiration", "0") form.Set("http_equality_check_mode", "true") b, contentType, _ := getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b) @@ -18231,6 +18278,7 @@ func TestWebUserAzureBlobMock(t *testing.T) { form.Set("patterns1", "*.zip") form.Set("pattern_type1", "denied") form.Set("max_upload_file_size", "0") + form.Set("default_shares_expiration", "0") // test invalid az_upload_part_size form.Set("az_upload_part_size", "a") b, contentType, _ := getMultipartFormData(form, "", "") @@ -18410,6 +18458,7 @@ func TestWebUserCryptMock(t *testing.T) { form.Set("patterns1", "*.zip") form.Set("pattern_type1", "denied") form.Set("max_upload_file_size", "0") + form.Set("default_shares_expiration", "0") // passphrase cannot be empty b, contentType, _ := getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b) @@ -18517,6 +18566,7 @@ func TestWebUserSFTPFsMock(t *testing.T) { form.Set("patterns1", "*.zip") form.Set("pattern_type1", "denied") form.Set("max_upload_file_size", "0") + form.Set("default_shares_expiration", "0") // empty sftpconfig b, contentType, _ := getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b) @@ -19326,6 +19376,7 @@ func TestAddWebGroup(t *testing.T) { checkResponseCode(t, http.StatusOK, rr) assert.Contains(t, rr.Body.String(), "invalid max upload file size") form.Set("max_upload_file_size", "0") + form.Set("default_shares_expiration", "0") b, contentType, err = getMultipartFormData(form, "", "") assert.NoError(t, err) req, err = http.NewRequest(http.MethodPost, webGroupPath, &b) @@ -19757,6 +19808,7 @@ func TestUpdateWebGroupMock(t *testing.T) { form.Set("download_data_transfer", "0") form.Set("total_data_transfer", "0") form.Set("max_upload_file_size", "0") + form.Set("default_shares_expiration", "0") form.Set("external_auth_cache_time", "0") form.Set("fs_provider", strconv.FormatInt(int64(group.UserSettings.FsConfig.Provider), 10)) form.Set("sftp_endpoint", group.UserSettings.FsConfig.SFTPConfig.Endpoint) diff --git a/internal/httpd/internal_test.go b/internal/httpd/internal_test.go index 477de947..caa7170b 100644 --- a/internal/httpd/internal_test.go +++ b/internal/httpd/internal_test.go @@ -2184,6 +2184,13 @@ func TestWebUserInvalidClaims(t *testing.T) { assert.Equal(t, http.StatusForbidden, rr.Code) assert.Contains(t, rr.Body.String(), "Invalid token claims") + rr = httptest.NewRecorder() + req, _ = http.NewRequest(http.MethodGet, webClientSharePath, nil) + req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"])) + server.handleClientAddShareGet(rr, req) + assert.Equal(t, http.StatusForbidden, rr.Code) + assert.Contains(t, rr.Body.String(), "Invalid token claims") + rr = httptest.NewRecorder() req, _ = http.NewRequest(http.MethodGet, webClientSharePath, nil) req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"])) diff --git a/internal/httpd/webadmin.go b/internal/httpd/webadmin.go index f7fad594..a1a9ef38 100644 --- a/internal/httpd/webadmin.go +++ b/internal/httpd/webadmin.go @@ -1281,6 +1281,10 @@ func getFiltersFromUserPostFields(r *http.Request) (sdk.BaseUserFilters, error) if err != nil { return filters, fmt.Errorf("invalid max upload file size: %w", err) } + defaultSharesExpiration, err := strconv.ParseInt(r.Form.Get("default_shares_expiration"), 10, 64) + if err != nil { + return filters, fmt.Errorf("invalid default shares expiration: %w", err) + } if r.Form.Get("ftp_security") == "1" { filters.FTPSecurity = 1 } @@ -1294,6 +1298,7 @@ func getFiltersFromUserPostFields(r *http.Request) (sdk.BaseUserFilters, error) filters.FilePatterns = getFilePatternsFromPostField(r) filters.TLSUsername = sdk.TLSUsername(r.Form.Get("tls_username")) filters.WebClient = r.Form["web_client_options"] + filters.DefaultSharesExpiration = int(defaultSharesExpiration) hooks := r.Form["hooks"] if util.Contains(hooks, "external_auth_disabled") { filters.Hooks.ExternalAuthDisabled = true @@ -2588,8 +2593,8 @@ func (s *httpdServer) handleWebAddUserGet(w http.ResponseWriter, r *http.Request Status: 1, Permissions: map[string][]string{ "/": {dataprovider.PermAny}, - }, - }} + }}, + } s.renderUserPage(w, r, &user, userPageModeAdd, "") } diff --git a/internal/httpd/webclient.go b/internal/httpd/webclient.go index 9e090eae..cf46ccb3 100644 --- a/internal/httpd/webclient.go +++ b/internal/httpd/webclient.go @@ -975,7 +975,20 @@ func (s *httpdServer) handleClientEditFile(w http.ResponseWriter, r *http.Reques func (s *httpdServer) handleClientAddShareGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + claims, err := getTokenClaims(r) + if err != nil || claims.Username == "" { + s.renderClientForbiddenPage(w, r, "Invalid token claims") + return + } + user, err := dataprovider.GetUserWithGroupSettings(claims.Username) + if err != nil { + s.renderClientMessagePage(w, r, "Unable to retrieve your user", "", getRespStatus(err), nil, "") + return + } share := &dataprovider.Share{Scope: dataprovider.ShareScopeRead} + if user.Filters.DefaultSharesExpiration > 0 { + share.ExpiresAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(24 * time.Hour * time.Duration(user.Filters.DefaultSharesExpiration))) + } dirName := "/" if _, ok := r.URL.Query()["path"]; ok { dirName = util.CleanPath(r.URL.Query().Get("path")) diff --git a/internal/httpdtest/httpdtest.go b/internal/httpdtest/httpdtest.go index ea268f4e..84df2035 100644 --- a/internal/httpdtest/httpdtest.go +++ b/internal/httpdtest/httpdtest.go @@ -2050,7 +2050,7 @@ func compareUserFiltersEqualFields(expected sdk.BaseUserFilters, actual sdk.Base return nil } -func compareUserFilters(expected sdk.BaseUserFilters, actual sdk.BaseUserFilters) error { +func compareBaseUserFilters(expected sdk.BaseUserFilters, actual sdk.BaseUserFilters) error { if len(expected.AllowedIP) != len(actual.AllowedIP) { return errors.New("allowed IP mismatch") } @@ -2084,6 +2084,16 @@ func compareUserFilters(expected sdk.BaseUserFilters, actual sdk.BaseUserFilters if expected.IsAnonymous != actual.IsAnonymous { return errors.New("is_anonymous mismatch") } + if expected.DefaultSharesExpiration != actual.DefaultSharesExpiration { + return errors.New("default_shares_expiration mismatch") + } + return nil +} + +func compareUserFilters(expected sdk.BaseUserFilters, actual sdk.BaseUserFilters) error { + if err := compareBaseUserFilters(expected, actual); err != nil { + return err + } if err := compareUserFilterSubStructs(expected, actual); err != nil { return err } diff --git a/internal/version/version.go b/internal/version/version.go index de26c926..c059747a 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -12,6 +12,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +// Package version defines SFTPGo version details package version import "strings" diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index d7b71045..65f6b17b 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -4777,6 +4777,9 @@ components: is_anonymous: type: boolean description: 'If enabled the user can login with any password or no password at all. Anonymous users are supported for FTP and WebDAV protocols and permissions will be automatically set to "list" and "download" (read only)' + default_shares_expiration: + type: integer + description: 'Defines the default expiration for newly created shares as number of days. 0 means no expiration' description: Additional user options UserFilters: allOf: diff --git a/templates/webadmin/eventrule.html b/templates/webadmin/eventrule.html index a1027c13..9346cba3 100644 --- a/templates/webadmin/eventrule.html +++ b/templates/webadmin/eventrule.html @@ -90,7 +90,7 @@ along with this program. If not, see . Schedules
-
Hours: 0-23. Day of week: 0-6 (Sun-Sat). Day of month: 1-31. Month: 1-12. Asterisk (*) indicates a match for all the values of the field. e.g. every day of week, every day of month and so on. More info.
+
The scheduler uses UTC time. Hours: 0-23. Day of week: 0-6 (Sun-Sat). Day of month: 1-31. Month: 1-12. Asterisk (*) indicates a match for all the values of the field. e.g. every day of week, every day of month and so on. More info.
{{range $idx, $val := .Rule.Conditions.Schedules}} diff --git a/templates/webadmin/group.html b/templates/webadmin/group.html index 88c90805..3f604f27 100644 --- a/templates/webadmin/group.html +++ b/templates/webadmin/group.html @@ -703,6 +703,17 @@ along with this program. If not, see .
+
+ +
+ + + Default expiration for newly created shares as number of days + +
+
+
diff --git a/templates/webadmin/user.html b/templates/webadmin/user.html index db2910e7..39ad1eb7 100644 --- a/templates/webadmin/user.html +++ b/templates/webadmin/user.html @@ -921,6 +921,17 @@ along with this program. If not, see .
+
+ +
+ + + Default expiration for newly created shares as number of days + +
+
+