users: add a setting to set the default expiration for shares

Fixes #960

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-08-28 14:41:42 +02:00
parent 9473dc3937
commit 37d98ca290
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
19 changed files with 176 additions and 56 deletions

View file

@ -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.

View file

@ -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

30
go.mod
View file

@ -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
)

60
go.sum
View file

@ -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=

View file

@ -12,6 +12,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Package command provides command configuration for SFTPGo hooks
package command
import (

View file

@ -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))

View file

@ -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) {

View file

@ -12,6 +12,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Package httpclient provides HTTP client configuration for SFTPGo hooks
package httpclient
import (

View file

@ -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)

View file

@ -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)

View file

@ -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"]))

View file

@ -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, "")
}

View file

@ -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"))

View file

@ -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
}

View file

@ -12,6 +12,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Package version defines SFTPGo version details
package version
import "strings"

View file

@ -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:

View file

@ -90,7 +90,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<b>Schedules</b>
</div>
<div class="card-body">
<h6 class="card-title mb-4">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 <a href="https://pkg.go.dev/github.com/robfig/cron/v3#hdr-CRON_Expression_Format" target="_blank">info</a>.</h6>
<h6 class="card-title mb-4">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 <a href="https://pkg.go.dev/github.com/robfig/cron/v3#hdr-CRON_Expression_Format" target="_blank">info</a>.</h6>
<div class="form-group row">
<div class="col-md-12 form_field_schedules_outer">
{{range $idx, $val := .Rule.Conditions.Schedules}}

View file

@ -703,6 +703,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</div>
</div>
<div class="form-group row">
<label for="idDefaultSharesExpiration" class="col-sm-2 col-form-label">Default shares expiration</label>
<div class="col-sm-10">
<input type="number" class="form-control" id="idDefaultSharesExpiration" name="default_shares_expiration"
value="{{.Group.UserSettings.Filters.DefaultSharesExpiration}}" min="0" aria-describedby="defaultSharesExpirationHelpBlock">
<small id="defaultSharesExpirationHelpBlock" class="form-text text-muted">
Default expiration for newly created shares as number of days
</small>
</div>
</div>
<div class="form-group row">
<label for="idHooks" class="col-sm-2 col-form-label">Hooks</label>
<div class="col-sm-10">

View file

@ -921,6 +921,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</div>
</div>
<div class="form-group row">
<label for="idDefaultSharesExpiration" class="col-sm-2 col-form-label">Default shares expiration</label>
<div class="col-sm-10">
<input type="number" class="form-control" id="idDefaultSharesExpiration" name="default_shares_expiration"
value="{{.User.Filters.DefaultSharesExpiration}}" min="0" aria-describedby="defaultSharesExpirationHelpBlock">
<small id="defaultSharesExpirationHelpBlock" class="form-text text-muted">
Default expiration for newly created shares as number of days
</small>
</div>
</div>
<div class="form-group row">
<label for="idHooks" class="col-sm-2 col-form-label">Hooks</label>
<div class="col-sm-10">