Compare commits
17 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cc381443be | ||
![]() |
89a251d640 | ||
![]() |
dbbae3129d | ||
![]() |
c457538280 | ||
![]() |
7f65aa1fa4 | ||
![]() |
abac3cfc8d | ||
![]() |
a805a930e8 | ||
![]() |
de72495092 | ||
![]() |
7c845f07d5 | ||
![]() |
b9ace46180 | ||
![]() |
e446e3392d | ||
![]() |
a503feaab6 | ||
![]() |
cba894987c | ||
![]() |
1d120bdd26 | ||
![]() |
7245710b31 | ||
![]() |
3a3df5670d | ||
![]() |
97bbf37af4 |
38 changed files with 1555 additions and 1133 deletions
4
.github/workflows/development.yml
vendored
4
.github/workflows/development.yml
vendored
|
@ -2,7 +2,7 @@ name: CI
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [2.5.x]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
@ -268,7 +268,7 @@ jobs:
|
|||
MYSQL_USER: sftpgo
|
||||
MYSQL_PASSWORD: sftpgo
|
||||
options: >-
|
||||
--health-cmd "mysqladmin status -h 127.0.0.1 -P 3306 -u root -p$MYSQL_ROOT_PASSWORD"
|
||||
--health-cmd "mariadb-admin status -h 127.0.0.1 -P 3306 -u root -p$MYSQL_ROOT_PASSWORD"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 6
|
||||
|
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
@ -5,7 +5,7 @@ on:
|
|||
# - cron: '0 4 * * *' # everyday at 4:00 AM UTC
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 2.5.x
|
||||
tags:
|
||||
- v*
|
||||
pull_request:
|
||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -5,7 +5,7 @@ on:
|
|||
tags: 'v*'
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.20.5
|
||||
GO_VERSION: 1.20.6
|
||||
|
||||
jobs:
|
||||
prepare-sources-with-deps:
|
||||
|
|
|
@ -22,7 +22,7 @@ I'd like to make SFTPGo into a sustainable long term project and would not like
|
|||
If you use SFTPGo, it is in your best interest to ensure that the project you rely on stays healthy and well maintained.
|
||||
This can only happen with your donations and [sponsorships](https://github.com/sponsors/drakkan) :heart:
|
||||
|
||||
You can also purchase support plans from the [SFTPGo website](https://sftpgo.com/#pricing).
|
||||
You can also purchase, using many payment methods, support plans from the [SFTPGo website](https://sftpgo.com/#pricing).
|
||||
|
||||
With sponsorships/donations or support plans we establish a channel for reciprocal access, ensuring better outcomes for both you and the project.
|
||||
|
||||
|
@ -67,6 +67,7 @@ If you report an invalid issue or ask for step-by-step support, your issue will
|
|||
- Partial authentication. You can configure multi-step authentication requiring, for example, the user password after successful public key authentication.
|
||||
- Per-user authentication methods.
|
||||
- [Two-factor authentication](./docs/howto/two-factor-authentication.md) based on time-based one time passwords (RFC 6238) which works with Authy, Google Authenticator, Microsoft Authenticator and other compatible apps.
|
||||
- LDAP/Active Directory authentication using a [plugin](https://github.com/sftpgo/sftpgo-plugin-auth).
|
||||
- Simplified user administrations using [groups](./docs/groups.md).
|
||||
- [Roles](./docs/roles.md) allow to create limited administrators who can only create and manage users with their role.
|
||||
- Custom authentication via [external programs/HTTP API](./docs/external-auth.md).
|
||||
|
|
|
@ -4,11 +4,11 @@ SFTPGo provides an official Docker image, it is available on both [Docker Hub](h
|
|||
|
||||
## Supported tags and respective Dockerfile links
|
||||
|
||||
- [v2.5.1, v2.5, v2, latest](https://github.com/drakkan/sftpgo/blob/v2.5.1/Dockerfile)
|
||||
- [v2.5.1-plugins, v2.5-plugins, v2-plugins, plugins](https://github.com/drakkan/sftpgo/blob/v2.5.1/Dockerfile)
|
||||
- [v2.5.1-alpine, v2.5-alpine, v2-alpine, alpine](https://github.com/drakkan/sftpgo/blob/v2.5.1/Dockerfile.alpine)
|
||||
- [v2.5.1-slim, v2.5-slim, v2-slim, slim](https://github.com/drakkan/sftpgo/blob/v2.5.1/Dockerfile)
|
||||
- [v2.5.1-alpine-slim, v2.5-alpine-slim, v2-alpine-slim, alpine-slim](https://github.com/drakkan/sftpgo/blob/v2.5.1/Dockerfile.alpine)
|
||||
- [v2.5.4, v2.5, v2, latest](https://github.com/drakkan/sftpgo/blob/v2.5.4/Dockerfile)
|
||||
- [v2.5.4-plugins, v2.5-plugins, v2-plugins, plugins](https://github.com/drakkan/sftpgo/blob/v2.5.4/Dockerfile)
|
||||
- [v2.5.4-alpine, v2.5-alpine, v2-alpine, alpine](https://github.com/drakkan/sftpgo/blob/v2.5.4/Dockerfile.alpine)
|
||||
- [v2.5.4-slim, v2.5-slim, v2-slim, slim](https://github.com/drakkan/sftpgo/blob/v2.5.4/Dockerfile)
|
||||
- [v2.5.4-alpine-slim, v2.5-alpine-slim, v2-alpine-slim, alpine-slim](https://github.com/drakkan/sftpgo/blob/v2.5.4/Dockerfile.alpine)
|
||||
- [edge](../Dockerfile)
|
||||
- [edge-plugins](../Dockerfile)
|
||||
- [edge-alpine](../Dockerfile.alpine)
|
||||
|
|
|
@ -17,7 +17,7 @@ esac
|
|||
|
||||
echo "download plugins for arch ${SUFFIX}"
|
||||
|
||||
for PLUGIN in geoipfilter kms pubsub eventstore eventsearch metadata
|
||||
for PLUGIN in geoipfilter kms pubsub eventstore eventsearch metadata auth
|
||||
do
|
||||
echo "download plugin from https://github.com/sftpgo/sftpgo-plugin-${PLUGIN}/releases/latest/download/sftpgo-plugin-${PLUGIN}-linux-${SUFFIX}"
|
||||
curl -L "https://github.com/sftpgo/sftpgo-plugin-${PLUGIN}/releases/latest/download/sftpgo-plugin-${PLUGIN}-linux-${SUFFIX}" --output "/usr/local/bin/sftpgo-plugin-${PLUGIN}"
|
||||
|
|
120
go.mod
120
go.mod
|
@ -3,32 +3,32 @@ module github.com/drakkan/sftpgo/v2
|
|||
go 1.20
|
||||
|
||||
require (
|
||||
cloud.google.com/go/storage v1.30.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0
|
||||
cloud.google.com/go/storage v1.31.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0
|
||||
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5
|
||||
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736
|
||||
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.25
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.24
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.67
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.11
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.1
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.19.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.19.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.28
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.27
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.72
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.14
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.11
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.3.4
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.3.5
|
||||
github.com/coreos/go-oidc/v3 v3.6.0
|
||||
github.com/drakkan/webdav v0.0.0-20230227175313-32996838bcd8
|
||||
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
|
||||
github.com/fclairamb/ftpserverlib v0.21.0
|
||||
github.com/fclairamb/go-log v0.4.1
|
||||
github.com/go-acme/lego/v4 v4.12.1
|
||||
github.com/go-chi/chi/v5 v5.0.9-0.20230502103705-7f280968675b
|
||||
github.com/go-chi/jwtauth/v5 v5.1.0
|
||||
github.com/go-chi/render v1.0.2
|
||||
github.com/go-acme/lego/v4 v4.12.3
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/jwtauth/v5 v5.1.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
|
@ -36,66 +36,66 @@ require (
|
|||
github.com/hashicorp/go-hclog v1.5.0
|
||||
github.com/hashicorp/go-plugin v1.4.10
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4
|
||||
github.com/jackc/pgx/v5 v5.3.2-0.20230603125928-d9560c78b8e6
|
||||
github.com/jackc/pgx/v5 v5.4.2
|
||||
github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126
|
||||
github.com/klauspost/compress v1.16.5
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.9
|
||||
github.com/klauspost/compress v1.16.7
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.11
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
github.com/mhale/smtpd v0.8.0
|
||||
github.com/minio/sio v0.3.1
|
||||
github.com/otiai10/copy v1.11.0
|
||||
github.com/otiai10/copy v1.12.0
|
||||
github.com/pires/go-proxyproto v0.7.0
|
||||
github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.15.1
|
||||
github.com/prometheus/client_golang v1.16.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/rs/cors v1.9.0
|
||||
github.com/rs/xid v1.5.0
|
||||
github.com/rs/zerolog v1.29.1
|
||||
github.com/sftpgo/sdk v0.1.5-0.20230524172149-afb96ebee860
|
||||
github.com/shirou/gopsutil/v3 v3.23.5
|
||||
github.com/sftpgo/sdk v0.1.5
|
||||
github.com/shirou/gopsutil/v3 v3.23.6
|
||||
github.com/spf13/afero v1.9.5
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/viper v1.16.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2
|
||||
github.com/studio-b12/gowebdav v0.9.0
|
||||
github.com/subosito/gotenv v1.4.2
|
||||
github.com/unrolled/secure v1.13.0
|
||||
github.com/wagslane/go-password-validator v0.3.0
|
||||
github.com/wneessen/go-mail v0.3.10-0.20230531074101-4100fef083df
|
||||
github.com/wneessen/go-mail v0.4.0
|
||||
github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
go.uber.org/automaxprocs v1.5.2
|
||||
gocloud.dev v0.29.0
|
||||
golang.org/x/crypto v0.9.0
|
||||
golang.org/x/net v0.10.0
|
||||
golang.org/x/oauth2 v0.8.0
|
||||
golang.org/x/sys v0.8.0
|
||||
golang.org/x/term v0.8.0
|
||||
gocloud.dev v0.30.0
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/net v0.12.0
|
||||
golang.org/x/oauth2 v0.10.0
|
||||
golang.org/x/sys v0.10.0
|
||||
golang.org/x/term v0.10.0
|
||||
golang.org/x/time v0.3.0
|
||||
google.golang.org/api v0.125.0
|
||||
google.golang.org/api v0.131.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.2 // indirect
|
||||
cloud.google.com/go/compute v1.20.0 // indirect
|
||||
cloud.google.com/go v0.110.6 // indirect
|
||||
cloud.google.com/go/compute v1.21.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.1.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.30 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect
|
||||
github.com/aws/smithy-go v1.13.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
|
@ -114,8 +114,8 @@ require (
|
|||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/s2a-go v0.1.4 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.10.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
|
@ -135,18 +135,19 @@ require (
|
|||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/miekg/dns v1.1.54 // indirect
|
||||
github.com/miekg/dns v1.1.55 // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.10.1 // indirect
|
||||
github.com/prometheus/procfs v0.11.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
|
@ -155,22 +156,23 @@ require (
|
|||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/tools v0.9.3 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/tools v0.11.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/grpc v1.55.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||
google.golang.org/grpc v1.56.2 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/fclairamb/ftpserverlib => github.com/drakkan/ftpserverlib v0.0.0-20230714144823-d8aff325a796
|
||||
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
|
||||
github.com/robfig/cron/v3 => github.com/drakkan/cron/v3 v3.0.0-20230222140221-217a1e4d96c0
|
||||
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20230608154636-e9d673c2a1a8
|
||||
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20230614155948-29e7be6c0fab
|
||||
)
|
||||
|
|
|
@ -1316,8 +1316,7 @@ func (c *BaseConnection) GetTransferQuota() dataprovider.TransferQuota {
|
|||
}
|
||||
|
||||
func (c *BaseConnection) checkUserQuota() (dataprovider.TransferQuota, int, int64) {
|
||||
clientIP := c.GetRemoteIP()
|
||||
ul, dl, total := c.User.GetDataTransferLimits(clientIP)
|
||||
ul, dl, total := c.User.GetDataTransferLimits()
|
||||
result := dataprovider.TransferQuota{
|
||||
ULSize: ul,
|
||||
DLSize: dl,
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/rs/xid"
|
||||
"github.com/sftpgo/sdk"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
"github.com/drakkan/sftpgo/v2/internal/kms"
|
||||
|
@ -647,7 +648,7 @@ func TestFsFileCopier(t *testing.T) {
|
|||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestFilterListDirs(t *testing.T) {
|
||||
func TestFilePatterns(t *testing.T) {
|
||||
filters := dataprovider.UserFilters{
|
||||
BaseUserFilters: sdk.BaseUserFilters{
|
||||
FilePatterns: []sdk.PatternsFilter{
|
||||
|
@ -661,6 +662,16 @@ func TestFilterListDirs(t *testing.T) {
|
|||
DenyPolicy: sdk.DenyPolicyHide,
|
||||
AllowedPatterns: []string{"*.jpg"},
|
||||
},
|
||||
{
|
||||
Path: "/dir3",
|
||||
DenyPolicy: sdk.DenyPolicyDefault,
|
||||
DeniedPatterns: []string{"*.jpg"},
|
||||
},
|
||||
{
|
||||
Path: "/dir4",
|
||||
DenyPolicy: sdk.DenyPolicyHide,
|
||||
DeniedPatterns: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -693,13 +704,90 @@ func TestFilterListDirs(t *testing.T) {
|
|||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
}
|
||||
|
||||
// dirContents are modified in place, we need to redefine them each time
|
||||
filtered := user.FilterListDir(dirContents, "/dir1")
|
||||
assert.Len(t, filtered, 5)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir1/vdir1")
|
||||
assert.Len(t, filtered, 2)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir2/vdir2")
|
||||
require.Len(t, filtered, 1)
|
||||
assert.Equal(t, "file1.jpg", filtered[0].Name())
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir2/vdir2/sub")
|
||||
require.Len(t, filtered, 1)
|
||||
assert.Equal(t, "file1.jpg", filtered[0].Name())
|
||||
|
||||
res, _ := user.IsFileAllowed("/dir1/vdir1/file.txt")
|
||||
assert.False(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir1/vdir1/sub/file.txt")
|
||||
assert.False(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir1/vdir1/file.jpg")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir1/vdir1/sub/file.jpg")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/file.jpg")
|
||||
assert.False(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/dir1/file.jpg")
|
||||
assert.False(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/dir1/sub/file.jpg")
|
||||
assert.False(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir4/file.jpg")
|
||||
assert.False(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir4/dir1/sub/file.jpg")
|
||||
assert.False(t, res)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir4")
|
||||
require.Len(t, filtered, 0)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir4/vdir2/sub")
|
||||
require.Len(t, filtered, 0)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
}
|
||||
|
||||
filtered = user.FilterListDir(dirContents, "/dir2")
|
||||
assert.Len(t, filtered, 2)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
}
|
||||
|
||||
filtered = user.FilterListDir(dirContents, "/dir4")
|
||||
assert.Len(t, filtered, 0)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
}
|
||||
|
||||
filtered = user.FilterListDir(dirContents, "/dir4/sub")
|
||||
assert.Len(t, filtered, 0)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("vdir3.jpg", false, 123, time.Now(), false),
|
||||
|
@ -708,11 +796,6 @@ func TestFilterListDirs(t *testing.T) {
|
|||
filtered = user.FilterListDir(dirContents, "/dir1")
|
||||
assert.Len(t, filtered, 5)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("vdir3.jpg", false, 123, time.Now(), false),
|
||||
}
|
||||
|
||||
filtered = user.FilterListDir(dirContents, "/dir2")
|
||||
if assert.Len(t, filtered, 1) {
|
||||
assert.True(t, filtered[0].IsDir())
|
||||
|
@ -799,7 +882,155 @@ func TestFilterListDirs(t *testing.T) {
|
|||
dirContents = append(dirContents, vfs.NewFileInfo("file.jpg", false, 123, time.Now(), false))
|
||||
|
||||
filtered = user.FilterListDir(dirContents, "/dir3")
|
||||
if assert.Len(t, filtered, 1) {
|
||||
assert.Equal(t, "ic35", filtered[0].Name())
|
||||
require.Len(t, filtered, 1)
|
||||
assert.Equal(t, "ic35", filtered[0].Name())
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir3/ic36")
|
||||
require.Len(t, filtered, 0)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir3/ic35")
|
||||
require.Len(t, filtered, 3)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir3/ic35/sub")
|
||||
require.Len(t, filtered, 3)
|
||||
|
||||
res, _ = user.IsFileAllowed("/dir3/file.txt")
|
||||
assert.False(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35a")
|
||||
assert.False(t, res)
|
||||
res, policy := user.IsFileAllowed("/dir3/ic35a/file")
|
||||
assert.False(t, res)
|
||||
assert.Equal(t, sdk.DenyPolicyHide, policy)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/file.jpg")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/file.txt")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub/file.txt")
|
||||
assert.True(t, res)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir3/ic35/sub")
|
||||
require.Len(t, filtered, 3)
|
||||
|
||||
user.Filters.FilePatterns = append(user.Filters.FilePatterns, sdk.PatternsFilter{
|
||||
Path: "/dir3/ic35/sub1",
|
||||
AllowedPatterns: []string{"*.jpg"},
|
||||
DenyPolicy: sdk.DenyPolicyDefault,
|
||||
})
|
||||
user.Filters.FilePatterns = append(user.Filters.FilePatterns, sdk.PatternsFilter{
|
||||
Path: "/dir3/ic35/sub2",
|
||||
DeniedPatterns: []string{"*.jpg"},
|
||||
DenyPolicy: sdk.DenyPolicyHide,
|
||||
})
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir3/ic35/sub1")
|
||||
require.Len(t, filtered, 3)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir3/ic35/sub2")
|
||||
require.Len(t, filtered, 2)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir3/ic35/sub2/sub1")
|
||||
require.Len(t, filtered, 2)
|
||||
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/file.jpg")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/file.txt")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub/dir/file.txt")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub/dir/file.jpg")
|
||||
assert.True(t, res)
|
||||
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub1/file.jpg")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub1/file.txt")
|
||||
assert.False(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub1/sub/file.jpg")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub1/sub2/file.txt")
|
||||
assert.False(t, res)
|
||||
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub2/file.jpg")
|
||||
assert.False(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub2/file.txt")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub2/sub/file.jpg")
|
||||
assert.False(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub2/sub1/file.txt")
|
||||
assert.True(t, res)
|
||||
|
||||
user.Filters.FilePatterns = append(user.Filters.FilePatterns, sdk.PatternsFilter{
|
||||
Path: "/dir3/ic35",
|
||||
DeniedPatterns: []string{"*.txt"},
|
||||
DenyPolicy: sdk.DenyPolicyHide,
|
||||
})
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/file.jpg")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/file.txt")
|
||||
assert.False(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/adir/sub/file.jpg")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/adir/file.txt")
|
||||
assert.False(t, res)
|
||||
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub2/file.jpg")
|
||||
assert.False(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub2/file.txt")
|
||||
assert.True(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub2/sub/file.jpg")
|
||||
assert.False(t, res)
|
||||
res, _ = user.IsFileAllowed("/dir3/ic35/sub2/sub1/file.txt")
|
||||
assert.True(t, res)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir3/ic35")
|
||||
require.Len(t, filtered, 1)
|
||||
|
||||
dirContents = []os.FileInfo{
|
||||
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
|
||||
vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
|
||||
}
|
||||
filtered = user.FilterListDir(dirContents, "/dir3/ic35/abc")
|
||||
require.Len(t, filtered, 1)
|
||||
}
|
||||
|
|
|
@ -1525,7 +1525,6 @@ func getUserForEventAction(user dataprovider.User) (dataprovider.User, error) {
|
|||
user.Filters.DisableFsChecks = false
|
||||
user.Filters.FilePatterns = nil
|
||||
user.Filters.BandwidthLimits = nil
|
||||
user.Filters.DataTransferLimits = nil
|
||||
for k := range user.Permissions {
|
||||
user.Permissions[k] = []string{dataprovider.PermAny}
|
||||
}
|
||||
|
|
|
@ -1548,12 +1548,6 @@ func TestVirtualFoldersQuotaRenameOverwrite(t *testing.T) {
|
|||
func TestQuotaRenameOverwrite(t *testing.T) {
|
||||
u := getTestUser()
|
||||
u.QuotaFiles = 100
|
||||
u.Filters.DataTransferLimits = []sdk.DataTransferLimit{
|
||||
{
|
||||
Sources: []string{"10.8.0.0/8"},
|
||||
TotalDataTransfer: 1,
|
||||
},
|
||||
}
|
||||
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
conn, client, err := getSftpClient(user)
|
||||
|
|
|
@ -332,41 +332,22 @@ func TestFTPMode(t *testing.T) {
|
|||
func TestTransferQuota(t *testing.T) {
|
||||
user := dataprovider.User{
|
||||
BaseUser: sdk.BaseUser{
|
||||
TotalDataTransfer: -1,
|
||||
UploadDataTransfer: -1,
|
||||
DownloadDataTransfer: -1,
|
||||
TotalDataTransfer: 3,
|
||||
UploadDataTransfer: 2,
|
||||
DownloadDataTransfer: 1,
|
||||
},
|
||||
}
|
||||
user.Filters.DataTransferLimits = []sdk.DataTransferLimit{
|
||||
{
|
||||
Sources: []string{"127.0.0.1/32", "192.168.1.0/24"},
|
||||
TotalDataTransfer: 100,
|
||||
UploadDataTransfer: 0,
|
||||
DownloadDataTransfer: 0,
|
||||
},
|
||||
{
|
||||
Sources: []string{"172.16.0.0/24"},
|
||||
TotalDataTransfer: 0,
|
||||
UploadDataTransfer: 120,
|
||||
DownloadDataTransfer: 150,
|
||||
},
|
||||
}
|
||||
ul, dl, total := user.GetDataTransferLimits("127.0.1.1")
|
||||
ul, dl, total := user.GetDataTransferLimits()
|
||||
assert.Equal(t, int64(2*1048576), ul)
|
||||
assert.Equal(t, int64(1*1048576), dl)
|
||||
assert.Equal(t, int64(3*1048576), total)
|
||||
user.TotalDataTransfer = -1
|
||||
user.UploadDataTransfer = -1
|
||||
user.DownloadDataTransfer = -1
|
||||
ul, dl, total = user.GetDataTransferLimits()
|
||||
assert.Equal(t, int64(0), ul)
|
||||
assert.Equal(t, int64(0), dl)
|
||||
assert.Equal(t, int64(0), total)
|
||||
ul, dl, total = user.GetDataTransferLimits("127.0.0.1")
|
||||
assert.Equal(t, int64(0), ul)
|
||||
assert.Equal(t, int64(0), dl)
|
||||
assert.Equal(t, int64(100*1048576), total)
|
||||
ul, dl, total = user.GetDataTransferLimits("192.168.1.4")
|
||||
assert.Equal(t, int64(0), ul)
|
||||
assert.Equal(t, int64(0), dl)
|
||||
assert.Equal(t, int64(100*1048576), total)
|
||||
ul, dl, total = user.GetDataTransferLimits("172.16.0.2")
|
||||
assert.Equal(t, int64(120*1048576), ul)
|
||||
assert.Equal(t, int64(150*1048576), dl)
|
||||
assert.Equal(t, int64(0), total)
|
||||
transferQuota := dataprovider.TransferQuota{}
|
||||
assert.True(t, transferQuota.HasDownloadSpace())
|
||||
assert.True(t, transferQuota.HasUploadSpace())
|
||||
|
|
|
@ -61,7 +61,7 @@ type baseTransferChecker struct {
|
|||
func (t *baseTransferChecker) isDataTransferExceeded(user dataprovider.User, transfer dataprovider.ActiveTransfer, ulSize,
|
||||
dlSize int64,
|
||||
) bool {
|
||||
ulQuota, dlQuota, totalQuota := user.GetDataTransferLimits(transfer.IP)
|
||||
ulQuota, dlQuota, totalQuota := user.GetDataTransferLimits()
|
||||
if totalQuota > 0 {
|
||||
allowedSize := totalQuota - (user.UsedUploadDataTransfer + user.UsedDownloadDataTransfer)
|
||||
if ulSize+dlSize > allowedSize {
|
||||
|
|
|
@ -622,17 +622,6 @@ func TestGetUsersForQuotaCheck(t *testing.T) {
|
|||
QuotaSize: 100,
|
||||
},
|
||||
},
|
||||
Filters: dataprovider.UserFilters{
|
||||
BaseUserFilters: sdk.BaseUserFilters{
|
||||
DataTransferLimits: []sdk.DataTransferLimit{
|
||||
{
|
||||
Sources: []string{"172.16.0.0/16"},
|
||||
UploadDataTransfer: 50,
|
||||
DownloadDataTransfer: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err = dataprovider.AddUser(&user, "", "", "")
|
||||
assert.NoError(t, err)
|
||||
|
@ -660,14 +649,10 @@ func TestGetUsersForQuotaCheck(t *testing.T) {
|
|||
assert.Len(t, user.VirtualFolders, 0, user.Username)
|
||||
}
|
||||
}
|
||||
ul, dl, total := user.GetDataTransferLimits("127.1.1.1")
|
||||
ul, dl, total := user.GetDataTransferLimits()
|
||||
assert.Equal(t, int64(0), ul)
|
||||
assert.Equal(t, int64(0), dl)
|
||||
assert.Equal(t, int64(0), total)
|
||||
ul, dl, total = user.GetDataTransferLimits("172.16.2.3")
|
||||
assert.Equal(t, int64(50*1024*1024), ul)
|
||||
assert.Equal(t, int64(80*1024*1024), dl)
|
||||
assert.Equal(t, int64(0), total)
|
||||
}
|
||||
|
||||
for i := 0; i < 40; i++ {
|
||||
|
|
|
@ -1961,6 +1961,11 @@ func getCommandConfigsFromEnv(idx int) {
|
|||
cfg.Env = env
|
||||
}
|
||||
|
||||
args, ok := lookupStringListFromEnv(fmt.Sprintf("SFTPGO_COMMAND__COMMANDS__%v__ARGS", idx))
|
||||
if ok {
|
||||
cfg.Args = args
|
||||
}
|
||||
|
||||
if cfg.Path != "" {
|
||||
if len(globalConf.CommandConfig.Commands) > idx {
|
||||
globalConf.CommandConfig.Commands[idx] = cfg
|
||||
|
|
|
@ -924,13 +924,17 @@ func TestCommandsFromEnv(t *testing.T) {
|
|||
os.Setenv("SFTPGO_COMMAND__COMMANDS__1__PATH", "cmd2")
|
||||
os.Setenv("SFTPGO_COMMAND__COMMANDS__1__TIMEOUT", "20")
|
||||
os.Setenv("SFTPGO_COMMAND__COMMANDS__1__ENV", "e=f")
|
||||
os.Setenv("SFTPGO_COMMAND__COMMANDS__1__ARGS", "arg1, arg2")
|
||||
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv("SFTPGO_COMMAND__TIMEOUT")
|
||||
os.Unsetenv("SFTPGO_COMMAND__ENV")
|
||||
os.Unsetenv("SFTPGO_COMMAND__COMMANDS__0__PATH")
|
||||
os.Unsetenv("SFTPGO_COMMAND__COMMANDS__0__TIMEOUT")
|
||||
os.Unsetenv("SFTPGO_COMMAND__COMMANDS__0__ENV")
|
||||
os.Unsetenv("SFTPGO_COMMAND__COMMANDS__1__PATH")
|
||||
os.Unsetenv("SFTPGO_COMMAND__COMMANDS__1__TIMEOUT")
|
||||
os.Unsetenv("SFTPGO_COMMAND__COMMANDS__1__ENV")
|
||||
os.Unsetenv("SFTPGO_COMMAND__COMMANDS__1__ARGS")
|
||||
})
|
||||
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
|
@ -945,6 +949,7 @@ func TestCommandsFromEnv(t *testing.T) {
|
|||
require.Equal(t, "cmd2", commandConfig.Commands[1].Path)
|
||||
require.Equal(t, 20, commandConfig.Commands[1].Timeout)
|
||||
require.Equal(t, []string{"e=f"}, commandConfig.Commands[1].Env)
|
||||
require.Equal(t, []string{"arg1", "arg2"}, commandConfig.Commands[1].Args)
|
||||
|
||||
err = os.Remove(configFilePath)
|
||||
assert.NoError(t, err)
|
||||
|
|
|
@ -2585,18 +2585,6 @@ func copyBaseUserFilters(in sdk.BaseUserFilters) sdk.BaseUserFilters {
|
|||
copy(bwLimit.Sources, limit.Sources)
|
||||
filters.BandwidthLimits = append(filters.BandwidthLimits, bwLimit)
|
||||
}
|
||||
filters.DataTransferLimits = make([]sdk.DataTransferLimit, 0, len(in.DataTransferLimits))
|
||||
for _, limit := range in.DataTransferLimits {
|
||||
dtLimit := sdk.DataTransferLimit{
|
||||
UploadDataTransfer: limit.UploadDataTransfer,
|
||||
DownloadDataTransfer: limit.DownloadDataTransfer,
|
||||
TotalDataTransfer: limit.TotalDataTransfer,
|
||||
Sources: make([]string, 0, len(limit.Sources)),
|
||||
}
|
||||
dtLimit.Sources = make([]string, len(limit.Sources))
|
||||
copy(dtLimit.Sources, limit.Sources)
|
||||
filters.DataTransferLimits = append(filters.DataTransferLimits, dtLimit)
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
|
@ -2943,26 +2931,6 @@ func validateBandwidthLimitsFilter(filters *sdk.BaseUserFilters) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateTransferLimitsFilter(filters *sdk.BaseUserFilters) error {
|
||||
for idx, limit := range filters.DataTransferLimits {
|
||||
filters.DataTransferLimits[idx].Sources = util.RemoveDuplicates(limit.Sources, false)
|
||||
if len(limit.Sources) == 0 {
|
||||
return util.NewValidationError("no data transfer limit source specified")
|
||||
}
|
||||
for _, source := range limit.Sources {
|
||||
_, _, err := net.ParseCIDR(source)
|
||||
if err != nil {
|
||||
return util.NewValidationError(fmt.Sprintf("could not parse data transfer limit source %q: %v", source, err))
|
||||
}
|
||||
}
|
||||
if limit.TotalDataTransfer > 0 {
|
||||
filters.DataTransferLimits[idx].UploadDataTransfer = 0
|
||||
filters.DataTransferLimits[idx].DownloadDataTransfer = 0
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateFiltersValues(filters *sdk.BaseUserFilters) {
|
||||
if filters.StartDirectory != "" {
|
||||
filters.StartDirectory = util.CleanPath(filters.StartDirectory)
|
||||
|
@ -2998,9 +2966,6 @@ func validateBaseFilters(filters *sdk.BaseUserFilters) error {
|
|||
if err := validateBandwidthLimitsFilter(filters); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateTransferLimitsFilter(filters); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(filters.DeniedLoginMethods) >= len(ValidLoginMethods) {
|
||||
return util.NewValidationError("invalid denied_login_methods")
|
||||
}
|
||||
|
@ -3525,7 +3490,7 @@ func doBuiltinKeyboardInteractiveAuth(user *User, client ssh.KeyboardInteractive
|
|||
return 0, err
|
||||
}
|
||||
if len(answers) != 1 {
|
||||
return 0, fmt.Errorf("unexpected number of answers: %v", len(answers))
|
||||
return 0, fmt.Errorf("unexpected number of answers: %d", len(answers))
|
||||
}
|
||||
err = user.LoadAndApplyGroupSettings()
|
||||
if err != nil {
|
||||
|
@ -3535,16 +3500,20 @@ func doBuiltinKeyboardInteractiveAuth(user *User, client ssh.KeyboardInteractive
|
|||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return checkKeyboardInteractiveSecondFactor(user, client, protocol)
|
||||
}
|
||||
|
||||
func checkKeyboardInteractiveSecondFactor(user *User, client ssh.KeyboardInteractiveChallenge, protocol string) (int, error) {
|
||||
if !user.Filters.TOTPConfig.Enabled || !util.Contains(user.Filters.TOTPConfig.Protocols, protocolSSH) {
|
||||
return 1, nil
|
||||
}
|
||||
err = user.Filters.TOTPConfig.Secret.TryDecrypt()
|
||||
err := user.Filters.TOTPConfig.Secret.TryDecrypt()
|
||||
if err != nil {
|
||||
providerLog(logger.LevelError, "unable to decrypt TOTP secret for user %q, protocol %v, err: %v",
|
||||
user.Username, protocol, err)
|
||||
return 0, err
|
||||
}
|
||||
answers, err = client("", "", []string{"Authentication code: "}, []bool{false})
|
||||
answers, err := client("", "", []string{"Authentication code: "}, []bool{false})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -3777,6 +3746,9 @@ func doKeyboardInteractiveAuth(user *User, authHook string, client ssh.KeyboardI
|
|||
var err error
|
||||
if plugin.Handler.HasAuthScope(plugin.AuthScopeKeyboardInteractive) {
|
||||
authResult, err = executeKeyboardInteractivePlugin(user, client, ip, protocol)
|
||||
if authResult == 1 && err == nil {
|
||||
authResult, err = checkKeyboardInteractiveSecondFactor(user, client, protocol)
|
||||
}
|
||||
} else if authHook != "" {
|
||||
if strings.HasPrefix(authHook, "http") {
|
||||
authResult, err = executeKeyboardInteractiveHTTPHook(user, authHook, client, ip, protocol)
|
||||
|
|
|
@ -981,9 +981,14 @@ func (u *User) getPatternsFilterForPath(virtualPath string) sdk.PatternsFilter {
|
|||
return filter
|
||||
}
|
||||
dirsForPath := util.GetDirsForVirtualPath(virtualPath)
|
||||
for _, dir := range dirsForPath {
|
||||
for idx, dir := range dirsForPath {
|
||||
for _, f := range u.Filters.FilePatterns {
|
||||
if f.Path == dir {
|
||||
if idx > 0 && len(f.AllowedPatterns) > 0 && len(f.DeniedPatterns) > 0 && f.DeniedPatterns[0] == "*" {
|
||||
if f.CheckAllowed(path.Base(dirsForPath[idx-1])) {
|
||||
return filter
|
||||
}
|
||||
}
|
||||
filter = f
|
||||
break
|
||||
}
|
||||
|
@ -1004,7 +1009,7 @@ func (u *User) isDirHidden(virtualPath string) bool {
|
|||
return false
|
||||
}
|
||||
filter := u.getPatternsFilterForPath(dirPath)
|
||||
if filter.DenyPolicy == sdk.DenyPolicyHide {
|
||||
if filter.DenyPolicy == sdk.DenyPolicyHide && filter.Path != dirPath {
|
||||
if !filter.CheckAllowed(path.Base(dirPath)) {
|
||||
return true
|
||||
}
|
||||
|
@ -1289,39 +1294,12 @@ func (u *User) HasQuotaRestrictions() bool {
|
|||
|
||||
// HasTransferQuotaRestrictions returns true if there are any data transfer restrictions
|
||||
func (u *User) HasTransferQuotaRestrictions() bool {
|
||||
if len(u.Filters.DataTransferLimits) > 0 {
|
||||
return true
|
||||
}
|
||||
return u.UploadDataTransfer > 0 || u.TotalDataTransfer > 0 || u.DownloadDataTransfer > 0
|
||||
}
|
||||
|
||||
// GetDataTransferLimits returns upload, download and total data transfer limits
|
||||
func (u *User) GetDataTransferLimits(clientIP string) (int64, int64, int64) {
|
||||
func (u *User) GetDataTransferLimits() (int64, int64, int64) {
|
||||
var total, ul, dl int64
|
||||
if len(u.Filters.DataTransferLimits) > 0 {
|
||||
ip := net.ParseIP(clientIP)
|
||||
if ip != nil {
|
||||
for _, limit := range u.Filters.DataTransferLimits {
|
||||
for _, source := range limit.Sources {
|
||||
_, ipNet, err := net.ParseCIDR(source)
|
||||
if err == nil {
|
||||
if ipNet.Contains(ip) {
|
||||
if limit.TotalDataTransfer > 0 {
|
||||
total = limit.TotalDataTransfer * 1048576
|
||||
}
|
||||
if limit.DownloadDataTransfer > 0 {
|
||||
dl = limit.DownloadDataTransfer * 1048576
|
||||
}
|
||||
if limit.UploadDataTransfer > 0 {
|
||||
ul = limit.UploadDataTransfer * 1048576
|
||||
}
|
||||
return ul, dl, total
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if u.TotalDataTransfer > 0 {
|
||||
total = u.TotalDataTransfer * 1048576
|
||||
}
|
||||
|
@ -1825,7 +1803,6 @@ func (u *User) mergeAdditiveProperties(group *Group, groupType int, replacer *st
|
|||
u.mergePermissions(group, groupType, replacer)
|
||||
u.mergeFilePatterns(group, groupType, replacer)
|
||||
u.Filters.BandwidthLimits = append(u.Filters.BandwidthLimits, group.UserSettings.Filters.BandwidthLimits...)
|
||||
u.Filters.DataTransferLimits = append(u.Filters.DataTransferLimits, group.UserSettings.Filters.DataTransferLimits...)
|
||||
u.Filters.AllowedIP = append(u.Filters.AllowedIP, group.UserSettings.Filters.AllowedIP...)
|
||||
u.Filters.DeniedIP = append(u.Filters.DeniedIP, group.UserSettings.Filters.DeniedIP...)
|
||||
u.Filters.DeniedLoginMethods = append(u.Filters.DeniedLoginMethods, group.UserSettings.Filters.DeniedLoginMethods...)
|
||||
|
|
|
@ -170,7 +170,7 @@ func searchFsEvents(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(data) //nolint:errcheck
|
||||
}
|
||||
|
||||
|
@ -205,7 +205,7 @@ func searchProviderEvents(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(data) //nolint:errcheck
|
||||
}
|
||||
|
||||
|
@ -238,7 +238,7 @@ func searchLogEvents(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(data) //nolint:errcheck
|
||||
}
|
||||
|
||||
|
|
|
@ -2627,103 +2627,6 @@ func TestEventRuleValidation(t *testing.T) {
|
|||
assert.Contains(t, string(resp), "invalid Identity Provider login event")
|
||||
}
|
||||
|
||||
func TestUserTransferLimits(t *testing.T) {
|
||||
u := getTestUser()
|
||||
u.TotalDataTransfer = 100
|
||||
u.Filters.DataTransferLimits = []sdk.DataTransferLimit{
|
||||
{
|
||||
Sources: nil,
|
||||
},
|
||||
}
|
||||
_, resp, err := httpdtest.AddUser(u, http.StatusBadRequest)
|
||||
assert.NoError(t, err, string(resp))
|
||||
assert.Contains(t, string(resp), "Validation error: no data transfer limit source specified")
|
||||
u.Filters.DataTransferLimits = []sdk.DataTransferLimit{
|
||||
{
|
||||
Sources: []string{"a"},
|
||||
},
|
||||
}
|
||||
_, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
|
||||
assert.NoError(t, err, string(resp))
|
||||
assert.Contains(t, string(resp), "Validation error: could not parse data transfer limit source")
|
||||
u.Filters.DataTransferLimits = []sdk.DataTransferLimit{
|
||||
{
|
||||
Sources: []string{"127.0.0.1/32"},
|
||||
UploadDataTransfer: 120,
|
||||
DownloadDataTransfer: 140,
|
||||
},
|
||||
{
|
||||
Sources: []string{"192.168.0.0/24", "192.168.1.0/24"},
|
||||
TotalDataTransfer: 400,
|
||||
},
|
||||
{
|
||||
Sources: []string{"10.0.0.0/8"},
|
||||
},
|
||||
}
|
||||
user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
|
||||
assert.NoError(t, err, string(resp))
|
||||
assert.Len(t, user.Filters.DataTransferLimits, 3)
|
||||
assert.Equal(t, u.Filters.DataTransferLimits, user.Filters.DataTransferLimits)
|
||||
up, down, total := user.GetDataTransferLimits("1.1.1.1")
|
||||
assert.Equal(t, user.TotalDataTransfer*1024*1024, total)
|
||||
assert.Equal(t, user.UploadDataTransfer*1024*1024, up)
|
||||
assert.Equal(t, user.DownloadDataTransfer*1024*1024, down)
|
||||
up, down, total = user.GetDataTransferLimits("127.0.0.1")
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[0].TotalDataTransfer*1024*1024, total)
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[0].UploadDataTransfer*1024*1024, up)
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[0].DownloadDataTransfer*1024*1024, down)
|
||||
up, down, total = user.GetDataTransferLimits("192.168.1.6")
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[1].TotalDataTransfer*1024*1024, total)
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[1].UploadDataTransfer*1024*1024, up)
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[1].DownloadDataTransfer*1024*1024, down)
|
||||
up, down, total = user.GetDataTransferLimits("10.1.2.3")
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[2].TotalDataTransfer*1024*1024, total)
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[2].UploadDataTransfer*1024*1024, up)
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[2].DownloadDataTransfer*1024*1024, down)
|
||||
|
||||
connID := xid.New().String()
|
||||
localAddr := "::1"
|
||||
conn := common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "1.1.1.2", user)
|
||||
transferQuota := conn.GetTransferQuota()
|
||||
assert.Equal(t, user.TotalDataTransfer*1024*1024, transferQuota.AllowedTotalSize)
|
||||
assert.Equal(t, user.UploadDataTransfer*1024*1024, transferQuota.AllowedULSize)
|
||||
assert.Equal(t, user.DownloadDataTransfer*1024*1024, transferQuota.AllowedDLSize)
|
||||
|
||||
conn = common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "127.0.0.1", user)
|
||||
transferQuota = conn.GetTransferQuota()
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[0].TotalDataTransfer*1024*1024, transferQuota.AllowedTotalSize)
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[0].UploadDataTransfer*1024*1024, transferQuota.AllowedULSize)
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[0].DownloadDataTransfer*1024*1024, transferQuota.AllowedDLSize)
|
||||
|
||||
conn = common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "192.168.1.5", user)
|
||||
transferQuota = conn.GetTransferQuota()
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[1].TotalDataTransfer*1024*1024, transferQuota.AllowedTotalSize)
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[1].UploadDataTransfer*1024*1024, transferQuota.AllowedULSize)
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[1].DownloadDataTransfer*1024*1024, transferQuota.AllowedDLSize)
|
||||
|
||||
u.UsedDownloadDataTransfer = 10 * 1024 * 1024
|
||||
u.UsedUploadDataTransfer = 5 * 1024 * 1024
|
||||
_, err = httpdtest.UpdateTransferQuotaUsage(u, "", http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
conn = common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "192.168.1.6", user)
|
||||
transferQuota = conn.GetTransferQuota()
|
||||
assert.Equal(t, (user.Filters.DataTransferLimits[1].TotalDataTransfer-15)*1024*1024, transferQuota.AllowedTotalSize)
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[1].UploadDataTransfer*1024*1024, transferQuota.AllowedULSize)
|
||||
assert.Equal(t, user.Filters.DataTransferLimits[1].DownloadDataTransfer*1024*1024, transferQuota.AllowedDLSize)
|
||||
|
||||
conn = common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "10.8.3.4", user)
|
||||
transferQuota = conn.GetTransferQuota()
|
||||
assert.Equal(t, int64(0), transferQuota.AllowedTotalSize)
|
||||
assert.Equal(t, int64(0), transferQuota.AllowedULSize)
|
||||
assert.Equal(t, int64(0), transferQuota.AllowedDLSize)
|
||||
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUserBandwidthLimits(t *testing.T) {
|
||||
u := getTestUser()
|
||||
u.UploadBandwidth = 128
|
||||
|
@ -19686,49 +19589,6 @@ func TestWebUserAddMock(t *testing.T) {
|
|||
assert.Contains(t, rr.Body.String(), "Validation error: could not parse bandwidth limit source")
|
||||
form.Set("bandwidth_limit_sources1", "127.0.0.1/32")
|
||||
form.Set("upload_bandwidth_source1", "-1")
|
||||
form.Set("data_transfer_limit_sources0", "127.0.1.1")
|
||||
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(), "could not parse data transfer limit source")
|
||||
form.Set("data_transfer_limit_sources0", "127.0.1.1/32")
|
||||
form.Set("upload_data_transfer_source0", "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 upload_data_transfer_source")
|
||||
form.Set("upload_data_transfer_source0", "0")
|
||||
form.Set("download_data_transfer_source0", "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 download_data_transfer_source")
|
||||
form.Set("download_data_transfer_source0", "0")
|
||||
form.Set("total_data_transfer_source0", "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 total_data_transfer_source")
|
||||
form.Set("total_data_transfer_source0", "0")
|
||||
form.Set("data_transfer_limit_sources10", "192.168.5.0/24, 10.8.0.0/16")
|
||||
form.Set("download_data_transfer_source10", "100")
|
||||
form.Set("upload_data_transfer_source10", "120")
|
||||
form.Set("data_transfer_limit_sources12", "192.168.3.0/24, 10.8.2.0/24,::1/64")
|
||||
form.Set("download_data_transfer_source12", "100")
|
||||
form.Set("upload_data_transfer_source12", "120")
|
||||
form.Set("total_data_transfer_source12", "200")
|
||||
// invalid external auth cache size
|
||||
form.Set("external_auth_cache_time", "a")
|
||||
b, contentType, _ = getMultipartFormData(form, "", "")
|
||||
|
@ -19850,30 +19710,6 @@ func TestWebUserAddMock(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if assert.Len(t, newUser.Filters.DataTransferLimits, 3) {
|
||||
for _, dtLimit := range newUser.Filters.DataTransferLimits {
|
||||
switch len(dtLimit.Sources) {
|
||||
case 3:
|
||||
assert.Equal(t, "192.168.3.0/24", dtLimit.Sources[0])
|
||||
assert.Equal(t, "10.8.2.0/24", dtLimit.Sources[1])
|
||||
assert.Equal(t, "::1/64", dtLimit.Sources[2])
|
||||
assert.Equal(t, int64(0), dtLimit.UploadDataTransfer)
|
||||
assert.Equal(t, int64(0), dtLimit.DownloadDataTransfer)
|
||||
assert.Equal(t, int64(200), dtLimit.TotalDataTransfer)
|
||||
case 2:
|
||||
assert.Equal(t, "192.168.5.0/24", dtLimit.Sources[0])
|
||||
assert.Equal(t, "10.8.0.0/16", dtLimit.Sources[1])
|
||||
assert.Equal(t, int64(120), dtLimit.UploadDataTransfer)
|
||||
assert.Equal(t, int64(100), dtLimit.DownloadDataTransfer)
|
||||
assert.Equal(t, int64(0), dtLimit.TotalDataTransfer)
|
||||
case 1:
|
||||
assert.Equal(t, "127.0.1.1/32", dtLimit.Sources[0])
|
||||
assert.Equal(t, int64(0), dtLimit.UploadDataTransfer)
|
||||
assert.Equal(t, int64(0), dtLimit.DownloadDataTransfer)
|
||||
assert.Equal(t, int64(0), dtLimit.TotalDataTransfer)
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.Len(t, newUser.Groups, 3)
|
||||
assert.Equal(t, sdk.TLSUsernameNone, newUser.Filters.TLSUsername)
|
||||
req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, newUser.Username), nil)
|
||||
|
|
|
@ -3365,6 +3365,92 @@ func TestGetLogEventString(t *testing.T) {
|
|||
assert.Empty(t, getLogEventString(0))
|
||||
}
|
||||
|
||||
func TestUserQuotaUsage(t *testing.T) {
|
||||
usage := userQuotaUsage{
|
||||
QuotaSize: 100,
|
||||
}
|
||||
require.True(t, usage.HasQuotaInfo())
|
||||
require.NotEmpty(t, usage.GetQuotaSize())
|
||||
providerConf := dataprovider.GetProviderConfig()
|
||||
quotaTracking := dataprovider.GetQuotaTracking()
|
||||
providerConf.TrackQuota = 0
|
||||
err := dataprovider.Close()
|
||||
assert.NoError(t, err)
|
||||
err = dataprovider.Initialize(providerConf, configDir, true)
|
||||
assert.NoError(t, err)
|
||||
err = dataprovider.Close()
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, usage.HasQuotaInfo())
|
||||
providerConf.TrackQuota = quotaTracking
|
||||
err = dataprovider.Initialize(providerConf, configDir, true)
|
||||
assert.NoError(t, err)
|
||||
usage.QuotaSize = 0
|
||||
assert.False(t, usage.HasQuotaInfo())
|
||||
assert.Empty(t, usage.GetQuotaSize())
|
||||
assert.Equal(t, 0, usage.GetQuotaSizePercentage())
|
||||
assert.False(t, usage.IsQuotaSizeLow())
|
||||
assert.False(t, usage.IsDiskQuotaLow())
|
||||
assert.False(t, usage.IsQuotaLow())
|
||||
usage.UsedQuotaSize = 9
|
||||
assert.NotEmpty(t, usage.GetQuotaSize())
|
||||
usage.QuotaSize = 10
|
||||
assert.True(t, usage.IsQuotaSizeLow())
|
||||
assert.True(t, usage.IsDiskQuotaLow())
|
||||
assert.True(t, usage.IsQuotaLow())
|
||||
usage.DownloadDataTransfer = 1
|
||||
assert.True(t, usage.HasQuotaInfo())
|
||||
assert.True(t, usage.HasTranferQuota())
|
||||
assert.Empty(t, usage.GetQuotaFiles())
|
||||
assert.Equal(t, 0, usage.GetQuotaFilesPercentage())
|
||||
usage.QuotaFiles = 1
|
||||
assert.NotEmpty(t, usage.GetQuotaFiles())
|
||||
usage.QuotaFiles = 0
|
||||
usage.UsedQuotaFiles = 9
|
||||
assert.NotEmpty(t, usage.GetQuotaFiles())
|
||||
usage.QuotaFiles = 10
|
||||
usage.DownloadDataTransfer = 0
|
||||
assert.True(t, usage.IsQuotaFilesLow())
|
||||
assert.True(t, usage.IsDiskQuotaLow())
|
||||
assert.False(t, usage.IsTotalTransferQuotaLow())
|
||||
assert.False(t, usage.IsUploadTransferQuotaLow())
|
||||
assert.False(t, usage.IsDownloadTransferQuotaLow())
|
||||
assert.Equal(t, 0, usage.GetTotalTransferQuotaPercentage())
|
||||
assert.Equal(t, 0, usage.GetUploadTransferQuotaPercentage())
|
||||
assert.Equal(t, 0, usage.GetDownloadTransferQuotaPercentage())
|
||||
assert.Empty(t, usage.GetTotalTransferQuota())
|
||||
assert.Empty(t, usage.GetUploadTransferQuota())
|
||||
assert.Empty(t, usage.GetDownloadTransferQuota())
|
||||
usage.TotalDataTransfer = 3
|
||||
usage.UsedUploadDataTransfer = 1 * 1048576
|
||||
assert.NotEmpty(t, usage.GetTotalTransferQuota())
|
||||
usage.TotalDataTransfer = 0
|
||||
assert.NotEmpty(t, usage.GetTotalTransferQuota())
|
||||
assert.NotEmpty(t, usage.GetUploadTransferQuota())
|
||||
usage.UploadDataTransfer = 2
|
||||
assert.NotEmpty(t, usage.GetUploadTransferQuota())
|
||||
usage.UsedDownloadDataTransfer = 1 * 1048576
|
||||
assert.NotEmpty(t, usage.GetDownloadTransferQuota())
|
||||
usage.DownloadDataTransfer = 2
|
||||
assert.NotEmpty(t, usage.GetDownloadTransferQuota())
|
||||
assert.False(t, usage.IsTransferQuotaLow())
|
||||
usage.UsedDownloadDataTransfer = 8 * 1048576
|
||||
usage.TotalDataTransfer = 10
|
||||
assert.True(t, usage.IsTotalTransferQuotaLow())
|
||||
assert.True(t, usage.IsTransferQuotaLow())
|
||||
usage.TotalDataTransfer = 0
|
||||
usage.UploadDataTransfer = 0
|
||||
usage.DownloadDataTransfer = 0
|
||||
assert.False(t, usage.IsTransferQuotaLow())
|
||||
usage.UploadDataTransfer = 10
|
||||
usage.UsedUploadDataTransfer = 9 * 1048576
|
||||
assert.True(t, usage.IsUploadTransferQuotaLow())
|
||||
assert.True(t, usage.IsTransferQuotaLow())
|
||||
usage.DownloadDataTransfer = 10
|
||||
usage.UsedDownloadDataTransfer = 9 * 1048576
|
||||
assert.True(t, usage.IsDownloadTransferQuotaLow())
|
||||
assert.True(t, usage.IsTransferQuotaLow())
|
||||
}
|
||||
|
||||
func isSharedProviderSupported() bool {
|
||||
// SQLite shares the implementation with other SQL-based provider but it makes no sense
|
||||
// to use it outside test cases
|
||||
|
|
|
@ -1353,50 +1353,6 @@ func getUserPermissionsFromPostFields(r *http.Request) map[string][]string {
|
|||
return permissions
|
||||
}
|
||||
|
||||
func getDataTransferLimitsFromPostFields(r *http.Request) ([]sdk.DataTransferLimit, error) {
|
||||
var result []sdk.DataTransferLimit
|
||||
|
||||
for k := range r.Form {
|
||||
if strings.HasPrefix(k, "data_transfer_limit_sources") {
|
||||
sources := getSliceFromDelimitedValues(r.Form.Get(k), ",")
|
||||
if len(sources) > 0 {
|
||||
dtLimit := sdk.DataTransferLimit{
|
||||
Sources: sources,
|
||||
}
|
||||
idx := strings.TrimPrefix(k, "data_transfer_limit_sources")
|
||||
ul := r.Form.Get(fmt.Sprintf("upload_data_transfer_source%v", idx))
|
||||
dl := r.Form.Get(fmt.Sprintf("download_data_transfer_source%v", idx))
|
||||
total := r.Form.Get(fmt.Sprintf("total_data_transfer_source%v", idx))
|
||||
if ul != "" {
|
||||
dataUL, err := strconv.ParseInt(ul, 10, 64)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("invalid upload_data_transfer_source%v %q: %w", idx, ul, err)
|
||||
}
|
||||
dtLimit.UploadDataTransfer = dataUL
|
||||
}
|
||||
if dl != "" {
|
||||
dataDL, err := strconv.ParseInt(dl, 10, 64)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("invalid download_data_transfer_source%v %q: %w", idx, dl, err)
|
||||
}
|
||||
dtLimit.DownloadDataTransfer = dataDL
|
||||
}
|
||||
if total != "" {
|
||||
dataTotal, err := strconv.ParseInt(total, 10, 64)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("invalid total_data_transfer_source%v %q: %w", idx, total, err)
|
||||
}
|
||||
dtLimit.TotalDataTransfer = dataTotal
|
||||
}
|
||||
|
||||
result = append(result, dtLimit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getBandwidthLimitsFromPostFields(r *http.Request) ([]sdk.BandwidthLimit, error) {
|
||||
var result []sdk.BandwidthLimit
|
||||
|
||||
|
@ -1534,10 +1490,6 @@ func getFiltersFromUserPostFields(r *http.Request) (sdk.BaseUserFilters, error)
|
|||
if err != nil {
|
||||
return filters, err
|
||||
}
|
||||
dtLimits, err := getDataTransferLimitsFromPostFields(r)
|
||||
if err != nil {
|
||||
return filters, err
|
||||
}
|
||||
maxFileSize, err := util.ParseBytes(r.Form.Get("max_upload_file_size"))
|
||||
if err != nil {
|
||||
return filters, fmt.Errorf("invalid max upload file size: %w", err)
|
||||
|
@ -1558,7 +1510,6 @@ func getFiltersFromUserPostFields(r *http.Request) (sdk.BaseUserFilters, error)
|
|||
filters.FTPSecurity = 1
|
||||
}
|
||||
filters.BandwidthLimits = bwLimits
|
||||
filters.DataTransferLimits = dtLimits
|
||||
filters.AllowedIP = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",")
|
||||
filters.DeniedIP = getSliceFromDelimitedValues(r.Form.Get("denied_ip"), ",")
|
||||
filters.DeniedLoginMethods = r.Form["denied_login_methods"]
|
||||
|
@ -1614,7 +1565,7 @@ func getS3Config(r *http.Request) (vfs.S3FsConfig, error) {
|
|||
config.Endpoint = strings.TrimSpace(r.Form.Get("s3_endpoint"))
|
||||
config.StorageClass = strings.TrimSpace(r.Form.Get("s3_storage_class"))
|
||||
config.ACL = strings.TrimSpace(r.Form.Get("s3_acl"))
|
||||
config.KeyPrefix = strings.TrimSpace(r.Form.Get("s3_key_prefix"))
|
||||
config.KeyPrefix = strings.TrimSpace(strings.TrimPrefix(r.Form.Get("s3_key_prefix"), "/"))
|
||||
config.UploadPartSize, err = strconv.ParseInt(r.Form.Get("s3_upload_part_size"), 10, 64)
|
||||
if err != nil {
|
||||
return config, fmt.Errorf("invalid s3 upload part size: %w", err)
|
||||
|
@ -1650,7 +1601,7 @@ func getGCSConfig(r *http.Request) (vfs.GCSFsConfig, error) {
|
|||
config.Bucket = strings.TrimSpace(r.Form.Get("gcs_bucket"))
|
||||
config.StorageClass = strings.TrimSpace(r.Form.Get("gcs_storage_class"))
|
||||
config.ACL = strings.TrimSpace(r.Form.Get("gcs_acl"))
|
||||
config.KeyPrefix = strings.TrimSpace(r.Form.Get("gcs_key_prefix"))
|
||||
config.KeyPrefix = strings.TrimSpace(strings.TrimPrefix(r.Form.Get("gcs_key_prefix"), "/"))
|
||||
uploadPartSize, err := strconv.ParseInt(r.Form.Get("gcs_upload_part_size"), 10, 64)
|
||||
if err == nil {
|
||||
config.UploadPartSize = uploadPartSize
|
||||
|
@ -1732,7 +1683,7 @@ func getAzureConfig(r *http.Request) (vfs.AzBlobFsConfig, error) {
|
|||
config.AccountKey = getSecretFromFormField(r, "az_account_key")
|
||||
config.SASURL = getSecretFromFormField(r, "az_sas_url")
|
||||
config.Endpoint = strings.TrimSpace(r.Form.Get("az_endpoint"))
|
||||
config.KeyPrefix = strings.TrimSpace(r.Form.Get("az_key_prefix"))
|
||||
config.KeyPrefix = strings.TrimSpace(strings.TrimPrefix(r.Form.Get("az_key_prefix"), "/"))
|
||||
config.AccessTier = strings.TrimSpace(r.Form.Get("az_access_tier"))
|
||||
config.UseEmulator = r.Form.Get("az_use_emulator") != ""
|
||||
config.UploadPartSize, err = strconv.ParseInt(r.Form.Get("az_upload_part_size"), 10, 64)
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -155,6 +156,7 @@ type filesPage struct {
|
|||
Error string
|
||||
Paths []dirMapping
|
||||
HasIntegrations bool
|
||||
QuotaUsage *userQuotaUsage
|
||||
}
|
||||
|
||||
type shareLoginPage struct {
|
||||
|
@ -229,6 +231,185 @@ type clientSharePage struct {
|
|||
IsAdd bool
|
||||
}
|
||||
|
||||
type userQuotaUsage struct {
|
||||
QuotaSize int64
|
||||
QuotaFiles int
|
||||
UsedQuotaSize int64
|
||||
UsedQuotaFiles int
|
||||
UploadDataTransfer int64
|
||||
DownloadDataTransfer int64
|
||||
TotalDataTransfer int64
|
||||
UsedUploadDataTransfer int64
|
||||
UsedDownloadDataTransfer int64
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) HasQuotaInfo() bool {
|
||||
if dataprovider.GetQuotaTracking() == 0 {
|
||||
return false
|
||||
}
|
||||
if u.HasDiskQuota() {
|
||||
return true
|
||||
}
|
||||
return u.HasTranferQuota()
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) HasDiskQuota() bool {
|
||||
if u.QuotaSize > 0 || u.UsedQuotaSize > 0 {
|
||||
return true
|
||||
}
|
||||
return u.QuotaFiles > 0 || u.UsedQuotaFiles > 0
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) HasTranferQuota() bool {
|
||||
if u.TotalDataTransfer > 0 || u.UploadDataTransfer > 0 || u.DownloadDataTransfer > 0 {
|
||||
return true
|
||||
}
|
||||
return u.UsedDownloadDataTransfer > 0 || u.UsedUploadDataTransfer > 0
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) GetQuotaSize() string {
|
||||
if u.QuotaSize > 0 {
|
||||
return fmt.Sprintf("%s/%s", util.ByteCountIEC(u.UsedQuotaSize), util.ByteCountIEC(u.QuotaSize))
|
||||
}
|
||||
if u.UsedQuotaSize > 0 {
|
||||
return util.ByteCountIEC(u.UsedQuotaSize)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) GetQuotaFiles() string {
|
||||
if u.QuotaFiles > 0 {
|
||||
return fmt.Sprintf("%d/%d", u.UsedQuotaFiles, u.QuotaFiles)
|
||||
}
|
||||
if u.UsedQuotaFiles > 0 {
|
||||
return strconv.FormatInt(int64(u.UsedQuotaFiles), 10)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) GetQuotaSizePercentage() int {
|
||||
if u.QuotaSize > 0 {
|
||||
return int(math.Round(100 * float64(u.UsedQuotaSize) / float64(u.QuotaSize)))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) GetQuotaFilesPercentage() int {
|
||||
if u.QuotaFiles > 0 {
|
||||
return int(math.Round(100 * float64(u.UsedQuotaFiles) / float64(u.QuotaFiles)))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) IsQuotaSizeLow() bool {
|
||||
return u.GetQuotaSizePercentage() > 85
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) IsQuotaFilesLow() bool {
|
||||
return u.GetQuotaFilesPercentage() > 85
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) IsDiskQuotaLow() bool {
|
||||
return u.IsQuotaSizeLow() || u.IsQuotaFilesLow()
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) GetTotalTransferQuota() string {
|
||||
total := u.UsedUploadDataTransfer + u.UsedDownloadDataTransfer
|
||||
if u.TotalDataTransfer > 0 {
|
||||
return fmt.Sprintf("%s/%s", util.ByteCountIEC(total), util.ByteCountIEC(u.TotalDataTransfer*1048576))
|
||||
}
|
||||
if total > 0 {
|
||||
return util.ByteCountIEC(total)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) GetUploadTransferQuota() string {
|
||||
if u.UploadDataTransfer > 0 {
|
||||
return fmt.Sprintf("%s/%s", util.ByteCountIEC(u.UsedUploadDataTransfer),
|
||||
util.ByteCountIEC(u.UploadDataTransfer*1048576))
|
||||
}
|
||||
if u.UsedUploadDataTransfer > 0 {
|
||||
return util.ByteCountIEC(u.UsedUploadDataTransfer)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) GetDownloadTransferQuota() string {
|
||||
if u.DownloadDataTransfer > 0 {
|
||||
return fmt.Sprintf("%s/%s", util.ByteCountIEC(u.UsedDownloadDataTransfer),
|
||||
util.ByteCountIEC(u.DownloadDataTransfer*1048576))
|
||||
}
|
||||
if u.UsedDownloadDataTransfer > 0 {
|
||||
return util.ByteCountIEC(u.UsedDownloadDataTransfer)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) GetTotalTransferQuotaPercentage() int {
|
||||
if u.TotalDataTransfer > 0 {
|
||||
return int(math.Round(100 * float64(u.UsedDownloadDataTransfer+u.UsedUploadDataTransfer) / float64(u.TotalDataTransfer*1048576)))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) GetUploadTransferQuotaPercentage() int {
|
||||
if u.UploadDataTransfer > 0 {
|
||||
return int(math.Round(100 * float64(u.UsedUploadDataTransfer) / float64(u.UploadDataTransfer*1048576)))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) GetDownloadTransferQuotaPercentage() int {
|
||||
if u.DownloadDataTransfer > 0 {
|
||||
return int(math.Round(100 * float64(u.UsedDownloadDataTransfer) / float64(u.DownloadDataTransfer*1048576)))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) IsTotalTransferQuotaLow() bool {
|
||||
if u.TotalDataTransfer > 0 {
|
||||
return u.GetTotalTransferQuotaPercentage() > 85
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) IsUploadTransferQuotaLow() bool {
|
||||
if u.UploadDataTransfer > 0 {
|
||||
return u.GetUploadTransferQuotaPercentage() > 85
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) IsDownloadTransferQuotaLow() bool {
|
||||
if u.DownloadDataTransfer > 0 {
|
||||
return u.GetDownloadTransferQuotaPercentage() > 85
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) IsTransferQuotaLow() bool {
|
||||
return u.IsTotalTransferQuotaLow() || u.IsUploadTransferQuotaLow() || u.IsDownloadTransferQuotaLow()
|
||||
}
|
||||
|
||||
func (u *userQuotaUsage) IsQuotaLow() bool {
|
||||
return u.IsDiskQuotaLow() || u.IsTransferQuotaLow()
|
||||
}
|
||||
|
||||
func newUserQuotaUsage(u *dataprovider.User) *userQuotaUsage {
|
||||
return &userQuotaUsage{
|
||||
QuotaSize: u.QuotaSize,
|
||||
QuotaFiles: u.QuotaFiles,
|
||||
UsedQuotaSize: u.UsedQuotaSize,
|
||||
UsedQuotaFiles: u.UsedQuotaFiles,
|
||||
TotalDataTransfer: u.TotalDataTransfer,
|
||||
UploadDataTransfer: u.UploadDataTransfer,
|
||||
DownloadDataTransfer: u.DownloadDataTransfer,
|
||||
UsedUploadDataTransfer: u.UsedUploadDataTransfer,
|
||||
UsedDownloadDataTransfer: u.UsedDownloadDataTransfer,
|
||||
}
|
||||
}
|
||||
|
||||
func getFileObjectURL(baseDir, name, baseWebPath string) string {
|
||||
return fmt.Sprintf("%v?path=%v&_=%v", baseWebPath, url.QueryEscape(path.Join(baseDir, name)), time.Now().UTC().Unix())
|
||||
}
|
||||
|
@ -595,7 +776,7 @@ func (s *httpdServer) renderUploadToSharePage(w http.ResponseWriter, r *http.Req
|
|||
renderClientTemplate(w, templateUploadToShare, data)
|
||||
}
|
||||
|
||||
func (s *httpdServer) renderFilesPage(w http.ResponseWriter, r *http.Request, dirName, error string, user dataprovider.User,
|
||||
func (s *httpdServer) renderFilesPage(w http.ResponseWriter, r *http.Request, dirName, error string, user *dataprovider.User,
|
||||
hasIntegrations bool,
|
||||
) {
|
||||
data := filesPage{
|
||||
|
@ -615,6 +796,7 @@ func (s *httpdServer) renderFilesPage(w http.ResponseWriter, r *http.Request, di
|
|||
CanShare: user.CanManageShares(),
|
||||
HasIntegrations: hasIntegrations,
|
||||
Paths: getDirMapping(dirName, webClientFilesPath),
|
||||
QuotaUsage: newUserQuotaUsage(user),
|
||||
}
|
||||
renderClientTemplate(w, templateClientFiles, data)
|
||||
}
|
||||
|
@ -964,11 +1146,11 @@ func (s *httpdServer) handleClientGetFiles(w http.ResponseWriter, r *http.Reques
|
|||
}
|
||||
if err != nil {
|
||||
s.renderFilesPage(w, r, path.Dir(name), fmt.Sprintf("unable to stat file %q: %v", name, err),
|
||||
user, len(s.binding.WebClientIntegrations) > 0)
|
||||
&user, len(s.binding.WebClientIntegrations) > 0)
|
||||
return
|
||||
}
|
||||
if info.IsDir() {
|
||||
s.renderFilesPage(w, r, name, "", user, len(s.binding.WebClientIntegrations) > 0)
|
||||
s.renderFilesPage(w, r, name, "", &user, len(s.binding.WebClientIntegrations) > 0)
|
||||
return
|
||||
}
|
||||
if status, err := downloadFile(w, r, connection, name, info, false, nil); err != nil && status != 0 {
|
||||
|
@ -977,7 +1159,7 @@ func (s *httpdServer) handleClientGetFiles(w http.ResponseWriter, r *http.Reques
|
|||
s.renderClientMessagePage(w, r, http.StatusText(status), "", status, err, "")
|
||||
return
|
||||
}
|
||||
s.renderFilesPage(w, r, path.Dir(name), err.Error(), user, len(s.binding.WebClientIntegrations) > 0)
|
||||
s.renderFilesPage(w, r, path.Dir(name), err.Error(), &user, len(s.binding.WebClientIntegrations) > 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2487,9 +2487,6 @@ func compareUserFilters(expected sdk.BaseUserFilters, actual sdk.BaseUserFilters
|
|||
if err := compareUserBandwidthLimitFilters(expected, actual); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := compareUserDataTransferLimitFilters(expected, actual); err != nil {
|
||||
return err
|
||||
}
|
||||
return compareUserFilePatternsFilters(expected, actual)
|
||||
}
|
||||
|
||||
|
@ -2505,30 +2502,6 @@ func checkFilterMatch(expected []string, actual []string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func compareUserDataTransferLimitFilters(expected sdk.BaseUserFilters, actual sdk.BaseUserFilters) error {
|
||||
if len(expected.DataTransferLimits) != len(actual.DataTransferLimits) {
|
||||
return errors.New("data transfer limits filters mismatch")
|
||||
}
|
||||
for idx, l := range expected.DataTransferLimits {
|
||||
if actual.DataTransferLimits[idx].UploadDataTransfer != l.UploadDataTransfer {
|
||||
return errors.New("data transfer limit upload_data_transfer mismatch")
|
||||
}
|
||||
if actual.DataTransferLimits[idx].DownloadDataTransfer != l.DownloadDataTransfer {
|
||||
return errors.New("data transfer limit download_data_transfer mismatch")
|
||||
}
|
||||
if actual.DataTransferLimits[idx].TotalDataTransfer != l.TotalDataTransfer {
|
||||
return errors.New("data transfer limit total_data_transfer mismatch")
|
||||
}
|
||||
for _, source := range actual.DataTransferLimits[idx].Sources {
|
||||
if !util.Contains(l.Sources, source) {
|
||||
return errors.New("data transfer limit source mismatch")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareUserBandwidthLimitFilters(expected sdk.BaseUserFilters, actual sdk.BaseUserFilters) error {
|
||||
if len(expected.BandwidthLimits) != len(actual.BandwidthLimits) {
|
||||
return errors.New("bandwidth limits filters mismatch")
|
||||
|
|
|
@ -17,7 +17,7 @@ package version
|
|||
|
||||
import "strings"
|
||||
|
||||
const version = "2.5.1-dev"
|
||||
const version = "2.5.4"
|
||||
|
||||
var (
|
||||
commit = ""
|
||||
|
|
|
@ -33,7 +33,7 @@ paths:
|
|||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FileInfo'
|
||||
401:
|
||||
|
@ -350,7 +350,7 @@ paths:
|
|||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
|
@ -384,7 +384,7 @@ paths:
|
|||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -424,7 +424,7 @@ paths:
|
|||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -459,7 +459,7 @@ paths:
|
|||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/StatVFS'
|
||||
401:
|
||||
|
@ -479,61 +479,61 @@ components:
|
|||
OKResponse:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
BadRequest:
|
||||
description: Bad Request
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
Unauthorized:
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
Forbidden:
|
||||
description: Forbidden
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
NotFound:
|
||||
description: Not Found
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
NotImplemented:
|
||||
description: Not Implemented
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
Conflict:
|
||||
description: Conflict
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
RequestEntityTooLarge:
|
||||
description: Request Entity Too Large, max allowed size exceeded
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
InternalServerError:
|
||||
description: Internal Server Error
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
DefaultResponse:
|
||||
description: Unexpected Error
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
schemas:
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
NFPM_VERSION=2.30.1
|
||||
NFPM_VERSION=2.32.0
|
||||
NFPM_ARCH=${NFPM_ARCH:-amd64}
|
||||
if [ -z ${SFTPGO_VERSION} ]
|
||||
then
|
||||
|
|
|
@ -459,7 +459,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
data: JSON.stringify(data),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'application/json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
$('#spinnerModal').modal('hide');
|
||||
|
@ -526,7 +526,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
data: JSON.stringify(data),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'application/json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
$('#spinnerModal').modal('hide');
|
||||
|
|
|
@ -216,7 +216,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
<input type="text" class="form-control" id="idS3KeyPrefix" name="s3_key_prefix" placeholder=""
|
||||
value="{{.S3Config.KeyPrefix}}" aria-describedby="S3KeyPrefixHelpBlock">
|
||||
<small id="S3KeyPrefixHelpBlock" class="form-text text-muted">
|
||||
Similar to a chroot for local filesystem. Cannot start with "/". Example: "somedir/subdir/".
|
||||
Similar to a chroot for local filesystem. Example: "somedir/subdir/".
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -298,7 +298,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
<input type="text" class="form-control" id="idGCSKeyPrefix" name="gcs_key_prefix" placeholder=""
|
||||
value="{{.GCSConfig.KeyPrefix}}" aria-describedby="GCSKeyPrefixHelpBlock">
|
||||
<small id="GCSKeyPrefixHelpBlock" class="form-text text-muted">
|
||||
Similar to a chroot for local filesystem. Cannot start with "/". Example: "somedir/subdir/".
|
||||
Similar to a chroot for local filesystem. Example: "somedir/subdir/".
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -420,7 +420,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
<input type="text" class="form-control" id="idAzKeyPrefix" name="az_key_prefix" placeholder=""
|
||||
value="{{.AzBlobConfig.KeyPrefix}}" aria-describedby="AzKeyPrefixHelpBlock">
|
||||
<small id="AzKeyPrefixHelpBlock" class="form-text text-muted">
|
||||
Similar to a chroot for local filesystem. Cannot start with "/". Example: "somedir/subdir/".
|
||||
Similar to a chroot for local filesystem. Example: "somedir/subdir/".
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -222,7 +222,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
</div>
|
||||
<div class="card-body">
|
||||
<h6 class="card-title mb-4">Comma separated denied or allowed files/directories, based on shell patterns.</h6>
|
||||
<p class="card-text">Match is case insensitive, set you patterns as lowercase. Denied entries are visible in directory listing by default, you can hide them by setting the "Hidden" policy, but please be aware that this may cause performance issues for large directories</p>
|
||||
<p class="card-text">Match is case insensitive, set you patterns as lowercase. Denied entries are visible in directory listing by default, you can hide them by setting the "Hidden" policy, but please be aware that this may cause performance issues for large directories. Setting a denied pattern as "*" and allowed pattern/s for the same directory you can create denied except rules, but note that if you allow a directory, everything in it will be allowed unless more specific patterns/permissions are defined.</p>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12 form_field_patterns_outer">
|
||||
{{range $idx, $pattern := .Group.UserSettings.Filters.GetFlatFilePatterns -}}
|
||||
|
@ -554,105 +554,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-header">
|
||||
<b>Per-source data transfer limits</b>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12 form_field_dtlimits_outer">
|
||||
{{range $idx, $dtLimit := .Group.UserSettings.Filters.DataTransferLimits -}}
|
||||
<div class="row form_field_dtlimits_outer_row">
|
||||
<div class="form-group col-md-5">
|
||||
<textarea class="form-control" id="idDataTransferLimitSources{{$idx}}" name="data_transfer_limit_sources{{$idx}}" rows="4" placeholder=""
|
||||
aria-describedby="dtLimitSourcesHelpBlock{{$idx}}">{{$dtLimit.GetSourcesAsString}}</textarea>
|
||||
<small id="dtLimitSourcesHelpBlock{{$idx}}" class="form-text text-muted">
|
||||
Comma separated IP/Mask in CIDR format, example: "192.168.1.0/24,10.8.0.100/32"
|
||||
</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idUploadTransferSource{{$idx}}" name="upload_data_transfer_source{{$idx}}"
|
||||
placeholder="" value="{{$dtLimit.UploadDataTransfer}}" min="0" aria-describedby="ulDtHelpBlock{{$idx}}">
|
||||
<small id="ulDtHelpBlock{{$idx}}" class="form-text text-muted">
|
||||
UL (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idDownloadTransferSource{{$idx}}" name="download_data_transfer_source{{$idx}}"
|
||||
placeholder="" value="{{$dtLimit.DownloadDataTransfer}}" min="0" aria-describedby="dlDtHelpBlock{{$idx}}">
|
||||
<small id="dlDtHelpBlock{{$idx}}" class="form-text text-muted">
|
||||
DL (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idTotalTransferSource{{$idx}}" name="total_data_transfer_source{{$idx}}"
|
||||
placeholder="" value="{{$dtLimit.TotalDataTransfer}}" min="0" aria-describedby="totalDtHelpBlock{{$idx}}">
|
||||
<small id="totalDtHelpBlock{{$idx}}" class="form-text text-muted">
|
||||
Total (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_dtlimit_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="row form_field_dtlimits_outer_row">
|
||||
<div class="form-group col-md-5">
|
||||
<textarea class="form-control" id="idDataTransferLimitSources0" name="data_transfer_limit_sources0" rows="4" placeholder=""
|
||||
aria-describedby="dtLimitSourcesHelpBlock0"></textarea>
|
||||
<small id="dtLimitSourcesHelpBlock0" class="form-text text-muted">
|
||||
Comma separated IP/Mask in CIDR format, example: "192.168.1.0/24,10.8.0.100/32"
|
||||
</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idUploadTransferSource0" name="upload_data_transfer_source0"
|
||||
placeholder="" value="" min="0" aria-describedby="ulDtHelpBlock0">
|
||||
<small id="ulDtHelpBlock0" class="form-text text-muted">
|
||||
UL (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idDownloadTransferSource0" name="download_data_transfer_source0"
|
||||
placeholder="" value="" min="0" aria-describedby="dlDtHelpBlock0">
|
||||
<small id="dlDtHelpBlock0" class="form-text text-muted">
|
||||
DL (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idTotalTransferSource0" name="total_data_transfer_source0"
|
||||
placeholder="" value="" min="0" aria-describedby="totalDtHelpBlock0">
|
||||
<small id="totalDtHelpBlock0" class="form-text text-muted">
|
||||
Total (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_dtlimit_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mx-1">
|
||||
<button type="button" class="btn btn-secondary add_new_dtlimit_field_btn">
|
||||
<i class="fas fa-plus"></i> Add new data transfer limit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -166,7 +166,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
data: JSON.stringify({"config_name": $('#idConfig option:selected').val()}),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'application/json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
$('.totpDisable').hide();
|
||||
|
@ -209,7 +209,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
data: JSON.stringify({"passcode": passcode, "config_name": $('#idConfig option:selected').val(), "secret": $('#idSecret').text()}),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'application/json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
totpSave();
|
||||
|
@ -242,7 +242,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
data: JSON.stringify({"enabled": true, "config_name": $('#idConfig option:selected').val(), "secret": {"status": "Plain", "payload": $('#idSecret').text()}}),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'application/json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
$('#successTOTPTxt').text("Configuration saved");
|
||||
|
@ -284,7 +284,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
data: JSON.stringify({"enabled": false}),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'application/json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
location.reload();
|
||||
|
@ -357,7 +357,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
type: 'POST',
|
||||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'application/json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
$('.viewRecoveryCodes').hide();
|
||||
|
|
|
@ -137,58 +137,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
$(this).closest(".form_field_bwlimits_outer_row").remove();
|
||||
});
|
||||
|
||||
$("body").on("click", ".add_new_dtlimit_field_btn", function () {
|
||||
let index = $(".form_field_dtlimits_outer").find(".form_field_dtlimits_outer_row").length;
|
||||
while (document.getElementById("idDataTransferLimitSources"+index) != null){
|
||||
index++;
|
||||
}
|
||||
$(".form_field_dtlimits_outer").append(`
|
||||
<div class="row form_field_dtlimits_outer_row">
|
||||
<div class="form-group col-md-5">
|
||||
<textarea class="form-control" id="idDataTransferLimitSources${index}" name="data_transfer_limit_sources${index}" rows="4" placeholder=""
|
||||
aria-describedby="dtLimitSourcesHelpBlock${index}"></textarea>
|
||||
<small id="dtLimitSourcesHelpBlock${index}" class="form-text text-muted">
|
||||
Comma separated IP/Mask in CIDR format, example: "192.168.1.0/24,10.8.0.100/32"
|
||||
</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idUploadTransferSource${index}" name="upload_data_transfer_source${index}"
|
||||
placeholder="" value="" min="0" aria-describedby="ulDtHelpBlock${index}">
|
||||
<small id="ulDtHelpBlock${index}" class="form-text text-muted">
|
||||
UL (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idDownloadTransferSource${index}" name="download_data_transfer_source${index}"
|
||||
placeholder="" value="" min="0" aria-describedby="dlDtHelpBlock${index}">
|
||||
<small id="dlDtHelpBlock${index}" class="form-text text-muted">
|
||||
DL (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idTotalTransferSource${index}" name="total_data_transfer_source${index}"
|
||||
placeholder="" value="" min="0" aria-describedby="totalDtHelpBlock${index}">
|
||||
<small id="totalDtHelpBlock${index}" class="form-text text-muted">
|
||||
Total (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_dtlimit_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
|
||||
$("body").on("click", ".remove_dtlimit_btn_frm_field", function () {
|
||||
$(this).closest(".form_field_dtlimits_outer_row").remove();
|
||||
});
|
||||
|
||||
$("body").on("click", ".add_new_pattern_field_btn", function () {
|
||||
let index = $(".form_field_patterns_outer").find(".form_field_patterns_outer_row").length;
|
||||
while (document.getElementById("idPatternPath"+index) != null){
|
||||
|
|
|
@ -502,7 +502,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
</div>
|
||||
<div class="card-body">
|
||||
<h6 class="card-title mb-4">Comma separated denied or allowed files/directories, based on shell patterns.</h6>
|
||||
<p class="card-text">Match is case insensitive, set you patterns as lowercase. Denied entries are visible in directory listing by default, you can hide them by setting the "Hidden" policy, but please be aware that this may cause performance issues for large directories</p>
|
||||
<p class="card-text">Match is case insensitive, set you patterns as lowercase. Denied entries are visible in directory listing by default, you can hide them by setting the "Hidden" policy, but please be aware that this may cause performance issues for large directories. Setting a denied pattern as "*" and allowed pattern/s for the same directory you can create denied except rules, but note that if you allow a directory, everything in it will be allowed unless more specific patterns/permissions are defined.</p>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12 form_field_patterns_outer">
|
||||
{{range $idx, $pattern := .User.Filters.GetFlatFilePatterns -}}
|
||||
|
@ -834,105 +834,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-header">
|
||||
<b>Per-source data transfer limits</b>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12 form_field_dtlimits_outer">
|
||||
{{range $idx, $dtLimit := .User.Filters.DataTransferLimits -}}
|
||||
<div class="row form_field_dtlimits_outer_row">
|
||||
<div class="form-group col-md-5">
|
||||
<textarea class="form-control" id="idDataTransferLimitSources{{$idx}}" name="data_transfer_limit_sources{{$idx}}" rows="4" placeholder=""
|
||||
aria-describedby="dtLimitSourcesHelpBlock{{$idx}}">{{$dtLimit.GetSourcesAsString}}</textarea>
|
||||
<small id="dtLimitSourcesHelpBlock{{$idx}}" class="form-text text-muted">
|
||||
Comma separated IP/Mask in CIDR format, example: "192.168.1.0/24,10.8.0.100/32"
|
||||
</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idUploadTransferSource{{$idx}}" name="upload_data_transfer_source{{$idx}}"
|
||||
placeholder="" value="{{$dtLimit.UploadDataTransfer}}" min="0" aria-describedby="ulDtHelpBlock{{$idx}}">
|
||||
<small id="ulDtHelpBlock{{$idx}}" class="form-text text-muted">
|
||||
UL (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idDownloadTransferSource{{$idx}}" name="download_data_transfer_source{{$idx}}"
|
||||
placeholder="" value="{{$dtLimit.DownloadDataTransfer}}" min="0" aria-describedby="dlDtHelpBlock{{$idx}}">
|
||||
<small id="dlDtHelpBlock{{$idx}}" class="form-text text-muted">
|
||||
DL (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idTotalTransferSource{{$idx}}" name="total_data_transfer_source{{$idx}}"
|
||||
placeholder="" value="{{$dtLimit.TotalDataTransfer}}" min="0" aria-describedby="totalDtHelpBlock{{$idx}}">
|
||||
<small id="totalDtHelpBlock{{$idx}}" class="form-text text-muted">
|
||||
Total (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_dtlimit_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="row form_field_dtlimits_outer_row">
|
||||
<div class="form-group col-md-5">
|
||||
<textarea class="form-control" id="idDataTransferLimitSources0" name="data_transfer_limit_sources0" rows="4" placeholder=""
|
||||
aria-describedby="dtLimitSourcesHelpBlock0"></textarea>
|
||||
<small id="dtLimitSourcesHelpBlock0" class="form-text text-muted">
|
||||
Comma separated IP/Mask in CIDR format, example: "192.168.1.0/24,10.8.0.100/32"
|
||||
</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idUploadTransferSource0" name="upload_data_transfer_source0"
|
||||
placeholder="" value="" min="0" aria-describedby="ulDtHelpBlock0">
|
||||
<small id="ulDtHelpBlock0" class="form-text text-muted">
|
||||
UL (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idDownloadTransferSource0" name="download_data_transfer_source0"
|
||||
placeholder="" value="" min="0" aria-describedby="dlDtHelpBlock0">
|
||||
<small id="dlDtHelpBlock0" class="form-text text-muted">
|
||||
DL (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" id="idTotalTransferSource0" name="total_data_transfer_source0"
|
||||
placeholder="" value="" min="0" aria-describedby="totalDtHelpBlock0">
|
||||
<small id="totalDtHelpBlock0" class="form-text text-muted">
|
||||
Total (MB). 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_dtlimit_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mx-1">
|
||||
<button type="button" class="btn btn-secondary add_new_dtlimit_field_btn">
|
||||
<i class="fas fa-plus"></i> Add new data transfer limit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -34,6 +34,82 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "additionalnavitems"}}
|
||||
{{if .QuotaUsage.HasQuotaInfo}}
|
||||
<li class="nav-item dropdown no-arrow mx-1">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="quotaDropdown" role="button"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="mr-2 d-none d-lg-inline text-gray-600 small">Quota</span>
|
||||
{{if .QuotaUsage.IsQuotaLow}}
|
||||
<i class="fas fa-exclamation-triangle fa-fw"></i>
|
||||
{{else}}
|
||||
<i class="fas fa-info fa-fw"></i>
|
||||
{{end}}
|
||||
</a>
|
||||
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in"
|
||||
ria-labelledby="alertsDropdown">
|
||||
<h6 class="dropdown-header">
|
||||
Quota usage
|
||||
</h6>
|
||||
{{ if .QuotaUsage.HasDiskQuota}}
|
||||
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||
<div class="mr-3">
|
||||
<div class="icon-circle {{if .QuotaUsage.IsDiskQuotaLow}}bg-warning{{else}}bg-success{{end}}">
|
||||
<i class="fas fa-hdd text-white"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="small text-gray-500">Disk quota</div>
|
||||
{{$size := .QuotaUsage.GetQuotaSize}}
|
||||
{{$files := .QuotaUsage.GetQuotaFiles}}
|
||||
{{if $size}}
|
||||
{{$percentage := .QuotaUsage.GetQuotaSizePercentage}}
|
||||
<span class="font-weight-bold {{if .QuotaUsage.IsQuotaSizeLow}}text-warning{{end}}">Size: {{$size}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</span>
|
||||
{{if $files}}<br>{{end}}
|
||||
{{end}}
|
||||
{{if $files}}
|
||||
{{$percentage := .QuotaUsage.GetQuotaFilesPercentage}}
|
||||
<span class="font-weight-bold {{if .QuotaUsage.IsQuotaFilesLow}}text-warning{{end}}">Files: {{$files}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
</a>
|
||||
{{end}}
|
||||
{{ if .QuotaUsage.HasTranferQuota}}
|
||||
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||
<div class="mr-3">
|
||||
<div class="icon-circle {{if .QuotaUsage.IsTransferQuotaLow}}bg-warning{{else}}bg-success{{end}}">
|
||||
<i class="fas fa-exchange-alt text-white"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="small text-gray-500">Transfer quota</div>
|
||||
{{$total := .QuotaUsage.GetTotalTransferQuota}}
|
||||
{{$upload := .QuotaUsage.GetUploadTransferQuota}}
|
||||
{{$download := .QuotaUsage.GetDownloadTransferQuota}}
|
||||
{{if $total}}
|
||||
{{$percentage := .QuotaUsage.GetTotalTransferQuotaPercentage}}
|
||||
<span class="font-weight-bold {{if .QuotaUsage.IsTotalTransferQuotaLow}}text-warning{{end}}">Total: {{$total}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</span>
|
||||
{{if or $upload $download}}<br>{{end}}
|
||||
{{end}}
|
||||
{{if $download}}
|
||||
{{$percentage := .QuotaUsage.GetDownloadTransferQuotaPercentage}}
|
||||
<span class="font-weight-bold {{if .QuotaUsage.IsDownloadTransferQuotaLow}}text-warning{{end}}">Download: {{$download}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</span>
|
||||
{{if $upload}}<br>{{end}}
|
||||
{{end}}
|
||||
{{if $upload}}
|
||||
{{$percentage := .QuotaUsage.GetUploadTransferQuotaPercentage}}
|
||||
<span class="font-weight-bold {{if .QuotaUsage.IsUploadTransferQuotaLow}}text-warning{{end}}">Upload: {{$upload}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<div class="topbar-divider d-none d-sm-block"></div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "page_body"}}
|
||||
<div id="errorMsg" class="alert alert-warning alert-dismissible fade show" style="display: none;" role="alert">
|
||||
<span id="errorTxt"></span>
|
||||
|
|
|
@ -192,7 +192,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
data: JSON.stringify({"config_name": $('#idConfig option:selected').val()}),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'application/json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
$('.totpDisable').hide();
|
||||
|
@ -236,7 +236,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
data: JSON.stringify({"passcode": passcode, "config_name": $('#idConfig option:selected').val(), "secret": $('#idSecret').text()}),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'application/json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
totpSave();
|
||||
|
@ -273,7 +273,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
data: JSON.stringify({"enabled": true, "config_name": $('#idConfig option:selected').val(), "protocols": protocolsArray, "secret": {"status": "Plain", "payload": $('#idSecret').text()}}),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'application/json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
$('#successTOTPTxt').text("Configuration saved");
|
||||
|
@ -318,7 +318,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
data: JSON.stringify({"protocols": protocolsArray}),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'application/json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
$('#successTOTPTxt').text("Protocols updated");
|
||||
|
@ -356,7 +356,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
data: JSON.stringify({"enabled": false}),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'application/json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
location.reload();
|
||||
|
@ -429,7 +429,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
type: 'POST',
|
||||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'application/json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
$('.viewRecoveryCodes').hide();
|
||||
|
|
|
@ -98,4 +98,16 @@ Filename: "{app}\{#MyAppExeName}"; Parameters: "service uninstall"; Flags: runhi
|
|||
Filename: "netsh"; Parameters: "advfirewall firewall delete rule name=""SFTPGo Service"""; Flags: runhidden; RunOnceId: "Remove SFTPGo firewall rule"
|
||||
|
||||
[Messages]
|
||||
FinishedLabel=Setup has finished installing SFTPGo on your computer. SFTPGo should already be running as a Windows service, it uses TCP port 8080 for HTTP service and TCP port 2022 for SFTP service by default, make sure the configured ports are not used by other services or edit the configuration according to your needs.
|
||||
FinishedLabel=Setup has finished installing SFTPGo on your computer. SFTPGo should already be running as a Windows service, it uses TCP port 8080 for HTTP service and TCP port 2022 for SFTP service by default, make sure the configured ports are not used by other services or edit the configuration according to your needs.
|
||||
|
||||
[Code]
|
||||
|
||||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||||
var
|
||||
Code: Integer;
|
||||
begin
|
||||
if (FileExists(ExpandConstant('{app}\{#MyAppExeName}'))) then
|
||||
begin
|
||||
Exec(ExpandConstant('{app}\{#MyAppExeName}'), 'service stop', '', SW_HIDE, ewWaitUntilTerminated, Code);
|
||||
end
|
||||
end;
|
Loading…
Add table
Reference in a new issue