Compare commits
131 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a0f781aeef | ||
![]() |
497c70012f | ||
![]() |
4897ef35ff | ||
![]() |
2459d96b58 | ||
![]() |
e53b4e51ae | ||
![]() |
28e476d1ed | ||
![]() |
d5bcbb610a | ||
![]() |
460dbaba82 | ||
![]() |
046615b107 | ||
![]() |
ba8f7823f1 | ||
![]() |
ecf7e0b49c | ||
![]() |
503508d962 | ||
![]() |
814f5022b1 | ||
![]() |
cface046dd | ||
![]() |
29cccddce1 | ||
![]() |
386448e6cb | ||
![]() |
39c30c7d14 | ||
![]() |
d1d7ab25ad | ||
![]() |
cf1e650d53 | ||
![]() |
cb6609e468 | ||
![]() |
3a47ba3ff9 | ||
![]() |
5212095b89 | ||
![]() |
2fad0467d9 | ||
![]() |
2658d06682 | ||
![]() |
cf3e1d3ec0 | ||
![]() |
ebad3f93f5 | ||
![]() |
c056c4e52c | ||
![]() |
e48b76821f | ||
![]() |
a3ed0f2d14 | ||
![]() |
d7d08c3d2f | ||
![]() |
f3a58b8ecc | ||
![]() |
3b68d0343a | ||
![]() |
fc4527354b | ||
![]() |
f07c9a7e01 | ||
![]() |
d0f348a46a | ||
![]() |
65e8e2c1d4 | ||
![]() |
5c163ed592 | ||
![]() |
1df1b8e4b5 | ||
![]() |
feaf3ac459 | ||
![]() |
f363d037a7 | ||
![]() |
f13eab1caf | ||
![]() |
dda89185fb | ||
![]() |
b4acae85b8 | ||
![]() |
bc317775d2 | ||
![]() |
10e4843a18 | ||
![]() |
256e3c1e3e | ||
![]() |
b4eabda7ad | ||
![]() |
5f659aa7b1 | ||
![]() |
b8fa4e72b4 | ||
![]() |
ccfe71b3fc | ||
![]() |
d3c15b0d6f | ||
![]() |
9c744da620 | ||
![]() |
7d24a4852c | ||
![]() |
87fdc1dec1 | ||
![]() |
cdbb376376 | ||
![]() |
07616f7b7a | ||
![]() |
5d087f6abe | ||
![]() |
18a014b95e | ||
![]() |
472bfac5fe | ||
![]() |
594eab3246 | ||
![]() |
5b8f283e03 | ||
![]() |
32a4a753f9 | ||
![]() |
e1008c30bf | ||
![]() |
c9d4920e81 | ||
![]() |
4f49457eee | ||
![]() |
8325fbc7dd | ||
![]() |
f6a5264a2e | ||
![]() |
5c27dedf5a | ||
![]() |
c74f391caf | ||
![]() |
9830efac33 | ||
![]() |
41a5b7007e | ||
![]() |
7337b294ca | ||
![]() |
89aa7f0f41 | ||
![]() |
c563b24f1b | ||
![]() |
84f3f877a5 | ||
![]() |
fe6bd8720f | ||
![]() |
d2b9e55209 | ||
![]() |
121d5ae34d | ||
![]() |
6f8bc59756 | ||
![]() |
2cc2fc64db | ||
![]() |
3169197252 | ||
![]() |
c2de3c3efc | ||
![]() |
ab83babcc6 | ||
![]() |
4fd92db12a | ||
![]() |
e03b0dfc7e | ||
![]() |
4660c2e859 | ||
![]() |
052ee04baa | ||
![]() |
04885f3601 | ||
![]() |
55169eb2d4 | ||
![]() |
636a1c2c38 | ||
![]() |
ffab21cec9 | ||
![]() |
a09e914635 | ||
![]() |
71b974d4f8 | ||
![]() |
c8e8fd5b25 | ||
![]() |
b78f8a3909 | ||
![]() |
ec9a9fff2f | ||
![]() |
87aecfc515 | ||
![]() |
063e33ad76 | ||
![]() |
fc1fecd021 | ||
![]() |
3462bba3f4 | ||
![]() |
7756cf9b1e | ||
![]() |
3f9a03e60d | ||
![]() |
1f8ac8bfe1 | ||
![]() |
aceecd9800 | ||
![]() |
952faaf76f | ||
![]() |
b83aaa863f | ||
![]() |
a215fad41a | ||
![]() |
3efff2ea8a | ||
![]() |
fb4b1e1bb5 | ||
![]() |
50de6cccc1 | ||
![]() |
db4558083e | ||
![]() |
deb46db45a | ||
![]() |
82cfe06140 | ||
![]() |
67dbada65e | ||
![]() |
98bdfad04d | ||
![]() |
92f3b2b61c | ||
![]() |
156983b59b | ||
![]() |
df441be8e3 | ||
![]() |
2cc481c3d0 | ||
![]() |
3cb884fad2 | ||
![]() |
0b49f1d88c | ||
![]() |
8ecb47e65f | ||
![]() |
c45be00963 | ||
![]() |
699b28acaa | ||
![]() |
4d9450a980 | ||
![]() |
2469c949aa | ||
![]() |
e7f315659f | ||
![]() |
13513b563b | ||
![]() |
bfe59cd8f8 | ||
![]() |
79ab3e74a5 | ||
![]() |
ed3b1fa3c2 |
140 changed files with 3785 additions and 1532 deletions
|
@ -2,9 +2,9 @@ freebsd_task:
|
|||
name: FreeBSD
|
||||
|
||||
matrix:
|
||||
- name: FreeBSD 14.0
|
||||
- name: FreeBSD 14.1
|
||||
freebsd_instance:
|
||||
image_family: freebsd-14-0
|
||||
image_family: freebsd-14-1
|
||||
|
||||
pkginstall_script:
|
||||
- pkg update -f
|
||||
|
@ -20,7 +20,7 @@ freebsd_task:
|
|||
- chown -R sftpgo:sftpgo /home/sftpgo/sftpgo
|
||||
|
||||
compile_script:
|
||||
- su sftpgo -c 'cd ~/sftpgo && go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo'
|
||||
- su sftpgo -c 'cd ~/sftpgo && go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo'
|
||||
- su sftpgo -c 'cd ~/sftpgo/tests/eventsearcher && go build -trimpath -ldflags "-s -w" -o eventsearcher'
|
||||
- su sftpgo -c 'cd ~/sftpgo/tests/ipfilter && go build -trimpath -ldflags "-s -w" -o ipfilter'
|
||||
|
||||
|
@ -28,4 +28,4 @@ freebsd_task:
|
|||
- su sftpgo -c 'cd ~/sftpgo && ./sftpgo initprovider && ./sftpgo resetprovider --force'
|
||||
|
||||
test_script:
|
||||
- su sftpgo -c 'cd ~/sftpgo && go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 20m ./... -coverprofile=coverage.txt -covermode=atomic'
|
||||
- su sftpgo -c 'cd ~/sftpgo && go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 20m ./... -coverprofile=coverage.txt -covermode=atomic'
|
||||
|
|
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
|
@ -1,11 +1,11 @@
|
|||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 2
|
||||
#- package-ecosystem: "gomod"
|
||||
# directory: "/"
|
||||
# schedule:
|
||||
# interval: "weekly"
|
||||
# open-pull-requests-limit: 2
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
|
|
266
.github/workflows/development.yml
vendored
266
.github/workflows/development.yml
vendored
|
@ -2,9 +2,13 @@ name: CI
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [2.6.x]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-deploy:
|
||||
name: Test and deploy
|
||||
|
@ -13,11 +17,6 @@ jobs:
|
|||
matrix:
|
||||
go: ['1.22']
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
upload-coverage: [true]
|
||||
include:
|
||||
- go: '1.22'
|
||||
os: windows-latest
|
||||
upload-coverage: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -30,9 +29,8 @@ jobs:
|
|||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Build for Linux/macOS x86_64
|
||||
if: startsWith(matrix.os, 'windows-') != true
|
||||
run: |
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
cd tests/eventsearcher
|
||||
go build -trimpath -ldflags "-s -w" -o eventsearcher
|
||||
cd -
|
||||
|
@ -44,46 +42,13 @@ jobs:
|
|||
|
||||
- name: Build for macOS arm64
|
||||
if: startsWith(matrix.os, 'macos-') == true
|
||||
run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 SDKROOT=$(xcrun --sdk macosx --show-sdk-path) go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo_arm64
|
||||
|
||||
- name: Build for Windows
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
run: |
|
||||
$GIT_COMMIT = (git describe --always --abbrev=8 --dirty) | Out-String
|
||||
$DATE_TIME = ([datetime]::Now.ToUniversalTime().toString("yyyy-MM-ddTHH:mm:ssZ")) | Out-String
|
||||
$LATEST_TAG = ((git describe --tags $(git rev-list --tags --max-count=1)) | Out-String).Trim()
|
||||
$REV_LIST=$LATEST_TAG+"..HEAD"
|
||||
$COMMITS_FROM_TAG= ((git rev-list $REV_LIST --count) | Out-String).Trim()
|
||||
$FILE_VERSION = $LATEST_TAG.substring(1) + "." + $COMMITS_FROM_TAG
|
||||
go install github.com/tc-hib/go-winres@latest
|
||||
go-winres simply --arch amd64 --product-version $LATEST_TAG-dev-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o sftpgo.exe
|
||||
cd tests/eventsearcher
|
||||
go build -trimpath -ldflags "-s -w" -o eventsearcher.exe
|
||||
cd ../..
|
||||
cd tests/ipfilter
|
||||
go build -trimpath -ldflags "-s -w" -o ipfilter.exe
|
||||
cd ../..
|
||||
mkdir arm64
|
||||
$Env:CGO_ENABLED='0'
|
||||
$Env:GOOS='windows'
|
||||
$Env:GOARCH='arm64'
|
||||
go-winres simply --arch arm64 --product-version $LATEST_TAG-dev-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\arm64\sftpgo.exe
|
||||
mkdir x86
|
||||
$Env:GOARCH='386'
|
||||
go-winres simply --arch 386 --product-version $LATEST_TAG-dev-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\x86\sftpgo.exe
|
||||
Remove-Item Env:\CGO_ENABLED
|
||||
Remove-Item Env:\GOOS
|
||||
Remove-Item Env:\GOARCH
|
||||
run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 SDKROOT=$(xcrun --sdk macosx --show-sdk-path) go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo_arm64
|
||||
|
||||
- name: Run test cases using SQLite provider
|
||||
run: go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 15m ./... -coverprofile=coverage.txt -covermode=atomic
|
||||
run: go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 15m ./... -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
if: ${{ matrix.upload-coverage }}
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
file: ./coverage.txt
|
||||
fail_ci_if_error: false
|
||||
|
@ -91,21 +56,21 @@ jobs:
|
|||
|
||||
- name: Run test cases using bolt provider
|
||||
run: |
|
||||
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 2m ./internal/config -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 5m ./internal/common -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 5m ./internal/httpd -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 8m ./internal/sftpd -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 5m ./internal/ftpd -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 5m ./internal/webdavd -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 2m ./internal/telemetry -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 2m ./internal/mfa -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 2m ./internal/command -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 2m ./internal/config -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 5m ./internal/common -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 5m ./internal/httpd -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 8m ./internal/sftpd -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 5m ./internal/ftpd -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 5m ./internal/webdavd -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 2m ./internal/telemetry -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 2m ./internal/mfa -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 2m ./internal/command -covermode=atomic
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: bolt
|
||||
SFTPGO_DATA_PROVIDER__NAME: 'sftpgo_bolt.db'
|
||||
|
||||
- name: Run test cases using memory provider
|
||||
run: go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 15m ./... -covermode=atomic
|
||||
run: go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 15m ./... -covermode=atomic
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: memory
|
||||
SFTPGO_DATA_PROVIDER__NAME: ''
|
||||
|
@ -126,8 +91,124 @@ jobs:
|
|||
./sftpgo gen man -d output/man/man1
|
||||
gzip output/man/man1/*
|
||||
|
||||
- name: Prepare Windows installer
|
||||
if: ${{ startsWith(matrix.os, 'windows-') && github.event_name != 'pull_request' }}
|
||||
- name: Upload build artifact
|
||||
if: startsWith(matrix.os, 'ubuntu-') != true
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo-${{ matrix.os }}-go-${{ matrix.go }}
|
||||
path: output
|
||||
|
||||
test-deploy-windows:
|
||||
name: Test and deploy Windows
|
||||
environment: signing
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Azure login
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
$GIT_COMMIT = (git describe --always --abbrev=8 --dirty) | Out-String
|
||||
$DATE_TIME = ([datetime]::Now.ToUniversalTime().toString("yyyy-MM-ddTHH:mm:ssZ")) | Out-String
|
||||
$LATEST_TAG = ((git describe --tags $(git rev-list --tags --max-count=1)) | Out-String).Trim()
|
||||
$REV_LIST=$LATEST_TAG+"..HEAD"
|
||||
$COMMITS_FROM_TAG= ((git rev-list $REV_LIST --count) | Out-String).Trim()
|
||||
$FILE_VERSION = $LATEST_TAG.substring(1) + "." + $COMMITS_FROM_TAG
|
||||
go install github.com/tc-hib/go-winres@latest
|
||||
go-winres simply --arch amd64 --product-version $LATEST_TAG-dev-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0 with additional terms" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o sftpgo.exe
|
||||
cd tests/eventsearcher
|
||||
go build -trimpath -ldflags "-s -w" -o eventsearcher.exe
|
||||
cd ../..
|
||||
cd tests/ipfilter
|
||||
go build -trimpath -ldflags "-s -w" -o ipfilter.exe
|
||||
cd ../..
|
||||
mkdir arm64
|
||||
$Env:CGO_ENABLED='0'
|
||||
$Env:GOOS='windows'
|
||||
$Env:GOARCH='arm64'
|
||||
go-winres simply --arch arm64 --product-version $LATEST_TAG-dev-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0 with additional terms" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\arm64\sftpgo.exe
|
||||
mkdir x86
|
||||
$Env:GOARCH='386'
|
||||
go-winres simply --arch 386 --product-version $LATEST_TAG-dev-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0 with additional terms" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\x86\sftpgo.exe
|
||||
Remove-Item Env:\CGO_ENABLED
|
||||
Remove-Item Env:\GOOS
|
||||
Remove-Item Env:\GOARCH
|
||||
|
||||
- name: Sign binaries
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: azure/trusted-signing-action@v0.5.0
|
||||
with:
|
||||
endpoint: https://eus.codesigning.azure.net/
|
||||
trusted-signing-account-name: nicola
|
||||
certificate-profile-name: SFTPGo
|
||||
files: |
|
||||
${{ github.workspace }}\sftpgo.exe
|
||||
${{ github.workspace }}\arm64\sftpgo.exe
|
||||
${{ github.workspace }}\x86\sftpgo.exe
|
||||
file-digest: SHA256
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
exclude-environment-credential: true
|
||||
exclude-workload-identity-credential: true
|
||||
exclude-managed-identity-credential: true
|
||||
exclude-shared-token-cache-credential: true
|
||||
exclude-visual-studio-credential: true
|
||||
exclude-visual-studio-code-credential: true
|
||||
exclude-azure-cli-credential: false
|
||||
exclude-azure-powershell-credential: true
|
||||
exclude-azure-developer-cli-credential: true
|
||||
exclude-interactive-browser-credential: true
|
||||
|
||||
- name: Run test cases using SQLite provider
|
||||
run: go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 15m ./... -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
- name: Run test cases using bolt provider
|
||||
run: |
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 2m ./internal/config -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 5m ./internal/common -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 5m ./internal/httpd -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 8m ./internal/sftpd -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 5m ./internal/ftpd -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 5m ./internal/webdavd -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 2m ./internal/telemetry -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 2m ./internal/mfa -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 2m ./internal/command -covermode=atomic
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: bolt
|
||||
SFTPGO_DATA_PROVIDER__NAME: 'sftpgo_bolt.db'
|
||||
|
||||
- name: Run test cases using memory provider
|
||||
run: go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 15m ./... -covermode=atomic
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: memory
|
||||
SFTPGO_DATA_PROVIDER__NAME: ''
|
||||
|
||||
- name: Initialize data provider
|
||||
run: |
|
||||
rm sftpgo.db
|
||||
./sftpgo initprovider
|
||||
shell: bash
|
||||
|
||||
- name: Prepare Windows installers
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
Remove-Item -LiteralPath "output" -Force -Recurse -ErrorAction Ignore
|
||||
mkdir output
|
||||
|
@ -135,6 +216,7 @@ jobs:
|
|||
copy .\sftpgo.json .\output
|
||||
copy .\sftpgo.db .\output
|
||||
copy .\LICENSE .\output\LICENSE.txt
|
||||
copy .\NOTICE .\output\NOTICE.txt
|
||||
mkdir output\templates
|
||||
xcopy .\templates .\output\templates\ /E
|
||||
mkdir output\static
|
||||
|
@ -145,15 +227,7 @@ jobs:
|
|||
$REV_LIST=$LATEST_TAG+"..HEAD"
|
||||
$COMMITS_FROM_TAG= ((git rev-list $REV_LIST --count) | Out-String).Trim()
|
||||
$Env:SFTPGO_ISS_DEV_VERSION = $LATEST_TAG + "." + $COMMITS_FROM_TAG
|
||||
$CERT_PATH=(Get-Location -PSProvider FileSystem).ProviderPath + "\cert.pfx"
|
||||
[IO.File]::WriteAllBytes($CERT_PATH,[System.Convert]::FromBase64String($Env:CERT_DATA))
|
||||
certutil -f -p "$Env:CERT_PASS" -importpfx MY "$CERT_PATH"
|
||||
rm "$CERT_PATH"
|
||||
& 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.20348.0/x86/signtool.exe' sign /sm /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /n "Nicola Murino" /d "SFTPGo" .\sftpgo.exe
|
||||
& 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.20348.0/x86/signtool.exe' sign /sm /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /n "Nicola Murino" /d "SFTPGo" .\arm64\sftpgo.exe
|
||||
& 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.20348.0/x86/signtool.exe' sign /sm /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /n "Nicola Murino" /d "SFTPGo" .\x86\sftpgo.exe
|
||||
$INNO_S='/Ssigntool=$qC:/Program Files (x86)/Windows Kits/10/bin/10.0.20348.0/x86/signtool.exe$q sign /sm /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /n $qNicola Murino$q /d $qSFTPGo$q $f'
|
||||
iscc "$INNO_S" .\windows-installer\sftpgo.iss
|
||||
iscc .\windows-installer\sftpgo.iss
|
||||
|
||||
rm .\output\sftpgo.exe
|
||||
rm .\output\sftpgo.db
|
||||
|
@ -165,40 +239,60 @@ jobs:
|
|||
Remove-Item Env:\SFTPGO_DATA_PROVIDER__DRIVER
|
||||
Remove-Item Env:\SFTPGO_DATA_PROVIDER__NAME
|
||||
$Env:SFTPGO_ISS_ARCH='arm64'
|
||||
iscc "$INNO_S" .\windows-installer\sftpgo.iss
|
||||
iscc .\windows-installer\sftpgo.iss
|
||||
|
||||
rm .\output\sftpgo.exe
|
||||
copy .\x86\sftpgo.exe .\output
|
||||
$Env:SFTPGO_ISS_ARCH='x86'
|
||||
iscc "$INNO_S" .\windows-installer\sftpgo.iss
|
||||
certutil -delstore MY "Nicola Murino"
|
||||
env:
|
||||
CERT_DATA: ${{ secrets.CERT_DATA }}
|
||||
CERT_PASS: ${{ secrets.CERT_PASS }}
|
||||
iscc .\windows-installer\sftpgo.iss
|
||||
|
||||
- name: Sign installers
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: azure/trusted-signing-action@v0.5.0
|
||||
with:
|
||||
endpoint: https://eus.codesigning.azure.net/
|
||||
trusted-signing-account-name: nicola
|
||||
certificate-profile-name: SFTPGo
|
||||
files: |
|
||||
${{ github.workspace }}\sftpgo_windows_x86_64.exe
|
||||
${{ github.workspace }}\sftpgo_windows_arm64.exe
|
||||
${{ github.workspace }}\sftpgo_windows_x86.exe
|
||||
file-digest: SHA256
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
exclude-environment-credential: true
|
||||
exclude-workload-identity-credential: true
|
||||
exclude-managed-identity-credential: true
|
||||
exclude-shared-token-cache-credential: true
|
||||
exclude-visual-studio-credential: true
|
||||
exclude-visual-studio-code-credential: true
|
||||
exclude-azure-cli-credential: false
|
||||
exclude-azure-powershell-credential: true
|
||||
exclude-azure-developer-cli-credential: true
|
||||
exclude-interactive-browser-credential: true
|
||||
|
||||
- name: Upload Windows installer x86_64 artifact
|
||||
if: ${{ startsWith(matrix.os, 'windows-') && github.event_name != 'pull_request' }}
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo_windows_installer_x86_64
|
||||
path: ./sftpgo_windows_x86_64.exe
|
||||
|
||||
- name: Upload Windows installer arm64 artifact
|
||||
if: ${{ startsWith(matrix.os, 'windows-') && github.event_name != 'pull_request' }}
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo_windows_installer_arm64
|
||||
path: ./sftpgo_windows_arm64.exe
|
||||
|
||||
- name: Upload Windows installer x86 artifact
|
||||
if: ${{ startsWith(matrix.os, 'windows-') && github.event_name != 'pull_request' }}
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo_windows_installer_x86
|
||||
path: ./sftpgo_windows_x86.exe
|
||||
|
||||
- name: Prepare build artifact for Windows
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
run: |
|
||||
Remove-Item -LiteralPath "output" -Force -Recurse -ErrorAction Ignore
|
||||
mkdir output
|
||||
|
@ -217,10 +311,9 @@ jobs:
|
|||
xcopy .\openapi .\output\openapi\ /E
|
||||
|
||||
- name: Upload build artifact
|
||||
if: startsWith(matrix.os, 'ubuntu-') != true
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo-${{ matrix.os }}-go-${{ matrix.go }}
|
||||
name: sftpgo-windows-portable
|
||||
path: output
|
||||
|
||||
test-build-flags:
|
||||
|
@ -237,10 +330,10 @@ jobs:
|
|||
|
||||
- name: Build
|
||||
run: |
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,nogcs,nos3,noportable,nobolt,nomysql,nopgsql,nosqlite,nometrics,noazblob,unixcrypt -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules,nogcs,nos3,noportable,nobolt,nomysql,nopgsql,nosqlite,nometrics,noazblob,unixcrypt -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
./sftpgo -v
|
||||
cp -r openapi static templates internal/bundle/
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,bundle -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules,bundle -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
./sftpgo -v
|
||||
|
||||
test-postgresql-mysql-crdb:
|
||||
|
@ -301,7 +394,7 @@ jobs:
|
|||
|
||||
- name: Build
|
||||
run: |
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
cd tests/eventsearcher
|
||||
go build -trimpath -ldflags "-s -w" -o eventsearcher
|
||||
cd -
|
||||
|
@ -313,7 +406,7 @@ jobs:
|
|||
run: |
|
||||
./sftpgo initprovider
|
||||
./sftpgo resetprovider --force
|
||||
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 15m ./... -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 15m ./... -covermode=atomic
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: mysql
|
||||
SFTPGO_DATA_PROVIDER__NAME: sftpgo
|
||||
|
@ -326,7 +419,7 @@ jobs:
|
|||
run: |
|
||||
./sftpgo initprovider
|
||||
./sftpgo resetprovider --force
|
||||
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 15m ./... -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 15m ./... -covermode=atomic
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: postgresql
|
||||
SFTPGO_DATA_PROVIDER__NAME: sftpgo
|
||||
|
@ -339,7 +432,7 @@ jobs:
|
|||
run: |
|
||||
./sftpgo initprovider
|
||||
./sftpgo resetprovider --force
|
||||
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 15m ./... -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 15m ./... -covermode=atomic
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: mysql
|
||||
SFTPGO_DATA_PROVIDER__NAME: sftpgo
|
||||
|
@ -356,7 +449,7 @@ jobs:
|
|||
docker exec crdb cockroach sql --insecure -e 'create database "sftpgo"'
|
||||
./sftpgo initprovider
|
||||
./sftpgo resetprovider --force
|
||||
go test -v -tags nopgxregisterdefaulttypes -p 1 -timeout 15m ./... -covermode=atomic
|
||||
go test -v -tags nopgxregisterdefaulttypes,disable_grpc_modules -p 1 -timeout 15m ./... -covermode=atomic
|
||||
docker stop crdb
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: cockroachdb
|
||||
|
@ -420,7 +513,7 @@ jobs:
|
|||
echo 'export PATH=$PATH:/usr/local/go/bin' >> build.sh
|
||||
echo 'go version' >> build.sh
|
||||
echo 'cd /usr/local/src' >> build.sh
|
||||
echo 'go build -buildvcs=false -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_commit.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo' >> build.sh
|
||||
echo 'go build -buildvcs=false -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_commit.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo' >> build.sh
|
||||
|
||||
chmod 755 build.sh
|
||||
docker run --rm --name ubuntu-build --mount type=bind,source=`pwd`,target=/usr/local/src ${{ matrix.distro }} /usr/local/src/build.sh
|
||||
|
@ -471,7 +564,7 @@ jobs:
|
|||
then
|
||||
export GOARM=7
|
||||
fi
|
||||
go build -buildvcs=false -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_commit.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
go build -buildvcs=false -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_commit.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
mkdir -p output/{init,bash_completion,zsh_completion}
|
||||
cp sftpgo.json output/
|
||||
cp -r templates output/
|
||||
|
@ -523,4 +616,5 @@ jobs:
|
|||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
args: --timeout=10m
|
||||
version: latest
|
||||
|
|
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
|
@ -5,7 +5,7 @@ on:
|
|||
# - cron: '0 4 * * *' # everyday at 4:00 AM UTC
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 2.6.x
|
||||
tags:
|
||||
- v*
|
||||
pull_request:
|
||||
|
@ -42,7 +42,7 @@ jobs:
|
|||
DOCKERFILE=Dockerfile
|
||||
MINOR=""
|
||||
MAJOR=""
|
||||
FEATURES="nopgxregisterdefaulttypes"
|
||||
FEATURES="nopgxregisterdefaulttypes,disable_grpc_modules"
|
||||
if [ "${{ github.event_name }}" = "schedule" ]; then
|
||||
VERSION=nightly
|
||||
elif [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
|
|
371
.github/workflows/release.yml
vendored
371
.github/workflows/release.yml
vendored
|
@ -4,8 +4,12 @@ on:
|
|||
push:
|
||||
tags: 'v*'
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.22.3
|
||||
GO_VERSION: 1.22.9
|
||||
|
||||
jobs:
|
||||
prepare-sources-with-deps:
|
||||
|
@ -38,12 +42,192 @@ jobs:
|
|||
path: ./sftpgo_${{ steps.get_version.outputs.VERSION }}_src_with_deps.tar.xz
|
||||
retention-days: 1
|
||||
|
||||
prepare-window-mac:
|
||||
name: Prepare binaries
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-12, windows-2022]
|
||||
prepare-windows:
|
||||
name: Prepare Windows binaries
|
||||
environment: signing
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Azure login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
- name: Get SFTPGo version
|
||||
id: get_version
|
||||
run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
$GIT_COMMIT = (git describe --always --abbrev=8 --dirty) | Out-String
|
||||
$DATE_TIME = ([datetime]::Now.ToUniversalTime().toString("yyyy-MM-ddTHH:mm:ssZ")) | Out-String
|
||||
$FILE_VERSION = $Env:SFTPGO_VERSION.substring(1) + ".0"
|
||||
go install github.com/tc-hib/go-winres@latest
|
||||
go-winres simply --arch amd64 --product-version $Env:SFTPGO_VERSION-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0 with additional terms" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o sftpgo.exe
|
||||
mkdir arm64
|
||||
$Env:CGO_ENABLED='0'
|
||||
$Env:GOOS='windows'
|
||||
$Env:GOARCH='arm64'
|
||||
go-winres simply --arch arm64 --product-version $Env:SFTPGO_VERSION-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0 with additional terms" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\arm64\sftpgo.exe
|
||||
mkdir x86
|
||||
$Env:GOARCH='386'
|
||||
go-winres simply --arch 386 --product-version $Env:SFTPGO_VERSION-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0 with additional terms" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\x86\sftpgo.exe
|
||||
Remove-Item Env:\CGO_ENABLED
|
||||
Remove-Item Env:\GOOS
|
||||
Remove-Item Env:\GOARCH
|
||||
env:
|
||||
SFTPGO_VERSION: ${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
- name: Sign binaries
|
||||
uses: azure/trusted-signing-action@v0.5.0
|
||||
with:
|
||||
endpoint: https://eus.codesigning.azure.net/
|
||||
trusted-signing-account-name: nicola
|
||||
certificate-profile-name: SFTPGo
|
||||
files: |
|
||||
${{ github.workspace }}\sftpgo.exe
|
||||
${{ github.workspace }}\arm64\sftpgo.exe
|
||||
${{ github.workspace }}\x86\sftpgo.exe
|
||||
file-digest: SHA256
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
exclude-environment-credential: true
|
||||
exclude-workload-identity-credential: true
|
||||
exclude-managed-identity-credential: true
|
||||
exclude-shared-token-cache-credential: true
|
||||
exclude-visual-studio-credential: true
|
||||
exclude-visual-studio-code-credential: true
|
||||
exclude-azure-cli-credential: false
|
||||
exclude-azure-powershell-credential: true
|
||||
exclude-azure-developer-cli-credential: true
|
||||
exclude-interactive-browser-credential: true
|
||||
|
||||
- name: Initialize data provider
|
||||
run: ./sftpgo initprovider
|
||||
shell: bash
|
||||
|
||||
- name: Prepare Release
|
||||
run: |
|
||||
mkdir output
|
||||
copy .\sftpgo.exe .\output
|
||||
copy .\sftpgo.json .\output
|
||||
copy .\sftpgo.db .\output
|
||||
copy .\LICENSE .\output\LICENSE.txt
|
||||
copy .\NOTICE .\output\NOTICE.txt
|
||||
mkdir output\templates
|
||||
xcopy .\templates .\output\templates\ /E
|
||||
mkdir output\static
|
||||
xcopy .\static .\output\static\ /E
|
||||
mkdir output\openapi
|
||||
xcopy .\openapi .\output\openapi\ /E
|
||||
iscc .\windows-installer\sftpgo.iss
|
||||
rm .\output\sftpgo.exe
|
||||
rm .\output\sftpgo.db
|
||||
copy .\arm64\sftpgo.exe .\output
|
||||
(Get-Content .\output\sftpgo.json).replace('"sqlite"', '"bolt"') | Set-Content .\output\sftpgo.json
|
||||
$Env:SFTPGO_DATA_PROVIDER__DRIVER='bolt'
|
||||
$Env:SFTPGO_DATA_PROVIDER__NAME='.\output\sftpgo.db'
|
||||
.\sftpgo.exe initprovider
|
||||
Remove-Item Env:\SFTPGO_DATA_PROVIDER__DRIVER
|
||||
Remove-Item Env:\SFTPGO_DATA_PROVIDER__NAME
|
||||
$Env:SFTPGO_ISS_ARCH='arm64'
|
||||
iscc .\windows-installer\sftpgo.iss
|
||||
|
||||
rm .\output\sftpgo.exe
|
||||
copy .\x86\sftpgo.exe .\output
|
||||
$Env:SFTPGO_ISS_ARCH='x86'
|
||||
iscc .\windows-installer\sftpgo.iss
|
||||
env:
|
||||
SFTPGO_ISS_VERSION: ${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
- name: Sign installers
|
||||
uses: azure/trusted-signing-action@v0.5.0
|
||||
with:
|
||||
endpoint: https://eus.codesigning.azure.net/
|
||||
trusted-signing-account-name: nicola
|
||||
certificate-profile-name: SFTPGo
|
||||
files: |
|
||||
${{ github.workspace }}\sftpgo_windows_x86_64.exe
|
||||
${{ github.workspace }}\sftpgo_windows_arm64.exe
|
||||
${{ github.workspace }}\sftpgo_windows_x86.exe
|
||||
file-digest: SHA256
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
exclude-environment-credential: true
|
||||
exclude-workload-identity-credential: true
|
||||
exclude-managed-identity-credential: true
|
||||
exclude-shared-token-cache-credential: true
|
||||
exclude-visual-studio-credential: true
|
||||
exclude-visual-studio-code-credential: true
|
||||
exclude-azure-cli-credential: false
|
||||
exclude-azure-powershell-credential: true
|
||||
exclude-azure-developer-cli-credential: true
|
||||
exclude-interactive-browser-credential: true
|
||||
|
||||
- name: Prepare Portable Release
|
||||
run: |
|
||||
mkdir win-portable
|
||||
copy .\sftpgo.exe .\win-portable
|
||||
mkdir win-portable\arm64
|
||||
copy .\arm64\sftpgo.exe .\win-portable\arm64
|
||||
mkdir win-portable\x86
|
||||
copy .\x86\sftpgo.exe .\win-portable\x86
|
||||
copy .\sftpgo.json .\win-portable
|
||||
(Get-Content .\win-portable\sftpgo.json).replace('"sqlite"', '"bolt"') | Set-Content .\win-portable\sftpgo.json
|
||||
copy .\output\sftpgo.db .\win-portable
|
||||
copy .\LICENSE .\win-portable\LICENSE.txt
|
||||
copy .\NOTICE .\win-portable\NOTICE.txt
|
||||
mkdir win-portable\templates
|
||||
xcopy .\templates .\win-portable\templates\ /E
|
||||
mkdir win-portable\static
|
||||
xcopy .\static .\win-portable\static\ /E
|
||||
mkdir win-portable\openapi
|
||||
xcopy .\openapi .\win-portable\openapi\ /E
|
||||
Compress-Archive .\win-portable\* sftpgo_portable.zip
|
||||
|
||||
- name: Upload Windows installer x86_64 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_windows_x86_64.exe
|
||||
path: ./sftpgo_windows_x86_64.exe
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload Windows installer arm64 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_windows_arm64.exe
|
||||
path: ./sftpgo_windows_arm64.exe
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload Windows installer x86 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_windows_x86.exe
|
||||
path: ./sftpgo_windows_x86.exe
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload Windows portable artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_windows_portable.zip
|
||||
path: ./sftpgo_portable.zip
|
||||
retention-days: 1
|
||||
|
||||
prepare-mac:
|
||||
name: Prepare macOS binaries
|
||||
runs-on: macos-12
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -57,64 +241,24 @@ jobs:
|
|||
run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Get OS name
|
||||
id: get_os_name
|
||||
run: |
|
||||
if [[ $MATRIX_OS =~ ^macos.* ]]
|
||||
then
|
||||
echo "OS=macOS" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "OS=windows" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
shell: bash
|
||||
env:
|
||||
MATRIX_OS: ${{ matrix.os }}
|
||||
|
||||
- name: Build for macOS x86_64
|
||||
if: startsWith(matrix.os, 'windows-') != true
|
||||
run: go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
run: go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
|
||||
- name: Build for macOS arm64
|
||||
if: startsWith(matrix.os, 'macos-') == true
|
||||
run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 SDKROOT=$(xcrun --sdk macosx --show-sdk-path) go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo_arm64
|
||||
|
||||
- name: Build for Windows
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
run: |
|
||||
$GIT_COMMIT = (git describe --always --abbrev=8 --dirty) | Out-String
|
||||
$DATE_TIME = ([datetime]::Now.ToUniversalTime().toString("yyyy-MM-ddTHH:mm:ssZ")) | Out-String
|
||||
$FILE_VERSION = $Env:SFTPGO_VERSION.substring(1) + ".0"
|
||||
go install github.com/tc-hib/go-winres@latest
|
||||
go-winres simply --arch amd64 --product-version $Env:SFTPGO_VERSION-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o sftpgo.exe
|
||||
mkdir arm64
|
||||
$Env:CGO_ENABLED='0'
|
||||
$Env:GOOS='windows'
|
||||
$Env:GOARCH='arm64'
|
||||
go-winres simply --arch arm64 --product-version $Env:SFTPGO_VERSION-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\arm64\sftpgo.exe
|
||||
mkdir x86
|
||||
$Env:GOARCH='386'
|
||||
go-winres simply --arch 386 --product-version $Env:SFTPGO_VERSION-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\x86\sftpgo.exe
|
||||
Remove-Item Env:\CGO_ENABLED
|
||||
Remove-Item Env:\GOOS
|
||||
Remove-Item Env:\GOARCH
|
||||
env:
|
||||
SFTPGO_VERSION: ${{ steps.get_version.outputs.VERSION }}
|
||||
run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 SDKROOT=$(xcrun --sdk macosx --show-sdk-path) go build -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo_arm64
|
||||
|
||||
- name: Initialize data provider
|
||||
run: ./sftpgo initprovider
|
||||
shell: bash
|
||||
|
||||
- name: Prepare Release for macOS
|
||||
if: startsWith(matrix.os, 'macos-')
|
||||
- name: Prepare Release
|
||||
run: |
|
||||
mkdir -p output/{init,sqlite,bash_completion,zsh_completion}
|
||||
echo "For documentation please take a look here:" > output/README.txt
|
||||
echo "" >> output/README.txt
|
||||
echo "https://github.com/drakkan/sftpgo/blob/${SFTPGO_VERSION}/README.md" >> output/README.txt
|
||||
echo "https://docs.sftpgo.com" >> output/README.txt
|
||||
cp LICENSE output/
|
||||
cp NOTICE output/
|
||||
cp sftpgo output/
|
||||
cp sftpgo.json output/
|
||||
cp sftpgo.db output/sqlite/
|
||||
|
@ -127,130 +271,27 @@ jobs:
|
|||
./sftpgo gen man -d output/man/man1
|
||||
gzip output/man/man1/*
|
||||
cd output
|
||||
tar cJvf ../sftpgo_${SFTPGO_VERSION}_${OS}_x86_64.tar.xz *
|
||||
tar cJvf ../sftpgo_${SFTPGO_VERSION}_macOS_x86_64.tar.xz *
|
||||
cd ..
|
||||
cp sftpgo_arm64 output/sftpgo
|
||||
cd output
|
||||
tar cJvf ../sftpgo_${SFTPGO_VERSION}_${OS}_arm64.tar.xz *
|
||||
tar cJvf ../sftpgo_${SFTPGO_VERSION}_macOS_arm64.tar.xz *
|
||||
cd ..
|
||||
env:
|
||||
SFTPGO_VERSION: ${{ steps.get_version.outputs.VERSION }}
|
||||
OS: ${{ steps.get_os_name.outputs.OS }}
|
||||
|
||||
- name: Prepare Release for Windows
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
run: |
|
||||
mkdir output
|
||||
copy .\sftpgo.exe .\output
|
||||
copy .\sftpgo.json .\output
|
||||
copy .\sftpgo.db .\output
|
||||
copy .\LICENSE .\output\LICENSE.txt
|
||||
mkdir output\templates
|
||||
xcopy .\templates .\output\templates\ /E
|
||||
mkdir output\static
|
||||
xcopy .\static .\output\static\ /E
|
||||
mkdir output\openapi
|
||||
xcopy .\openapi .\output\openapi\ /E
|
||||
$CERT_PATH=(Get-Location -PSProvider FileSystem).ProviderPath + "\cert.pfx"
|
||||
[IO.File]::WriteAllBytes($CERT_PATH,[System.Convert]::FromBase64String($Env:CERT_DATA))
|
||||
certutil -f -p "$Env:CERT_PASS" -importpfx MY "$CERT_PATH"
|
||||
rm "$CERT_PATH"
|
||||
& 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.20348.0/x86/signtool.exe' sign /sm /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /n "Nicola Murino" /d "SFTPGo" .\sftpgo.exe
|
||||
& 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.20348.0/x86/signtool.exe' sign /sm /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /n "Nicola Murino" /d "SFTPGo" .\arm64\sftpgo.exe
|
||||
& 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.20348.0/x86/signtool.exe' sign /sm /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /n "Nicola Murino" /d "SFTPGo" .\x86\sftpgo.exe
|
||||
$INNO_S='/Ssigntool=$qC:/Program Files (x86)/Windows Kits/10/bin/10.0.20348.0/x86/signtool.exe$q sign /sm /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /n $qNicola Murino$q /d $qSFTPGo$q $f'
|
||||
iscc "$INNO_S" .\windows-installer\sftpgo.iss
|
||||
|
||||
rm .\output\sftpgo.exe
|
||||
rm .\output\sftpgo.db
|
||||
copy .\arm64\sftpgo.exe .\output
|
||||
(Get-Content .\output\sftpgo.json).replace('"sqlite"', '"bolt"') | Set-Content .\output\sftpgo.json
|
||||
$Env:SFTPGO_DATA_PROVIDER__DRIVER='bolt'
|
||||
$Env:SFTPGO_DATA_PROVIDER__NAME='.\output\sftpgo.db'
|
||||
.\sftpgo.exe initprovider
|
||||
Remove-Item Env:\SFTPGO_DATA_PROVIDER__DRIVER
|
||||
Remove-Item Env:\SFTPGO_DATA_PROVIDER__NAME
|
||||
$Env:SFTPGO_ISS_ARCH='arm64'
|
||||
iscc "$INNO_S" .\windows-installer\sftpgo.iss
|
||||
|
||||
rm .\output\sftpgo.exe
|
||||
copy .\x86\sftpgo.exe .\output
|
||||
$Env:SFTPGO_ISS_ARCH='x86'
|
||||
iscc "$INNO_S" .\windows-installer\sftpgo.iss
|
||||
certutil -delstore MY "Nicola Murino"
|
||||
env:
|
||||
SFTPGO_ISS_VERSION: ${{ steps.get_version.outputs.VERSION }}
|
||||
SFTPGO_ISS_DOC_URL: https://github.com/drakkan/sftpgo/blob/${{ steps.get_version.outputs.VERSION }}/README.md
|
||||
CERT_DATA: ${{ secrets.CERT_DATA }}
|
||||
CERT_PASS: ${{ secrets.CERT_PASS }}
|
||||
|
||||
- name: Prepare Portable Release for Windows
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
run: |
|
||||
mkdir win-portable
|
||||
copy .\sftpgo.exe .\win-portable
|
||||
mkdir win-portable\arm64
|
||||
copy .\arm64\sftpgo.exe .\win-portable\arm64
|
||||
mkdir win-portable\x86
|
||||
copy .\x86\sftpgo.exe .\win-portable\x86
|
||||
copy .\sftpgo.json .\win-portable
|
||||
(Get-Content .\win-portable\sftpgo.json).replace('"sqlite"', '"bolt"') | Set-Content .\win-portable\sftpgo.json
|
||||
copy .\output\sftpgo.db .\win-portable
|
||||
copy .\LICENSE .\win-portable\LICENSE.txt
|
||||
mkdir win-portable\templates
|
||||
xcopy .\templates .\win-portable\templates\ /E
|
||||
mkdir win-portable\static
|
||||
xcopy .\static .\win-portable\static\ /E
|
||||
mkdir win-portable\openapi
|
||||
xcopy .\openapi .\win-portable\openapi\ /E
|
||||
Compress-Archive .\win-portable\* sftpgo_portable.zip
|
||||
|
||||
- name: Upload macOS x86_64 artifact
|
||||
if: startsWith(matrix.os, 'macos-')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_x86_64.tar.xz
|
||||
path: ./sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_x86_64.tar.xz
|
||||
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_macOS_x86_64.tar.xz
|
||||
path: ./sftpgo_${{ steps.get_version.outputs.VERSION }}_macOS_x86_64.tar.xz
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload macOS arm64 artifact
|
||||
if: startsWith(matrix.os, 'macos-')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_arm64.tar.xz
|
||||
path: ./sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_arm64.tar.xz
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload Windows installer x86_64 artifact
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_x86_64.exe
|
||||
path: ./sftpgo_windows_x86_64.exe
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload Windows installer arm64 artifact
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_arm64.exe
|
||||
path: ./sftpgo_windows_arm64.exe
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload Windows installer x86 artifact
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_x86.exe
|
||||
path: ./sftpgo_windows_x86.exe
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload Windows portable artifact
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_${{ steps.get_os_name.outputs.OS }}_portable.zip
|
||||
path: ./sftpgo_portable.zip
|
||||
name: sftpgo_${{ steps.get_version.outputs.VERSION }}_macOS_arm64.tar.xz
|
||||
path: ./sftpgo_${{ steps.get_version.outputs.VERSION }}_macOS_arm64.tar.xz
|
||||
retention-days: 1
|
||||
|
||||
prepare-linux:
|
||||
|
@ -310,7 +351,7 @@ jobs:
|
|||
echo 'export PATH=$PATH:/usr/local/go/bin' >> build.sh
|
||||
echo 'go version' >> build.sh
|
||||
echo 'cd /usr/local/src' >> build.sh
|
||||
echo 'go build -buildvcs=false -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_version.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo' >> build.sh
|
||||
echo 'go build -buildvcs=false -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_version.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo' >> build.sh
|
||||
|
||||
chmod 755 build.sh
|
||||
docker run --rm --name ubuntu-build --mount type=bind,source=`pwd`,target=/usr/local/src ${{ matrix.distro }} /usr/local/src/build.sh
|
||||
|
@ -319,6 +360,7 @@ jobs:
|
|||
echo "" >> output/README.txt
|
||||
echo "https://github.com/drakkan/sftpgo/blob/${SFTPGO_VERSION}/README.md" >> output/README.txt
|
||||
cp LICENSE output/
|
||||
cp NOTICE output/
|
||||
cp sftpgo.json output/
|
||||
cp -r templates output/
|
||||
cp -r static output/
|
||||
|
@ -362,12 +404,13 @@ jobs:
|
|||
run: |
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
go version
|
||||
go build -buildvcs=false -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_version.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
go build -buildvcs=false -trimpath -tags nopgxregisterdefaulttypes,disable_grpc_modules -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_version.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
|
||||
mkdir -p output/{init,sqlite,bash_completion,zsh_completion}
|
||||
echo "For documentation please take a look here:" > output/README.txt
|
||||
echo "" >> output/README.txt
|
||||
echo "https://github.com/drakkan/sftpgo/blob/${{ steps.get_version.outputs.SFTPGO_VERSION }}/README.md" >> output/README.txt
|
||||
cp LICENSE output/
|
||||
cp NOTICE output/
|
||||
cp sftpgo.json output/
|
||||
cp -r templates output/
|
||||
cp -r static output/
|
||||
|
@ -475,7 +518,7 @@ jobs:
|
|||
|
||||
create-release:
|
||||
name: Release
|
||||
needs: [prepare-linux-bundle, prepare-sources-with-deps, prepare-window-mac]
|
||||
needs: [prepare-linux-bundle, prepare-sources-with-deps, prepare-mac, prepare-windows]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
|
12
NOTICE
Normal file
12
NOTICE
Normal file
|
@ -0,0 +1,12 @@
|
|||
Additional terms under GNU AGPL version 3 section 7.3(b) and 13.1:
|
||||
|
||||
If you have included SFTPGo so that it is offered through any network
|
||||
interactions, including by means of an external user interface, or
|
||||
any other integration, even without modifying its source code and then
|
||||
SFTPGo is partially, fully or optionally configured via your frontend,
|
||||
you must provide reasonable but clear attribution to the SFTPGo project
|
||||
and its author(s), not imply any endorsement by or affiliation with the
|
||||
SFTPGo project, and you must prominently offer all users interacting
|
||||
with it remotely through a computer network an opportunity to receive
|
||||
the Corresponding Source of the SFTPGo version you include by providing
|
||||
a link to the Corresponding Source in the SFTPGo source code repository.
|
23
README.md
23
README.md
|
@ -19,13 +19,9 @@ The WebClient UI allows end users to change their credentials, browse and manage
|
|||
|
||||
We strongly believe in Open Source software model, so we decided to make SFTPGo available to everyone, but maintaining and evolving SFTPGo takes a lot of time and work. To make development and maintenance sustainable you should consider to support the project with a [sponsorship](https://github.com/sponsors/drakkan).
|
||||
|
||||
We also provide [professional services](https://sftpgo.com/#pricing) to support you in using SFTPGo to the fullest.
|
||||
We love doing the work and we'd like to keep doing it - your support helps make SFTPGo possible.
|
||||
|
||||
The open source license grant you freedom but not assurance of help. So why would you rely on free software without support or any guarantee it will stay healthy and maintained for the upcoming years?
|
||||
|
||||
Supporting the project benefit businesses and the community because if the project is financially sustainable, using this business model, we don't have to restrict features and/or switch to an [Open-core](https://en.wikipedia.org/wiki/Open-core_model) model. The technology stays truly open source. Everyone wins.
|
||||
|
||||
You should support the project for its ongoing maintenance, even if you don't have any questions or need new features. If SFTPGo is no longer maintained you will have troubles and your company will lose money: bugs and security vulnerabilities will no longer be fixed, new algorithms will not be added to support newer clients, and so on. You will be forced to switch to a similar proprietary product and pay for its license and the migration cost.
|
||||
It is important to understand that you should support SFTPGo and any other Open Source project you rely on for ongoing maintenance, even if you don't have any questions or need new features, to mitigate the business risk of a project you depend on going unmaintained, with its security and development velocity implications.
|
||||
|
||||
### Thank you to our sponsors
|
||||
|
||||
|
@ -47,21 +43,24 @@ You should support the project for its ongoing maintenance, even if you don't ha
|
|||
</br></br>
|
||||
[<img src="./img/vps2day.png" alt="VPS2day logo" width="234" height="56">](https://www.vps2day.com/)
|
||||
|
||||
## Support policy
|
||||
## Support
|
||||
|
||||
You can use SFTPGo for free, respecting the obligations of the Open Source license, but please do not ask or expect free support as well.
|
||||
You can use SFTPGo for free, respecting the obligations of the Open Source [license](#license), but please do not ask or expect free support as well.
|
||||
|
||||
Use [discussions](https://github.com/drakkan/sftpgo/discussions) to ask questions and get support from the community.
|
||||
|
||||
If you report an invalid issue and/or ask for step-by-step support, your issue will be closed as invalid without further explanation. Invalid bug reports left open may confuse other users. Thanks for understanding.
|
||||
We offer commercial support, guarantees, and advice for SFTPGo:
|
||||
|
||||
- With our [plans](https://sftpgo.com/plans) you can safely install and use SFTPGo on-premise in professional environments.
|
||||
- With our [SaaS offerings](https://sftpgo.com/saas) you can use SFTPGo hosted in the cloud, fully managed and supported.
|
||||
|
||||
## Documentation
|
||||
|
||||
You can read more about supported features and documentation at [sftpgo.github.io](https://sftpgo.github.io/).
|
||||
You can read more about supported features and documentation at [docs.sftpgo.com](https://docs.sftpgo.com/).
|
||||
|
||||
## Release Cadence
|
||||
|
||||
SFTPGo releases are feature-driven, we don't have a fixed time based schedule. As a rough estimate, you can expect 1 or 2 new releases per year.
|
||||
SFTPGo releases are feature-driven, we don't have a fixed time based schedule. As a rough estimate, you can expect 1 or 2 new major releases per year and several bug fix releases.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
|
@ -79,7 +78,7 @@ Thank you to [Incode](https://www.incode.it/) for helping us to improve the UI/U
|
|||
|
||||
## License
|
||||
|
||||
SFTPGo source code is licensed under the GNU AGPL-3.0-only.
|
||||
SFTPGo source code is licensed under the GNU AGPL-3.0-only with [additional terms](./NOTICE).
|
||||
|
||||
The [theme](https://keenthemes.com/products/templates-mega-bundle) used in WebAdmin and WebClient user interfaces is proprietary, this means:
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Data Backup
|
||||
|
||||
:warning: Since v2.4.0 you can use the [EventManager](https://sftpgo.github.io/latest/eventmanager/) to schedule backups.
|
||||
:warning: Since v2.4.0 you can use the [EventManager](https://docs.sftpgo.com/latest/eventmanager/) to schedule backups.
|
||||
|
||||
The `backup` example script shows how to use the SFTPGo REST API to backup your data.
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# File retention policies
|
||||
|
||||
:warning: Since v2.4.0 you can use the [EventManager](https://sftpgo.github.io/latest/eventmanager/) to schedule data retention checks.
|
||||
:warning: Since v2.4.0 you can use the [EventManager](https://docs.sftpgo.com/latest/eventmanager/) to schedule data retention checks.
|
||||
|
||||
The `checkretention` example script shows how to use the SFTPGo REST API to manage data retention.
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@ go 1.22.2
|
|||
|
||||
require (
|
||||
github.com/go-ldap/ldap/v3 v3.4.8
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
)
|
||||
|
|
|
@ -45,8 +45,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -71,16 +71,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
|
|
|
@ -3,41 +3,41 @@ module github.com/drakkan/sftpgo/ldapauthserver
|
|||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/go-ldap/ldap/v3 v3.4.8
|
||||
github.com/nathanaelle/password/v2 v2.0.1
|
||||
github.com/rs/zerolog v1.32.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/viper v1.18.2
|
||||
golang.org/x/crypto v0.23.0
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/magiconair/properties v1.8.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/sagikazarmark/locafero v0.6.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
@ -5,20 +5,20 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY
|
|||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
|
||||
|
@ -53,8 +53,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
|
||||
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
|
@ -65,8 +65,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
|
|||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/nathanaelle/password/v2 v2.0.1 h1:ItoCTdsuIWzilYmllQPa3DR3YoCXcpfxScWLqr8Ii2s=
|
||||
github.com/nathanaelle/password/v2 v2.0.1/go.mod h1:eaoT+ICQEPNtikBRIAatN8ThWwMhVG+r1jTw60BvPJk=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
|
@ -74,34 +74,32 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
|
|||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
||||
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
|
||||
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
|
@ -115,10 +113,10 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU=
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
|
@ -130,8 +128,9 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -148,24 +147,24 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Update user quota
|
||||
|
||||
:warning: Since v2.4.0 you can use the [EventManager](https://sftpgo.github.io/latest/eventmanager/) to schedule quota scans.
|
||||
:warning: Since v2.4.0 you can use the [EventManager](https://docs.sftpgo.com/latest/eventmanager/) to schedule quota scans.
|
||||
|
||||
The `scanuserquota` example script shows how to use the SFTPGo REST API to update the users' quota.
|
||||
|
||||
|
|
239
go.mod
239
go.mod
|
@ -1,190 +1,203 @@
|
|||
module github.com/drakkan/sftpgo/v2
|
||||
|
||||
go 1.22.2
|
||||
go 1.22.7
|
||||
|
||||
require (
|
||||
cloud.google.com/go/storage v1.41.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2
|
||||
cloud.google.com/go/storage v1.49.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0
|
||||
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5
|
||||
github.com/alexedwards/argon2id v1.0.0
|
||||
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.13
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.13
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.18
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.21.5
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.54.0
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.28.7
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.7
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.7
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.48
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.44
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.25.8
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.8
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3
|
||||
github.com/bmatcuk/doublestar/v4 v4.7.1
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.3.8
|
||||
github.com/coreos/go-oidc/v3 v3.10.0
|
||||
github.com/drakkan/webdav v0.0.0-20240503091431-218ec83910bb
|
||||
github.com/coreos/go-oidc/v3 v3.11.0
|
||||
github.com/drakkan/webdav v0.0.0-20241026165615-b8b8f74ae71b
|
||||
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
|
||||
github.com/fclairamb/ftpserverlib v0.24.0
|
||||
github.com/fclairamb/ftpserverlib v0.25.0
|
||||
github.com/fclairamb/go-log v0.5.0
|
||||
github.com/go-acme/lego/v4 v4.16.1
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/go-chi/jwtauth/v5 v5.3.1
|
||||
github.com/go-acme/lego/v4 v4.21.0
|
||||
github.com/go-chi/chi/v5 v5.2.0
|
||||
github.com/go-chi/jwtauth/v5 v5.3.2
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/go-hclog v1.6.3
|
||||
github.com/hashicorp/go-plugin v1.6.1
|
||||
github.com/hashicorp/go-retryablehttp v0.7.6
|
||||
github.com/jackc/pgx/v5 v5.5.5
|
||||
github.com/hashicorp/go-plugin v1.6.2
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||
github.com/jackc/pgx/v5 v5.7.1
|
||||
github.com/jlaffaye/ftp v0.2.0
|
||||
github.com/klauspost/compress v1.17.8
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.21
|
||||
github.com/klauspost/compress v1.17.11
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.3
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/mattn/go-sqlite3 v1.14.24
|
||||
github.com/mhale/smtpd v0.8.3
|
||||
github.com/minio/sio v0.3.1
|
||||
github.com/minio/sio v0.4.1
|
||||
github.com/otiai10/copy v1.14.0
|
||||
github.com/pires/go-proxyproto v0.7.0
|
||||
github.com/pkg/sftp v1.13.7-0.20240410063531-637088883317
|
||||
github.com/pires/go-proxyproto v0.8.0
|
||||
github.com/pkg/sftp v1.13.7
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/rs/cors v1.11.0
|
||||
github.com/rs/xid v1.5.0
|
||||
github.com/rs/zerolog v1.32.0
|
||||
github.com/sftpgo/sdk v0.1.7
|
||||
github.com/shirou/gopsutil/v3 v3.24.4
|
||||
github.com/rs/cors v1.11.1
|
||||
github.com/rs/xid v1.6.0
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/sftpgo/sdk v0.1.8
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/viper v1.18.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/studio-b12/gowebdav v0.9.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/studio-b12/gowebdav v0.10.0
|
||||
github.com/subosito/gotenv v1.6.0
|
||||
github.com/unrolled/secure v1.14.0
|
||||
github.com/unrolled/secure v1.17.0
|
||||
github.com/wagslane/go-password-validator v0.3.0
|
||||
github.com/wneessen/go-mail v0.4.1
|
||||
github.com/wneessen/go-mail v0.5.2
|
||||
github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a
|
||||
go.etcd.io/bbolt v1.3.10
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
gocloud.dev v0.37.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/oauth2 v0.20.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/term v0.20.0
|
||||
golang.org/x/time v0.5.0
|
||||
google.golang.org/api v0.180.0
|
||||
go.etcd.io/bbolt v1.3.11
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
gocloud.dev v0.40.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/net v0.33.0
|
||||
golang.org/x/oauth2 v0.24.0
|
||||
golang.org/x/sys v0.28.0
|
||||
golang.org/x/term v0.27.0
|
||||
golang.org/x/time v0.8.0
|
||||
google.golang.org/api v0.214.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.113.0 // indirect
|
||||
cloud.google.com/go/auth v0.4.1 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.8 // indirect
|
||||
cel.dev/expr v0.19.1 // indirect
|
||||
cloud.google.com/go v0.117.0 // indirect
|
||||
cloud.google.com/go/auth v0.13.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
cloud.google.com/go/iam v1.3.0 // indirect
|
||||
cloud.google.com/go/monitoring v1.22.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 // indirect
|
||||
github.com/aws/smithy-go v1.20.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect
|
||||
github.com/aws/smithy-go v1.22.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.2 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20241213214725-57cfbe6fad57 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane v0.13.1 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/goccy/go-json v0.10.4 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc v1.0.5 // indirect
|
||||
github.com/lestrrat-go/httprc v1.0.6 // indirect
|
||||
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||
github.com/magiconair/properties v1.8.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/miekg/dns v1.1.59 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/miekg/dns v1.1.62 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.53.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.0 // indirect
|
||||
github.com/prometheus/common v0.61.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.6.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
|
||||
go.opentelemetry.io/otel v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.26.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.33.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||
go.opentelemetry.io/otel v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.33.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/tools v0.21.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/tools v0.28.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/genproto v0.0.0-20241219192143-6b3ec007d9bb // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb // indirect
|
||||
google.golang.org/grpc v1.69.2 // indirect
|
||||
google.golang.org/protobuf v1.36.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-20240510125431-4617586dfa1c
|
||||
github.com/fclairamb/ftpserverlib => github.com/drakkan/ftpserverlib v0.0.0-20240603150004-6a8f643fbf2e
|
||||
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20240430173938-7ba8270c8e7f
|
||||
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-20240509175024-33071fb6437f
|
||||
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20241215105839-39c227bf1e16
|
||||
)
|
||||
|
|
529
go.sum
529
go.sum
|
@ -1,116 +1,131 @@
|
|||
cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4=
|
||||
cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.113.0 h1:g3C70mn3lWfckKBiCVsAshabrDg01pQ0pnX1MNtnMkA=
|
||||
cloud.google.com/go v0.113.0/go.mod h1:glEqlogERKYeePz6ZdkcLJ28Q2I6aERgDDErBg9GzO8=
|
||||
cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
|
||||
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
||||
cloud.google.com/go/compute v1.26.0 h1:uHf0NN2nvxl1Gh4QO83yRCOdMK4zivtMS5gv0dEX0hg=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
|
||||
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
|
||||
cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY=
|
||||
cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc=
|
||||
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
||||
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
||||
cloud.google.com/go/storage v1.41.0 h1:RusiwatSu6lHeEXe3kglxakAmAbfV+rhtPqA6i8RBx0=
|
||||
cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80=
|
||||
cloud.google.com/go v0.117.0 h1:Z5TNFfQxj7WG2FgOGX1ekC5RiXrYgms6QscOm32M/4s=
|
||||
cloud.google.com/go v0.117.0/go.mod h1:ZbwhVTb1DBGt2Iwb3tNO6SEK4q+cplHZmLWH+DelYYc=
|
||||
cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs=
|
||||
cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8=
|
||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
cloud.google.com/go/iam v1.3.0 h1:4Wo2qTaGKFtajbLpF6I4mywg900u3TLlHDb6mriLDPU=
|
||||
cloud.google.com/go/iam v1.3.0/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY=
|
||||
cloud.google.com/go/kms v1.20.2 h1:NGTHOxAyhDVUGVU5KngeyGScrg2D39X76Aphe6NC7S0=
|
||||
cloud.google.com/go/kms v1.20.2/go.mod h1:LywpNiVCvzYNJWS9JUcGJSVTNSwPwi0vBAotzDqn2nc=
|
||||
cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk=
|
||||
cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM=
|
||||
cloud.google.com/go/longrunning v0.6.3 h1:A2q2vuyXysRcwzqDpMMLSI6mb6o39miS52UEG/Rd2ng=
|
||||
cloud.google.com/go/longrunning v0.6.3/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI=
|
||||
cloud.google.com/go/monitoring v1.22.0 h1:mQ0040B7dpuRq1+4YiQD43M2vW9HgoVxY98xhqGT+YI=
|
||||
cloud.google.com/go/monitoring v1.22.0/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU=
|
||||
cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw=
|
||||
cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU=
|
||||
cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI=
|
||||
cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0/go.mod h1:T5RfihdXtBDxt1Ch2wobif3TvzTdumDy29kahv6AV9A=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 h1:YUUxeiOWgdAQE3pXt2H7QXzZs0q8UBjgRbl56qo8GYM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2/go.mod h1:dmXQgZuiSubAecswZE+Sm8jkvEa7kQgTPVRvwL/nd0E=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0 h1:mlmW46Q0B79I+Aj4azKC6xDMFN9a9SyZWESlGWYXbFs=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0/go.mod h1:PXe2h+LKcWTX9afWdZoHyODqR4fBa5boUM/8uJfZ0Jo=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI=
|
||||
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 h1:o90wcURuxekmXrtxmYWTyNla0+ZEHhud6DI1ZTxd1vI=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0/go.mod h1:6fTWu4m3jocfUZLYF5KsZC1TUfRvEjs7lM4crme/irw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0 h1:jJKWl98inONJAr/IZrdFQUWcwUO95DLY1XMD1ZIut+g=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0/go.mod h1:l2fIqmwB+FKSfvn3bAD/0i+AXAxhIZjTK2svT/mgUXs=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 h1:GYUJLfvd++4DMuMhCFLgLXvFwofIxh/qOwoGuS/LTew=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w=
|
||||
github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw=
|
||||
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 h1:I9YN9WMo3SUh7p/4wKeNvD/IQla3U3SUa61U7ul+xM4=
|
||||
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964/go.mod h1:eFiR01PwTcpbzXtdMces7zxg6utvFM5puiWHpWB8D/k=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.17 h1:9b1Os1s11mF5qTIKLgSsyPG810di2+ySSLIIt9bwe9I=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.17/go.mod h1:9Wp7tDOMhv0+sb/FTRAkbHNQ7abYDnoJRzm5AAtCnTc=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.18 h1:fUHit8Pe+2dWEHtxpOVDTOSQR257iH24HjT17DAz6qs=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.18/go.mod h1:IX1n1o870YYxzqN56w26s7FrO5Zaw/hdatxhJDiEf2U=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 h1:81KE7vaZzrl7yHBYHVEzYB8sypz11NMOZ40YlWvPxsU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5/go.mod h1:LIt2rg7Mcgn09Ygbdh/RdIm0rQ+3BNkbP1gyVMFtRK0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 h1:ZMeFZ5yk+Ek+jNr1+uwCd2tG89t6oTS5yVWpa6yy2es=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7/go.mod h1:mxV05U+4JiHqIpGqqYXOHLPKUC6bDXC44bsUhNjOEwY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 h1:f9RyWNtS8oH7cZlbn+/JNPpjUk5+5fLd5lM9M0i49Ys=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5/go.mod h1:h5CoMZV2VF297/VLhRhO1WF+XYWOzXo+4HsObA4HjBQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.21.5 h1:p2PxN+OO28p2bCCXE79sJfFBaSohwxa24bQdjuyPZCs=
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.21.5/go.mod h1:Q01yJLephuOzv6IYzcknrpVAriOqB66+qtGnpqgw9UE=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.2 h1:rq2hglTQM3yHZvOPVMtNvLS5x6hijx7JvRDgKiTNDGQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.2/go.mod h1:qmdkIIAC+GCLASF7R2whgNrJADz0QZPX+Seiw/i4S3o=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.54.0 h1:Ls94RY3P6HtB88JkzXo1lHrXzonHPpNR//OSAV63mSE=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.54.0/go.mod h1:qmdkIIAC+GCLASF7R2whgNrJADz0QZPX+Seiw/i4S3o=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.28.7 h1:4cziOtpDwtgcb+wTYRzz8C+GoH1XySy0p7j4oBbqPQE=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.28.7/go.mod h1:3Ba++UwWd154xtP4FRX5pUK3Gt4up5sDHCve6kVfE+g=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.7/go.mod h1:vZGX6GVkIE8uECSUHB6MWAUsd4ZcG2Yq/dMa4refR3M=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.48 h1:IYdLD1qTJ0zanRavulofmqut4afs45mOWEI+MzZtTfQ=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.48/go.mod h1:tOscxHN3CGmuX9idQ3+qbkzrjVIx32lqDSU1/0d/qXs=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/y1phm1JlC8/iT+5XRmAn8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.44 h1:2zxMLXLedpB4K1ilbJFxtMKsVKaexOqDttOhc0QGm3Q=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.44/go.mod h1:VuLHdqwjSvgftNC7yqPWyGVhEwPmJpeRi07gOgOfHF8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 h1:GeNJsIFHB+WW5ap2Tec4K6dzcVTsRbsT1Lra46Hv9ME=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26/go.mod h1:zfgMpwHDXX2WGoG84xG2H+ZlPTkJUU4YUvx2svLQYWo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 h1:tB4tNw83KcajNAzaIMhkhVI2Nt8fAZd5A5ro113FEMY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7/go.mod h1:lvpyBGkZ3tZ9iSsUIcC2EWp+0ywa7aK3BLT+FwZi+mQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 h1:Hi0KGbrnr57bEHWM0bJ1QcBzxLrL/k2DHvGYhb8+W1w=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7/go.mod h1:wKNgWgExdjjrm4qvfbTorkvocEstaoDl4WCvGfeCy9c=
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.25.8 h1:A9pJ60b8AKwlXiSbznKBcDaBTA7jIaI6gHSDqQeAZOg=
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.25.8/go.mod h1:/aDbp2jKTGdpJwFHuwQeypaIPlCjkxMqDVUB+7GizdU=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1 h1:aOVVZJgWbaH+EJYPvEgkNhCEbXXvH7+oML36oaPK3zE=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1/go.mod h1:r+xl5yzMk9083rMR+sJ5TYj9Tihvf/l1oxzZXDgGj2Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.8 h1:WT3EPriVEpHE2jeNqHqj7l43JCIWPoZjNNRluZ7agII=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.8/go.mod h1:By/yiMzR0yfhPaqRWE3GrT9B/Z6871z1GfWGc+vf4Y8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 h1:CvuUmnXI7ebaUAhbJcDy9YQx8wHR69eZ9I7q5hszt/g=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.8/go.mod h1:XDeGv1opzwm8ubxddF0cgqkZWsyOtw4lr6dxwmb6YQg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 h1:F2rBfNAL5UyswqoeWv9zs74N/NanhK16ydHW1pahX6E=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7/go.mod h1:JfyQ0g2JG8+Krq0EuZNnRwX0mU0HrwY/tG6JNfcqh4k=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 h1:Xgv/hyNgvLda/M9l9qxXc4UFSgppnRczLxlMs5Ae/QY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3/go.mod h1:5Gn+d+VaaRgsjewpMvGazt0WfcFO+Md4wLOuBfGR9Bc=
|
||||
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
|
||||
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q=
|
||||
github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
|
||||
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
|
||||
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/xds/go v0.0.0-20241213214725-57cfbe6fad57 h1:put7Je9ZyxbHtwr7IqGrW4LLVUupJQ2gbsDshKISSgU=
|
||||
github.com/cncf/xds/go v0.0.0-20241213214725-57cfbe6fad57/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.3.8 h1:53yoUo4+EtrC1NrAEgnnad4AS3ntNvGup1PAXZ7UmpE=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.3.8/go.mod h1:9uH5jK4yQ3ZQUT9IXe4I2fHzMIF5+JC/oOdzTRgJYJk=
|
||||
github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU=
|
||||
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
|
||||
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
|
||||
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
|
@ -119,48 +134,52 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnN
|
|||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
github.com/drakkan/cron/v3 v3.0.0-20230222140221-217a1e4d96c0 h1:EW9gIJRmt9lzk66Fhh4S8VEtURA6QHZqGeSRE9Nb2/U=
|
||||
github.com/drakkan/cron/v3 v3.0.0-20230222140221-217a1e4d96c0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/drakkan/crypto v0.0.0-20240509175024-33071fb6437f h1:4+0I7deWH0/8dTS1xVgFrNSq7aaNvKrfaqLlfFBNV64=
|
||||
github.com/drakkan/crypto v0.0.0-20240509175024-33071fb6437f/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
github.com/drakkan/crypto v0.0.0-20241215105839-39c227bf1e16 h1:L5sDMg/hkI4OLReZ9niFvGAJs0vhMtWJreTeRXliNrQ=
|
||||
github.com/drakkan/crypto v0.0.0-20241215105839-39c227bf1e16/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
github.com/drakkan/ftp v0.0.0-20240430173938-7ba8270c8e7f h1:S9JUlrOzjK58UKoLqqb40YLyVlt0bcIFtYrvnanV3zc=
|
||||
github.com/drakkan/ftp v0.0.0-20240430173938-7ba8270c8e7f/go.mod h1:4p8lUl4vQ80L598CygL+3IFtm+3nggvvW/palOlViwE=
|
||||
github.com/drakkan/ftpserverlib v0.0.0-20240510125431-4617586dfa1c h1:cO3eqB2Bjv8WM8HUDfajAt3bFFGj6FUQ2eIxsxVvyC8=
|
||||
github.com/drakkan/ftpserverlib v0.0.0-20240510125431-4617586dfa1c/go.mod h1:+9afJRWESpCq4/O8Vr00Q2jfinRxP6PiCpXph6CgGuc=
|
||||
github.com/drakkan/webdav v0.0.0-20240503091431-218ec83910bb h1:067/Uo8cfeY7QC0yzWCr/RImuNcM0rLWAsBUyMks59o=
|
||||
github.com/drakkan/webdav v0.0.0-20240503091431-218ec83910bb/go.mod h1:zOVb1QDhwwqWn2L2qZ0U3swMSO4GTSNyIwXCGO/UGWE=
|
||||
github.com/drakkan/ftpserverlib v0.0.0-20240603150004-6a8f643fbf2e h1:VBpqQeChkGXSV1FXCtvd3BJTyB+DcMgiu7SfkpsGuKw=
|
||||
github.com/drakkan/ftpserverlib v0.0.0-20240603150004-6a8f643fbf2e/go.mod h1:aAwyOAC6IIe+IZeeGD1QjuE3GGDzqW/c5Xtn+Dp0JUM=
|
||||
github.com/drakkan/webdav v0.0.0-20241026165615-b8b8f74ae71b h1:Y1tLiQ8fnxM5f3wiBjAXsHzHNwiY9BR+mXZA75nZwrs=
|
||||
github.com/drakkan/webdav v0.0.0-20241026165615-b8b8f74ae71b/go.mod h1:zOVb1QDhwwqWn2L2qZ0U3swMSO4GTSNyIwXCGO/UGWE=
|
||||
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 h1:/ZshrfQzayqRSBDodmp3rhNCHJCff+utvgBuWRbiqu4=
|
||||
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001/go.mod h1:kltMsfRMTHSFdMbK66XdS8mfMW77+FZA1fGY1xYMF84=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE=
|
||||
github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fclairamb/go-log v0.5.0 h1:Gz9wSamEaA6lta4IU2cjJc2xSq5sV5VYSB5w/SUHhVc=
|
||||
github.com/fclairamb/go-log v0.5.0/go.mod h1:XoRO1dYezpsGmLLkZE9I+sHqpqY65p8JA+Vqblb7k40=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ=
|
||||
github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.1 h1:1ePWrjVctvp1tyBq5b/2ER8Th/+RbYc7x4qNsc5rh5A=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.1/go.mod h1:6Fl2RRmWXs3tJYE1IQGX81FsPoGqDwq9c15j52R5q80=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-acme/lego/v4 v4.21.0 h1:arEW+8o5p7VI8Bk1kr/PDlgD1DrxtTH1gJ4b7mehL8o=
|
||||
github.com/go-acme/lego/v4 v4.21.0/go.mod h1:HrSWzm3Ckj45Ie3i+p1zKVobbQoMOaGu9m4up0dUeDI=
|
||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.2 h1:s+ON3ATyyMs3Me0kqyuua6Rwu+2zqIIkL0GCaMarwvs=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.2/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
|
||||
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
|
||||
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
|
||||
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
|
@ -168,17 +187,17 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
|||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -197,14 +216,12 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
|
||||
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
|
||||
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
|
@ -213,10 +230,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
|
||||
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
|
@ -226,33 +243,28 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1
|
|||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI=
|
||||
github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.6 h1:TwRYfx2z2C4cLbXmT8I5PgP/xmuqASDyiVuGYfs9GZM=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.6/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog=
|
||||
github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
||||
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
||||
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
|
@ -265,23 +277,22 @@ github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N
|
|||
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||
github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk=
|
||||
github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
|
||||
github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k=
|
||||
github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
|
||||
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
|
||||
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.21 h1:jAPKupy4uHgrHFEdjVjNkUgoBKtVDgrQPB/h55FHrR0=
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.21/go.mod h1:09mLW8zto6bWL9GbwnqAli+ArLf+5M33QLQPDggkUWM=
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.3 h1:Ud4lb2QuxRClYAmRleF50KrbKIoM1TddXgBrneT5/Jo=
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.3/go.mod h1:q6uFgbgZfEmQrfJfrCo90QcQOcXFMfbI/fO0NqRtvZo=
|
||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
|
||||
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
|
@ -292,74 +303,76 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
|||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mhale/smtpd v0.8.3 h1:8j8YNXajksoSLZja3HdwvYVZPuJSqAxFsib3adzRRt8=
|
||||
github.com/mhale/smtpd v0.8.3/go.mod h1:MQl+y2hwIEQCXtNhe5+55n0GZOjSmeqORDIXbqUL3x4=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
github.com/minio/sio v0.3.1 h1:d59r5RTHb1OsQaSl1EaTWurzMMDRLA5fgNmjzD4eVu4=
|
||||
github.com/minio/sio v0.3.1/go.mod h1:S0ovgVgc+sTlQyhiXA1ppBLv7REM7TYi5yyq2qL/Y6o=
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
|
||||
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/minio/sio v0.4.1 h1:EMe3YBC1nf+sRQia65Rutxi+Z554XPV0dt8BIBA+a/0=
|
||||
github.com/minio/sio v0.4.1/go.mod h1:oBSjJeGbBdRMZZwna07sX9EFzZy+ywu5aofRiV1g79I=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
|
||||
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
||||
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
|
||||
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
|
||||
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
|
||||
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
|
||||
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.7-0.20240410063531-637088883317 h1:kupFhKi4R3XqKmUmqGSHWn/WZbC9CnwSoW421tL1gGw=
|
||||
github.com/pkg/sftp v1.13.7-0.20240410063531-637088883317/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
|
||||
github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
|
||||
github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=
|
||||
github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
|
||||
github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek=
|
||||
github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
|
||||
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ=
|
||||
github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
||||
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
|
||||
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo=
|
||||
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/sftpgo/sdk v0.1.7 h1:lzOKBDnKb1PpSMlskqCPxBYKxVWz34uMBhT78r/13iA=
|
||||
github.com/sftpgo/sdk v0.1.7/go.mod h1:ler/KG6kMLlsOs/8s6dVN3oom+z+NkbXBVWO//Cv/WA=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
|
||||
github.com/sftpgo/sdk v0.1.8 h1:HAywJl9jZnigFGztA/CWLieOW+R+HH6js6o6/qYvuSY=
|
||||
github.com/sftpgo/sdk v0.1.8/go.mod h1:Isl0IEzS/Muvh8Fr4X+NWFsOS/fZQHRD4oPQpoY7C4g=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
|
@ -368,18 +381,17 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS
|
|||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
|
@ -387,62 +399,70 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/studio-b12/gowebdav v0.9.0 h1:1j1sc9gQnNxbXXM4M/CebPOX4aXYtr7MojAVcN4dHjU=
|
||||
github.com/studio-b12/gowebdav v0.9.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/studio-b12/gowebdav v0.10.0 h1:Yewz8FFiadcGEu4hxS/AAJQlHelndqln1bns3hcJIYc=
|
||||
github.com/studio-b12/gowebdav v0.10.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/unrolled/secure v1.14.0 h1:u9vJTU/pR4Bny0ntLUMxdfLtmIRGvQf2sEFuA0TG9AE=
|
||||
github.com/unrolled/secure v1.14.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
||||
github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU=
|
||||
github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I=
|
||||
github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ=
|
||||
github.com/wneessen/go-mail v0.4.1 h1:m2rSg/sc8FZQCdtrV5M8ymHYOFrC6KJAQAIcgrXvqoo=
|
||||
github.com/wneessen/go-mail v0.4.1/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8=
|
||||
github.com/wneessen/go-mail v0.5.2 h1:MZKwgHJoRboLJ+EHMLuHpZc95wo+u1xViL/4XSswDT8=
|
||||
github.com/wneessen/go-mail v0.5.2/go.mod h1:kRroJvEq2hOSEPFRiKjN7Csrz0G1w+RpiGR3b6yo+Ck=
|
||||
github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a h1:XfF01GyP+0eWCaVp0y6rNN+kFp7pt9Da4UUYrJ5XPWA=
|
||||
github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a/go.mod h1:aXb8yZQEWo1XHGMf1qQfnb83GR/EJ2EBlwtUgAaNBoE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
|
||||
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
|
||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
|
||||
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
|
||||
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
|
||||
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
|
||||
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
||||
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
|
||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
|
||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.33.0 h1:FVPoXEoILwgbZUu4X7YSgsESsAmGRgoYcnXkzgQPhP4=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.33.0/go.mod h1:ZHrLmr4ikK2AwRj9QL+c9s2SOlgoSRyMpNVzUj2fZqI=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
|
||||
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
|
||||
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I=
|
||||
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
|
||||
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
|
||||
go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
|
||||
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q=
|
||||
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
|
||||
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
gocloud.dev v0.37.0 h1:XF1rN6R0qZI/9DYjN16Uy0durAmSlf58DHOcb28GPro=
|
||||
gocloud.dev v0.37.0/go.mod h1:7/O4kqdInCNsc6LqgmuFnS0GRew4XNNYWpA44yQnwco=
|
||||
gocloud.dev v0.40.0 h1:f8LgP+4WDqOG/RXoUcyLpeIAGOcAbZrZbDQCUee10ng=
|
||||
gocloud.dev v0.40.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
|
||||
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -451,19 +471,25 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -482,27 +508,31 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
@ -511,34 +541,34 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
|
|||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4=
|
||||
google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA=
|
||||
google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8 h1:XpH03M6PDRKTo1oGfZBXu2SzwcbfxUokgobVinuUZoU=
|
||||
google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8/go.mod h1:OLh2Ylz+WlYAJaSBRpJIJLP8iQP+8da+fpxbwNEAV/o=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
|
||||
google.golang.org/genproto v0.0.0-20241219192143-6b3ec007d9bb h1:JGs+s1Q6osip3cDY197L1HmkuPn8wPp9Hfy9jl+Uz+U=
|
||||
google.golang.org/genproto v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:o8GgNarfULyZPNaIY8RDfXM7AZcmcKC/tbMWp/ZOFDw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb h1:B7GIB7sr443wZ/EAEl7VZjmh1V6qzkt5V+RYcUYtS1U=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:E5//3O5ZIG2l71Xnt+P/CYUY8Bxs8E7WMoZ9tlcMbAY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb h1:3oy2tynMOP1QbTC0MsNNAV+Se8M2Bd0A5+x1QHyw+pI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
|
||||
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
@ -548,8 +578,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
|
||||
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
@ -558,9 +588,6 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -671,7 +671,7 @@ func (c *Configuration) notifyCertificateRenewal(domain string, err error) {
|
|||
params := common.EventParams{
|
||||
Name: domain,
|
||||
Event: "Certificate renewal",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
if err != nil {
|
||||
params.Status = 2
|
||||
|
|
|
@ -37,6 +37,7 @@ var (
|
|||
Short: "Reset the password for the specified administrator",
|
||||
Long: `This command reads the data provider connection details from the specified
|
||||
configuration file and resets the password for the specified administrator.
|
||||
Two-factor authentication is also disabled.
|
||||
This command is not supported for the memory provider.
|
||||
For embedded providers like bolt and SQLite you should stop the running SFTPGo
|
||||
instance to avoid database corruption.
|
||||
|
@ -98,6 +99,7 @@ Please take a look at the usage below to customize the options.`,
|
|||
os.Exit(1)
|
||||
}
|
||||
admin.Password = string(pwd)
|
||||
admin.Filters.TOTPConfig.Enabled = false
|
||||
if err := dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSystem, "", ""); err != nil {
|
||||
logger.ErrorToConsole("Unable to update password: %v", err)
|
||||
os.Exit(1)
|
||||
|
|
|
@ -16,13 +16,21 @@ package cmd
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/subosito/gotenv"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/service"
|
||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
||||
)
|
||||
|
||||
const (
|
||||
envFileMaxSize = 1048576
|
||||
)
|
||||
|
||||
var (
|
||||
serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
|
@ -34,9 +42,11 @@ $ sftpgo serve
|
|||
|
||||
Please take a look at the usage below to customize the startup options`,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
configDir := util.CleanDirInput(configDir)
|
||||
checkServeParamsFromEnvFiles(configDir)
|
||||
service.SetGraceTime(graceTime)
|
||||
service := service.Service{
|
||||
ConfigDir: util.CleanDirInput(configDir),
|
||||
ConfigDir: configDir,
|
||||
ConfigFile: configFile,
|
||||
LogFilePath: logFilePath,
|
||||
LogMaxSize: logMaxSize,
|
||||
|
@ -62,6 +72,75 @@ Please take a look at the usage below to customize the startup options`,
|
|||
}
|
||||
)
|
||||
|
||||
func setIntFromEnv(receiver *int, val string) {
|
||||
converted, err := strconv.Atoi(val)
|
||||
if err == nil {
|
||||
*receiver = converted
|
||||
}
|
||||
}
|
||||
|
||||
func setBoolFromEnv(receiver *bool, val string) {
|
||||
converted, err := strconv.ParseBool(strings.TrimSpace(val))
|
||||
if err == nil {
|
||||
*receiver = converted
|
||||
}
|
||||
}
|
||||
|
||||
func checkServeParamsFromEnvFiles(configDir string) { //nolint:gocyclo
|
||||
// The logger is not yet initialized here, we have no way to report errors.
|
||||
envd := filepath.Join(configDir, "env.d")
|
||||
entries, err := os.ReadDir(envd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, entry := range entries {
|
||||
info, err := entry.Info()
|
||||
if err == nil && info.Mode().IsRegular() {
|
||||
envFile := filepath.Join(envd, entry.Name())
|
||||
if info.Size() > envFileMaxSize {
|
||||
continue
|
||||
}
|
||||
envVars, err := gotenv.Read(envFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for k, v := range envVars {
|
||||
if _, isSet := os.LookupEnv(k); isSet {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case "SFTPGO_LOG_FILE_PATH":
|
||||
logFilePath = v
|
||||
case "SFTPGO_LOG_MAX_SIZE":
|
||||
setIntFromEnv(&logMaxSize, v)
|
||||
case "SFTPGO_LOG_MAX_BACKUPS":
|
||||
setIntFromEnv(&logMaxBackups, v)
|
||||
case "SFTPGO_LOG_MAX_AGE":
|
||||
setIntFromEnv(&logMaxAge, v)
|
||||
case "SFTPGO_LOG_COMPRESS":
|
||||
setBoolFromEnv(&logCompress, v)
|
||||
case "SFTPGO_LOG_LEVEL":
|
||||
logLevel = v
|
||||
case "SFTPGO_LOG_UTC_TIME":
|
||||
setBoolFromEnv(&logUTCTime, v)
|
||||
case "SFTPGO_CONFIG_FILE":
|
||||
configFile = v
|
||||
case "SFTPGO_LOADDATA_FROM":
|
||||
loadDataFrom = v
|
||||
case "SFTPGO_LOADDATA_MODE":
|
||||
setIntFromEnv(&loadDataMode, v)
|
||||
case "SFTPGO_LOADDATA_CLEAN":
|
||||
setBoolFromEnv(&loadDataClean, v)
|
||||
case "SFTPGO_LOADDATA_QUOTA_SCAN":
|
||||
setIntFromEnv(&loadDataQuotaScan, v)
|
||||
case "SFTPGO_GRACE_TIME":
|
||||
setIntFromEnv(&graceTime, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(serveCmd)
|
||||
addServeFlags(serveCmd)
|
||||
|
|
|
@ -17,7 +17,6 @@ package cmd
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
|
@ -31,21 +30,23 @@ var (
|
|||
Short: "Start the SFTPGo Windows Service",
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
configDir = util.CleanDirInput(configDir)
|
||||
if !filepath.IsAbs(logFilePath) && util.IsFileInputValid(logFilePath) {
|
||||
logFilePath = filepath.Join(configDir, logFilePath)
|
||||
}
|
||||
checkServeParamsFromEnvFiles(configDir)
|
||||
service.SetGraceTime(graceTime)
|
||||
s := service.Service{
|
||||
ConfigDir: configDir,
|
||||
ConfigFile: configFile,
|
||||
LogFilePath: logFilePath,
|
||||
LogMaxSize: logMaxSize,
|
||||
LogMaxBackups: logMaxBackups,
|
||||
LogMaxAge: logMaxAge,
|
||||
LogCompress: logCompress,
|
||||
LogLevel: logLevel,
|
||||
LogUTCTime: logUTCTime,
|
||||
Shutdown: make(chan bool),
|
||||
ConfigDir: configDir,
|
||||
ConfigFile: configFile,
|
||||
LogFilePath: logFilePath,
|
||||
LogMaxSize: logMaxSize,
|
||||
LogMaxBackups: logMaxBackups,
|
||||
LogMaxAge: logMaxAge,
|
||||
LogCompress: logCompress,
|
||||
LogLevel: logLevel,
|
||||
LogUTCTime: logUTCTime,
|
||||
LoadDataFrom: loadDataFrom,
|
||||
LoadDataMode: loadDataMode,
|
||||
LoadDataQuotaScan: loadDataQuotaScan,
|
||||
LoadDataClean: loadDataClean,
|
||||
Shutdown: make(chan bool),
|
||||
}
|
||||
winService := service.WindowsService{
|
||||
Service: s,
|
||||
|
|
|
@ -90,6 +90,10 @@ Command-line flags should be specified in the Subsystem declaration.
|
|||
logger.Error(logSender, connectionID, "unable to initialize KMS: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := plugin.Initialize(config.GetPluginsConfig(), logLevel); err != nil {
|
||||
logger.Error(logSender, connectionID, "unable to initialize plugin system: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
mfaConfig := config.GetMFAConfig()
|
||||
err = mfaConfig.Initialize()
|
||||
if err != nil {
|
||||
|
@ -109,10 +113,6 @@ Command-line flags should be specified in the Subsystem declaration.
|
|||
logger.Error(logSender, connectionID, "unable to initialize the data provider: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := plugin.Initialize(config.GetPluginsConfig(), logLevel); err != nil {
|
||||
logger.Error(logSender, connectionID, "unable to initialize plugin system: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
smtpConfig := config.GetSMTPConfig()
|
||||
err = smtpConfig.Initialize(configDir, false)
|
||||
if err != nil {
|
||||
|
|
|
@ -91,8 +91,9 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str
|
|||
if !hasHook && !hasNotifiersPlugin && !hasRules {
|
||||
return 0, nil
|
||||
}
|
||||
dateTime := time.Now()
|
||||
event = newActionNotification(&conn.User, operation, filePath, virtualPath, "", "", "",
|
||||
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, openFlags, conn.getNotificationStatus(nil), 0, nil)
|
||||
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, openFlags, conn.getNotificationStatus(nil), 0, dateTime, nil)
|
||||
if hasNotifiersPlugin {
|
||||
plugin.Handler.NotifyFsEvent(event)
|
||||
}
|
||||
|
@ -112,7 +113,7 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str
|
|||
Protocol: event.Protocol,
|
||||
IP: event.IP,
|
||||
Role: event.Role,
|
||||
Timestamp: event.Timestamp,
|
||||
Timestamp: dateTime,
|
||||
Email: conn.User.Email,
|
||||
Object: nil,
|
||||
}
|
||||
|
@ -137,8 +138,9 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
|
|||
if !hasHook && !hasNotifiersPlugin && !hasRules {
|
||||
return nil
|
||||
}
|
||||
dateTime := time.Now()
|
||||
notification := newActionNotification(&conn.User, operation, filePath, virtualPath, target, virtualTarget, sshCmd,
|
||||
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, 0, conn.getNotificationStatus(err), elapsed, metadata)
|
||||
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, 0, conn.getNotificationStatus(err), elapsed, dateTime, metadata)
|
||||
if hasNotifiersPlugin {
|
||||
plugin.Handler.NotifyFsEvent(notification)
|
||||
}
|
||||
|
@ -159,7 +161,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
|
|||
Protocol: notification.Protocol,
|
||||
IP: notification.IP,
|
||||
Role: notification.Role,
|
||||
Timestamp: notification.Timestamp,
|
||||
Timestamp: dateTime,
|
||||
Email: conn.User.Email,
|
||||
Object: nil,
|
||||
Metadata: metadata,
|
||||
|
@ -197,6 +199,7 @@ func newActionNotification(
|
|||
operation, filePath, virtualPath, target, virtualTarget, sshCmd, protocol, ip, sessionID string,
|
||||
fileSize int64,
|
||||
openFlags, status int, elapsed int64,
|
||||
datetime time.Time,
|
||||
metadata map[string]string,
|
||||
) *notifier.FsEvent {
|
||||
var bucket, endpoint string
|
||||
|
@ -238,7 +241,7 @@ func newActionNotification(
|
|||
SessionID: sessionID,
|
||||
OpenFlags: openFlags,
|
||||
Role: user.Role,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: datetime.UnixNano(),
|
||||
Elapsed: elapsed,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
"github.com/rs/xid"
|
||||
|
@ -71,7 +72,7 @@ func TestNewActionNotification(t *testing.T) {
|
|||
c := NewBaseConnection("id", ProtocolSSH, "", "", user)
|
||||
sessionID := xid.New().String()
|
||||
a := newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(errors.New("fake error")), 0, nil)
|
||||
123, 0, c.getNotificationStatus(errors.New("fake error")), 0, time.Now(), nil)
|
||||
assert.Equal(t, user.Username, a.Username)
|
||||
assert.Equal(t, 0, len(a.Bucket))
|
||||
assert.Equal(t, 0, len(a.Endpoint))
|
||||
|
@ -79,38 +80,38 @@ func TestNewActionNotification(t *testing.T) {
|
|||
|
||||
user.FsConfig.Provider = sdk.S3FilesystemProvider
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(nil), 0, nil)
|
||||
123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil)
|
||||
assert.Equal(t, "s3bucket", a.Bucket)
|
||||
assert.Equal(t, "endpoint", a.Endpoint)
|
||||
assert.Equal(t, 1, a.Status)
|
||||
|
||||
user.FsConfig.Provider = sdk.GCSFilesystemProvider
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(ErrQuotaExceeded), 0, nil)
|
||||
123, 0, c.getNotificationStatus(ErrQuotaExceeded), 0, time.Now(), nil)
|
||||
assert.Equal(t, "gcsbucket", a.Bucket)
|
||||
assert.Equal(t, 0, len(a.Endpoint))
|
||||
assert.Equal(t, 3, a.Status)
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(fmt.Errorf("wrapper quota error: %w", ErrQuotaExceeded)), 0, nil)
|
||||
123, 0, c.getNotificationStatus(fmt.Errorf("wrapper quota error: %w", ErrQuotaExceeded)), 0, time.Now(), nil)
|
||||
assert.Equal(t, "gcsbucket", a.Bucket)
|
||||
assert.Equal(t, 0, len(a.Endpoint))
|
||||
assert.Equal(t, 3, a.Status)
|
||||
|
||||
user.FsConfig.Provider = sdk.HTTPFilesystemProvider
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(nil), 0, nil)
|
||||
123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil)
|
||||
assert.Equal(t, "httpendpoint", a.Endpoint)
|
||||
assert.Equal(t, 1, a.Status)
|
||||
|
||||
user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(nil), 0, nil)
|
||||
123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil)
|
||||
assert.Equal(t, "azcontainer", a.Bucket)
|
||||
assert.Equal(t, "azendpoint", a.Endpoint)
|
||||
assert.Equal(t, 1, a.Status)
|
||||
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
|
||||
123, os.O_APPEND, c.getNotificationStatus(nil), 0, nil)
|
||||
123, os.O_APPEND, c.getNotificationStatus(nil), 0, time.Now(), nil)
|
||||
assert.Equal(t, "azcontainer", a.Bucket)
|
||||
assert.Equal(t, "azendpoint", a.Endpoint)
|
||||
assert.Equal(t, 1, a.Status)
|
||||
|
@ -118,7 +119,7 @@ func TestNewActionNotification(t *testing.T) {
|
|||
|
||||
user.FsConfig.Provider = sdk.SFTPFilesystemProvider
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(nil), 0, nil)
|
||||
123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil)
|
||||
assert.Equal(t, "sftpendpoint", a.Endpoint)
|
||||
}
|
||||
|
||||
|
@ -135,7 +136,7 @@ func TestActionHTTP(t *testing.T) {
|
|||
},
|
||||
}
|
||||
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "",
|
||||
xid.New().String(), 123, 0, 1, 0, nil)
|
||||
xid.New().String(), 123, 0, 1, 0, time.Now(), nil)
|
||||
status, err := actionHandler.Handle(a)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, status)
|
||||
|
@ -175,7 +176,7 @@ func TestActionCMD(t *testing.T) {
|
|||
}
|
||||
sessionID := shortuuid.New()
|
||||
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
|
||||
123, 0, 1, 0, map[string]string{"key": "value"})
|
||||
123, 0, 1, 0, time.Now(), map[string]string{"key": "value"})
|
||||
status, err := actionHandler.Handle(a)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, status)
|
||||
|
@ -208,7 +209,7 @@ func TestWrongActions(t *testing.T) {
|
|||
}
|
||||
|
||||
a := newActionNotification(user, operationUpload, "", "", "", "", "", ProtocolSFTP, "", xid.New().String(),
|
||||
123, 0, 1, 0, nil)
|
||||
123, 0, 1, 0, time.Now(), nil)
|
||||
status, err := actionHandler.Handle(a)
|
||||
assert.Error(t, err, "action with bad command must fail")
|
||||
assert.Equal(t, 1, status)
|
||||
|
|
|
@ -228,6 +228,9 @@ func Initialize(c Configuration, isShared int) error {
|
|||
if err := c.initializeProxyProtocol(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.EventManager.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
vfs.SetTempPath(c.TempPath)
|
||||
dataprovider.SetTempPath(c.TempPath)
|
||||
vfs.SetAllowSelfConnections(c.AllowSelfConnections)
|
||||
|
@ -236,6 +239,7 @@ func Initialize(c Configuration, isShared int) error {
|
|||
vfs.SetResumeMaxSize(c.ResumeMaxSize)
|
||||
vfs.SetUploadMode(c.UploadMode)
|
||||
dataprovider.SetAllowSelfConnections(c.AllowSelfConnections)
|
||||
dataprovider.EnabledActionCommands = c.EventManager.EnabledCommands
|
||||
transfersChecker = getTransfersChecker(isShared)
|
||||
return nil
|
||||
}
|
||||
|
@ -327,6 +331,13 @@ func Reload() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DelayLogin applies the configured login delay
|
||||
func DelayLogin(err error) {
|
||||
if Config.defender != nil {
|
||||
Config.defender.DelayLogin(err)
|
||||
}
|
||||
}
|
||||
|
||||
// IsBanned returns true if the specified IP address is banned
|
||||
func IsBanned(ip, protocol string) bool {
|
||||
if plugin.Handler.IsIPBanned(ip, protocol) {
|
||||
|
@ -477,6 +488,23 @@ type ConnectionTransfer struct {
|
|||
DLSize int64 `json:"-"`
|
||||
}
|
||||
|
||||
// EventManagerConfig defines the configuration for the EventManager
|
||||
type EventManagerConfig struct {
|
||||
// EnabledCommands defines the system commands that can be executed via EventManager,
|
||||
// an empty list means that any command is allowed to be executed.
|
||||
// Commands must be set as an absolute path
|
||||
EnabledCommands []string `json:"enabled_commands" mapstructure:"enabled_commands"`
|
||||
}
|
||||
|
||||
func (c *EventManagerConfig) validate() error {
|
||||
for _, c := range c.EnabledCommands {
|
||||
if !filepath.IsAbs(c) {
|
||||
return fmt.Errorf("invalid command %q: it must be an absolute path", c)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MetadataConfig defines how to handle metadata for cloud storage backends
|
||||
type MetadataConfig struct {
|
||||
// If not zero the metadata will be read before downloads and will be
|
||||
|
@ -582,7 +610,9 @@ type Configuration struct {
|
|||
// Defines the server version
|
||||
ServerVersion string `json:"server_version" mapstructure:"server_version"`
|
||||
// Metadata configuration
|
||||
Metadata MetadataConfig `json:"metadata" mapstructure:"metadata"`
|
||||
Metadata MetadataConfig `json:"metadata" mapstructure:"metadata"`
|
||||
// EventManager configuration
|
||||
EventManager EventManagerConfig `json:"event_manager" mapstructure:"event_manager"`
|
||||
idleTimeoutAsDuration time.Duration
|
||||
idleLoginTimeout time.Duration
|
||||
defender Defender
|
||||
|
@ -624,7 +654,7 @@ func (c *Configuration) GetProxyListener(listener net.Listener) (*proxyproto.Lis
|
|||
|
||||
return &proxyproto.Listener{
|
||||
Listener: listener,
|
||||
Policy: getProxyPolicy(c.proxyAllowed, c.proxySkipped, defaultPolicy),
|
||||
ConnPolicy: getProxyPolicy(c.proxyAllowed, c.proxySkipped, defaultPolicy),
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
}, nil
|
||||
}
|
||||
|
@ -799,12 +829,13 @@ func (c *Configuration) ExecutePostConnectHook(ipAddr, protocol string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func getProxyPolicy(allowed, skipped []func(net.IP) bool, def proxyproto.Policy) proxyproto.PolicyFunc {
|
||||
return func(upstream net.Addr) (proxyproto.Policy, error) {
|
||||
upstreamIP, err := util.GetIPFromNetAddr(upstream)
|
||||
func getProxyPolicy(allowed, skipped []func(net.IP) bool, def proxyproto.Policy) proxyproto.ConnPolicyFunc {
|
||||
return func(connPolicyOptions proxyproto.ConnPolicyOptions) (proxyproto.Policy, error) {
|
||||
upstreamIP, err := util.GetIPFromNetAddr(connPolicyOptions.Upstream)
|
||||
if err != nil {
|
||||
// something is wrong with the source IP, better reject the connection
|
||||
return proxyproto.REJECT, err
|
||||
// Something is wrong with the source IP, better reject the
|
||||
// connection.
|
||||
return proxyproto.REJECT, proxyproto.ErrInvalidUpstream
|
||||
}
|
||||
|
||||
for _, skippedFrom := range skipped {
|
||||
|
@ -822,6 +853,11 @@ func getProxyPolicy(allowed, skipped []func(net.IP) bool, def proxyproto.Policy)
|
|||
}
|
||||
}
|
||||
|
||||
if def == proxyproto.REQUIRE {
|
||||
logger.Debug(logSender, "", "reject connection from ip %q: proxy protocol signature required and not set",
|
||||
upstreamIP)
|
||||
return proxyproto.REJECT, proxyproto.ErrInvalidUpstream
|
||||
}
|
||||
return def, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -216,6 +216,33 @@ func TestConnections(t *testing.T) {
|
|||
Connections.RUnlock()
|
||||
}
|
||||
|
||||
func TestEventManagerCommandsInitialization(t *testing.T) {
|
||||
configCopy := Config
|
||||
|
||||
c := Configuration{
|
||||
EventManager: EventManagerConfig{
|
||||
EnabledCommands: []string{"ls"}, // not an absolute path
|
||||
},
|
||||
}
|
||||
err := Initialize(c, 0)
|
||||
assert.ErrorContains(t, err, "invalid command")
|
||||
|
||||
var commands []string
|
||||
if runtime.GOOS == osWindows {
|
||||
commands = []string{"C:\\command"}
|
||||
} else {
|
||||
commands = []string{"/bin/ls"}
|
||||
}
|
||||
|
||||
c.EventManager.EnabledCommands = commands
|
||||
err = Initialize(c, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, commands, dataprovider.EnabledActionCommands)
|
||||
|
||||
dataprovider.EnabledActionCommands = configCopy.EventManager.EnabledCommands
|
||||
Config = configCopy
|
||||
}
|
||||
|
||||
func TestInitializationProxyErrors(t *testing.T) {
|
||||
configCopy := Config
|
||||
|
||||
|
@ -419,6 +446,9 @@ func TestDefenderIntegration(t *testing.T) {
|
|||
ObservationTime: 15,
|
||||
EntriesSoftLimit: 100,
|
||||
EntriesHardLimit: 150,
|
||||
LoginDelay: LoginDelay{
|
||||
PasswordFailed: 200,
|
||||
},
|
||||
}
|
||||
err = Initialize(Config, 0)
|
||||
// ScoreInvalid cannot be greater than threshold
|
||||
|
@ -477,6 +507,16 @@ func TestDefenderIntegration(t *testing.T) {
|
|||
assert.Nil(t, banTime)
|
||||
assert.False(t, DeleteDefenderHost(ip))
|
||||
|
||||
startTime := time.Now()
|
||||
DelayLogin(nil)
|
||||
elapsed := time.Since(startTime)
|
||||
assert.Less(t, elapsed, time.Millisecond*50)
|
||||
|
||||
startTime = time.Now()
|
||||
DelayLogin(ErrInternalFailure)
|
||||
elapsed = time.Since(startTime)
|
||||
assert.Greater(t, elapsed, time.Millisecond*150)
|
||||
|
||||
Config = configCopy
|
||||
}
|
||||
|
||||
|
@ -1028,9 +1068,13 @@ func TestQuotaScansRole(t *testing.T) {
|
|||
|
||||
func TestProxyPolicy(t *testing.T) {
|
||||
addr := net.TCPAddr{}
|
||||
downstream := net.TCPAddr{IP: net.ParseIP("1.1.1.1")}
|
||||
p := getProxyPolicy(nil, nil, proxyproto.IGNORE)
|
||||
policy, err := p(&addr)
|
||||
assert.Error(t, err)
|
||||
policy, err := p(proxyproto.ConnPolicyOptions{
|
||||
Upstream: &addr,
|
||||
Downstream: &downstream,
|
||||
})
|
||||
assert.ErrorIs(t, err, proxyproto.ErrInvalidUpstream)
|
||||
assert.Equal(t, proxyproto.REJECT, policy)
|
||||
ip1 := net.ParseIP("10.8.1.1")
|
||||
ip2 := net.ParseIP("10.8.1.2")
|
||||
|
@ -1040,31 +1084,55 @@ func TestProxyPolicy(t *testing.T) {
|
|||
skipped, err := util.ParseAllowedIPAndRanges([]string{ip2.String(), ip3.String()})
|
||||
assert.NoError(t, err)
|
||||
p = getProxyPolicy(allowed, skipped, proxyproto.IGNORE)
|
||||
policy, err = p(&net.TCPAddr{IP: ip1})
|
||||
policy, err = p(proxyproto.ConnPolicyOptions{
|
||||
Upstream: &net.TCPAddr{IP: ip1},
|
||||
Downstream: &downstream,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, proxyproto.USE, policy)
|
||||
policy, err = p(&net.TCPAddr{IP: ip2})
|
||||
policy, err = p(proxyproto.ConnPolicyOptions{
|
||||
Upstream: &net.TCPAddr{IP: ip2},
|
||||
Downstream: &downstream,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, proxyproto.SKIP, policy)
|
||||
policy, err = p(&net.TCPAddr{IP: ip3})
|
||||
policy, err = p(proxyproto.ConnPolicyOptions{
|
||||
Upstream: &net.TCPAddr{IP: ip3},
|
||||
Downstream: &downstream,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, proxyproto.SKIP, policy)
|
||||
policy, err = p(&net.TCPAddr{IP: net.ParseIP("10.8.1.4")})
|
||||
policy, err = p(proxyproto.ConnPolicyOptions{
|
||||
Upstream: &net.TCPAddr{IP: net.ParseIP("10.8.1.4")},
|
||||
Downstream: &downstream,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, proxyproto.IGNORE, policy)
|
||||
p = getProxyPolicy(allowed, skipped, proxyproto.REQUIRE)
|
||||
policy, err = p(&net.TCPAddr{IP: ip1})
|
||||
policy, err = p(proxyproto.ConnPolicyOptions{
|
||||
Upstream: &net.TCPAddr{IP: ip1},
|
||||
Downstream: &downstream,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, proxyproto.REQUIRE, policy)
|
||||
policy, err = p(&net.TCPAddr{IP: ip2})
|
||||
policy, err = p(proxyproto.ConnPolicyOptions{
|
||||
Upstream: &net.TCPAddr{IP: ip2},
|
||||
Downstream: &downstream,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, proxyproto.SKIP, policy)
|
||||
policy, err = p(&net.TCPAddr{IP: ip3})
|
||||
policy, err = p(proxyproto.ConnPolicyOptions{
|
||||
Upstream: &net.TCPAddr{IP: ip3},
|
||||
Downstream: &downstream,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, proxyproto.SKIP, policy)
|
||||
policy, err = p(&net.TCPAddr{IP: net.ParseIP("10.8.1.5")})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, proxyproto.REQUIRE, policy)
|
||||
policy, err = p(proxyproto.ConnPolicyOptions{
|
||||
Upstream: &net.TCPAddr{IP: net.ParseIP("10.8.1.5")},
|
||||
Downstream: &downstream,
|
||||
})
|
||||
assert.ErrorIs(t, err, proxyproto.ErrInvalidUpstream)
|
||||
assert.Equal(t, proxyproto.REJECT, policy)
|
||||
}
|
||||
|
||||
func TestProxyProtocolVersion(t *testing.T) {
|
||||
|
@ -1078,12 +1146,12 @@ func TestProxyProtocolVersion(t *testing.T) {
|
|||
c.ProxyProtocol = 1
|
||||
proxyListener, err := c.GetProxyListener(nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, proxyListener.Policy)
|
||||
assert.NotNil(t, proxyListener.ConnPolicy)
|
||||
|
||||
c.ProxyProtocol = 2
|
||||
proxyListener, err = c.GetProxyListener(nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, proxyListener.Policy)
|
||||
assert.NotNil(t, proxyListener.ConnPolicy)
|
||||
}
|
||||
|
||||
func TestStartupHook(t *testing.T) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
@ -321,10 +322,9 @@ func (c *BaseConnection) ListDir(virtualPath string) (*DirListerAt, error) {
|
|||
}
|
||||
return &DirListerAt{
|
||||
virtualPath: virtualPath,
|
||||
user: &c.User,
|
||||
conn: c,
|
||||
fs: fs,
|
||||
info: c.User.GetVirtualFoldersInfo(virtualPath),
|
||||
id: c.ID,
|
||||
protocol: c.protocol,
|
||||
lister: lister,
|
||||
}, nil
|
||||
}
|
||||
|
@ -792,7 +792,7 @@ func (c *BaseConnection) Rename(virtualSourcePath, virtualTargetPath string) err
|
|||
return c.renameInternal(virtualSourcePath, virtualTargetPath, false)
|
||||
}
|
||||
|
||||
func (c *BaseConnection) renameInternal(virtualSourcePath, virtualTargetPath string, checkParentDestination bool) error {
|
||||
func (c *BaseConnection) renameInternal(virtualSourcePath, virtualTargetPath string, checkParentDestination bool) error { //nolint:gocyclo
|
||||
if virtualSourcePath == virtualTargetPath {
|
||||
return fmt.Errorf("the rename source and target cannot be the same: %w", c.GetOpUnsupportedError())
|
||||
}
|
||||
|
@ -813,7 +813,11 @@ func (c *BaseConnection) renameInternal(virtualSourcePath, virtualTargetPath str
|
|||
return c.GetPermissionDeniedError()
|
||||
}
|
||||
initialSize := int64(-1)
|
||||
if dstInfo, err := fsDst.Lstat(fsTargetPath); err == nil {
|
||||
dstInfo, err := fsDst.Lstat(fsTargetPath)
|
||||
if err != nil && !fsDst.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
checkParentDestination = false
|
||||
if dstInfo.IsDir() {
|
||||
c.Log(logger.LevelWarn, "attempted to rename %q overwriting an existing directory %q",
|
||||
|
@ -918,16 +922,6 @@ func (c *BaseConnection) CreateSymlink(virtualSourcePath, virtualTargetPath stri
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *BaseConnection) getPathForSetStatPerms(fs vfs.Fs, fsPath, virtualPath string) string {
|
||||
pathForPerms := virtualPath
|
||||
if fi, err := fs.Lstat(fsPath); err == nil {
|
||||
if fi.IsDir() {
|
||||
pathForPerms = path.Dir(virtualPath)
|
||||
}
|
||||
}
|
||||
return pathForPerms
|
||||
}
|
||||
|
||||
func (c *BaseConnection) doStatInternal(virtualPath string, mode int, checkFilePatterns,
|
||||
convertResult bool,
|
||||
) (os.FileInfo, error) {
|
||||
|
@ -1064,7 +1058,7 @@ func (c *BaseConnection) SetStat(virtualPath string, attributes *StatAttributes)
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pathForPerms := c.getPathForSetStatPerms(fs, fsPath, virtualPath)
|
||||
pathForPerms := path.Dir(virtualPath)
|
||||
|
||||
if attributes.Flags&StatAttrTimes != 0 {
|
||||
if err = c.handleChtimes(fs, fsPath, pathForPerms, attributes); err != nil {
|
||||
|
@ -1814,10 +1808,9 @@ func (c *BaseConnection) GetFsAndResolvedPath(virtualPath string) (vfs.Fs, strin
|
|||
// DirListerAt defines a directory lister implementing the ListAt method.
|
||||
type DirListerAt struct {
|
||||
virtualPath string
|
||||
user *dataprovider.User
|
||||
conn *BaseConnection
|
||||
fs vfs.Fs
|
||||
info []os.FileInfo
|
||||
id string
|
||||
protocol string
|
||||
mu sync.Mutex
|
||||
lister vfs.DirLister
|
||||
}
|
||||
|
@ -1827,7 +1820,7 @@ func (l *DirListerAt) Add(fi os.FileInfo) {
|
|||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
l.info = append(l.info, fi)
|
||||
l.info = slices.Insert(l.info, 0, fi)
|
||||
}
|
||||
|
||||
// ListAt implements sftp.ListerAt
|
||||
|
@ -1840,10 +1833,10 @@ func (l *DirListerAt) ListAt(f []os.FileInfo, _ int64) (int, error) {
|
|||
}
|
||||
if len(f) <= len(l.info) {
|
||||
files := make([]os.FileInfo, 0, len(f))
|
||||
for idx := len(l.info) - 1; idx >= 0; idx-- {
|
||||
for idx := range l.info {
|
||||
files = append(files, l.info[idx])
|
||||
if len(files) == len(f) {
|
||||
l.info = l.info[:idx]
|
||||
l.info = l.info[idx+1:]
|
||||
n := copy(f, files)
|
||||
return n, nil
|
||||
}
|
||||
|
@ -1860,14 +1853,12 @@ func (l *DirListerAt) Next(limit int) ([]os.FileInfo, error) {
|
|||
for {
|
||||
files, err := l.lister.Next(limit)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
logger.Debug(l.protocol, l.id, "error retrieving directory entries: %+v", err)
|
||||
return files, err
|
||||
l.conn.Log(logger.LevelDebug, "error retrieving directory entries: %+v", err)
|
||||
return files, l.conn.GetFsError(l.fs, err)
|
||||
}
|
||||
files = l.user.FilterListDir(files, l.virtualPath)
|
||||
files = l.conn.User.FilterListDir(files, l.virtualPath)
|
||||
if len(l.info) > 0 {
|
||||
for _, fi := range l.info {
|
||||
files = util.PrependFileInfo(files, fi)
|
||||
}
|
||||
files = slices.Concat(l.info, files)
|
||||
l.info = nil
|
||||
}
|
||||
if err != nil || len(files) > 0 {
|
||||
|
|
|
@ -1090,7 +1090,7 @@ func TestListerAt(t *testing.T) {
|
|||
require.ErrorIs(t, err, io.EOF)
|
||||
require.Len(t, files, 0)
|
||||
_, err = lister.Next(-1)
|
||||
require.ErrorContains(t, err, "invalid limit")
|
||||
require.ErrorContains(t, err, conn.GetGenericError(err).Error())
|
||||
err = lister.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
|
@ -269,7 +269,7 @@ func (c *RetentionCheck) cleanupFolder(folderPath string, recursion int) error {
|
|||
return nil
|
||||
}
|
||||
result.Error = fmt.Sprintf("unable to get lister for directory %q", folderPath)
|
||||
c.conn.Log(logger.LevelError, result.Error)
|
||||
c.conn.Log(logger.LevelError, "%s", result.Error)
|
||||
return err
|
||||
}
|
||||
defer lister.Close()
|
||||
|
|
|
@ -53,6 +53,7 @@ type Defender interface {
|
|||
GetBanTime(ip string) (*time.Time, error)
|
||||
GetScore(ip string) (int, error)
|
||||
DeleteHost(ip string) bool
|
||||
DelayLogin(err error)
|
||||
}
|
||||
|
||||
// DefenderConfig defines the "defender" configuration
|
||||
|
@ -90,6 +91,16 @@ type DefenderConfig struct {
|
|||
// to return when you request for the entire host list from the defender
|
||||
EntriesSoftLimit int `json:"entries_soft_limit" mapstructure:"entries_soft_limit"`
|
||||
EntriesHardLimit int `json:"entries_hard_limit" mapstructure:"entries_hard_limit"`
|
||||
// Configuration to impose a delay between login attempts
|
||||
LoginDelay LoginDelay `json:"login_delay" mapstructure:"login_delay"`
|
||||
}
|
||||
|
||||
// LoginDelay defines the delays to impose between login attempts.
|
||||
type LoginDelay struct {
|
||||
// The number of milliseconds to pause prior to allowing a successful login
|
||||
Success int `json:"success" mapstructure:"success"`
|
||||
// The number of milliseconds to pause prior to reporting a failed login
|
||||
PasswordFailed int `json:"password_failed" mapstructure:"password_failed"`
|
||||
}
|
||||
|
||||
type baseDefender struct {
|
||||
|
@ -163,6 +174,19 @@ func (d *baseDefender) logBan(ip, protocol string) {
|
|||
Send()
|
||||
}
|
||||
|
||||
// DelayLogin applies the configured login delay.
|
||||
func (d *baseDefender) DelayLogin(err error) {
|
||||
if err == nil {
|
||||
if d.config.LoginDelay.Success > 0 {
|
||||
time.Sleep(time.Duration(d.config.LoginDelay.Success) * time.Millisecond)
|
||||
}
|
||||
return
|
||||
}
|
||||
if d.config.LoginDelay.PasswordFailed > 0 {
|
||||
time.Sleep(time.Duration(d.config.LoginDelay.PasswordFailed) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
type hostEvent struct {
|
||||
dateTime time.Time
|
||||
score int
|
||||
|
|
|
@ -435,6 +435,31 @@ func TestDefenderCleanup(t *testing.T) {
|
|||
assert.Equal(t, 0, score)
|
||||
}
|
||||
|
||||
func TestDefenderDelay(t *testing.T) {
|
||||
d := memoryDefender{
|
||||
baseDefender: baseDefender{
|
||||
config: &DefenderConfig{
|
||||
ObservationTime: 1,
|
||||
EntriesSoftLimit: 2,
|
||||
EntriesHardLimit: 3,
|
||||
LoginDelay: LoginDelay{
|
||||
Success: 50,
|
||||
PasswordFailed: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
startTime := time.Now()
|
||||
d.DelayLogin(nil)
|
||||
elapsed := time.Since(startTime)
|
||||
assert.Less(t, elapsed, time.Millisecond*100)
|
||||
|
||||
startTime = time.Now()
|
||||
d.DelayLogin(ErrInternalFailure)
|
||||
elapsed = time.Since(startTime)
|
||||
assert.Greater(t, elapsed, time.Millisecond*150)
|
||||
}
|
||||
|
||||
func TestDefenderConfig(t *testing.T) {
|
||||
c := DefenderConfig{}
|
||||
err := c.validate()
|
||||
|
|
|
@ -110,7 +110,7 @@ func (d *dbDefender) AddEvent(ip, protocol string, event HostEvent) bool {
|
|||
eventManager.handleIPBlockedEvent(EventParams{
|
||||
Event: ipBlockedEventName,
|
||||
IP: ip,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Status: 1,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -218,7 +218,7 @@ func (d *memoryDefender) AddEvent(ip, protocol string, event HostEvent) bool {
|
|||
eventManager.handleIPBlockedEvent(EventParams{
|
||||
Event: ipBlockedEventName,
|
||||
IP: ip,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Status: 1,
|
||||
})
|
||||
} else {
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -57,6 +58,7 @@ const (
|
|||
maxAttachmentsSize = int64(10 * 1024 * 1024)
|
||||
objDataPlaceholder = "{{ObjectData}}"
|
||||
objDataPlaceholderString = "{{ObjectDataString}}"
|
||||
dateTimeMillisFormat = "2006-01-02T15:04:05.000"
|
||||
)
|
||||
|
||||
// Supported IDP login events
|
||||
|
@ -69,6 +71,9 @@ var (
|
|||
// eventManager handle the supported event rules actions
|
||||
eventManager eventRulesContainer
|
||||
multipartQuoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
|
||||
fsEventsWithSize = []string{operationPreDelete, OperationPreUpload, operationDelete,
|
||||
operationCopy, operationDownload, operationFirstUpload, operationFirstDownload,
|
||||
operationUpload}
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -88,11 +93,12 @@ func init() {
|
|||
ObjectType: objectType,
|
||||
IP: ip,
|
||||
Role: role,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Object: object,
|
||||
}
|
||||
if u, ok := object.(*dataprovider.User); ok {
|
||||
p.Email = u.Email
|
||||
p.Groups = u.Groups
|
||||
} else if a, ok := object.(*dataprovider.Admin); ok {
|
||||
p.Email = a.Email
|
||||
}
|
||||
|
@ -313,6 +319,9 @@ func (*eventRulesContainer) checkProviderEventMatch(conditions *dataprovider.Eve
|
|||
if !checkEventConditionPatterns(params.Name, conditions.Options.Names) {
|
||||
return false
|
||||
}
|
||||
if !checkEventGroupConditionPatterns(params.Groups, conditions.Options.GroupNames) {
|
||||
return false
|
||||
}
|
||||
if !checkEventConditionPatterns(params.Role, conditions.Options.RoleNames) {
|
||||
return false
|
||||
}
|
||||
|
@ -341,7 +350,7 @@ func (*eventRulesContainer) checkFsEventMatch(conditions *dataprovider.EventCond
|
|||
if len(conditions.Options.Protocols) > 0 && !util.Contains(conditions.Options.Protocols, params.Protocol) {
|
||||
return false
|
||||
}
|
||||
if params.Event == operationUpload || params.Event == operationDownload {
|
||||
if slices.Contains(fsEventsWithSize, params.Event) {
|
||||
if conditions.Options.MinFileSize > 0 {
|
||||
if params.FileSize < conditions.Options.MinFileSize {
|
||||
return false
|
||||
|
@ -555,7 +564,7 @@ type EventParams struct {
|
|||
IP string
|
||||
Role string
|
||||
Email string
|
||||
Timestamp int64
|
||||
Timestamp time.Time
|
||||
UID string
|
||||
IDPCustomFields *map[string]string
|
||||
Object plugin.Renderer
|
||||
|
@ -639,7 +648,7 @@ func (p *EventParams) setBackupParams(backupPath string) {
|
|||
p.FsPath = backupPath
|
||||
p.ObjectName = filepath.Base(backupPath)
|
||||
p.VirtualPath = "/" + p.ObjectName
|
||||
p.Timestamp = time.Now().UnixNano()
|
||||
p.Timestamp = time.Now()
|
||||
info, err := os.Stat(backupPath)
|
||||
if err == nil {
|
||||
p.FileSize = info.Size()
|
||||
|
@ -778,6 +787,7 @@ func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []s
|
|||
"{{Event}}", p.Event,
|
||||
"{{Status}}", fmt.Sprintf("%d", p.Status),
|
||||
"{{VirtualPath}}", p.getStringReplacement(p.VirtualPath, jsonEscaped),
|
||||
"{{EscapedVirtualPath}}", p.getStringReplacement(url.QueryEscape(p.VirtualPath), jsonEscaped),
|
||||
"{{FsPath}}", p.getStringReplacement(p.FsPath, jsonEscaped),
|
||||
"{{VirtualTargetPath}}", p.getStringReplacement(p.VirtualTargetPath, jsonEscaped),
|
||||
"{{FsTargetPath}}", p.getStringReplacement(p.FsTargetPath, jsonEscaped),
|
||||
|
@ -789,7 +799,8 @@ func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []s
|
|||
"{{IP}}", p.IP,
|
||||
"{{Role}}", p.getStringReplacement(p.Role, jsonEscaped),
|
||||
"{{Email}}", p.getStringReplacement(p.Email, jsonEscaped),
|
||||
"{{Timestamp}}", strconv.FormatInt(p.Timestamp, 10),
|
||||
"{{Timestamp}}", strconv.FormatInt(p.Timestamp.UnixNano(), 10),
|
||||
"{{DateTime}}", p.Timestamp.UTC().Format(dateTimeMillisFormat),
|
||||
"{{StatusString}}", p.getStatusString(),
|
||||
"{{UID}}", p.getStringReplacement(p.UID, jsonEscaped),
|
||||
"{{Ext}}", p.getStringReplacement(p.Extension, jsonEscaped),
|
||||
|
@ -1316,7 +1327,7 @@ func writeHTTPPart(m *multipart.Writer, part dataprovider.HTTPPart, h textproto.
|
|||
return nil
|
||||
}
|
||||
|
||||
func getHTTPRuleActionBody(c *dataprovider.EventActionHTTPConfig, replacer *strings.Replacer,
|
||||
func getHTTPRuleActionBody(c *dataprovider.EventActionHTTPConfig, replacer *strings.Replacer, //nolint:gocyclo
|
||||
cancel context.CancelFunc, user dataprovider.User, params *EventParams, addObjectData bool,
|
||||
) (io.Reader, string, error) {
|
||||
var body io.Reader
|
||||
|
@ -1362,6 +1373,9 @@ func getHTTPRuleActionBody(c *dataprovider.EventActionHTTPConfig, replacer *stri
|
|||
go func() {
|
||||
defer w.Close()
|
||||
defer user.CloseFs() //nolint:errcheck
|
||||
if conn != nil {
|
||||
defer conn.CloseFS() //nolint:errcheck
|
||||
}
|
||||
|
||||
for _, part := range c.Parts {
|
||||
h := make(textproto.MIMEHeader)
|
||||
|
@ -1476,6 +1490,9 @@ func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventPa
|
|||
}
|
||||
|
||||
func executeCommandRuleAction(c dataprovider.EventActionCommandConfig, params *EventParams) error {
|
||||
if !dataprovider.IsActionCommandAllowed(c.Cmd) {
|
||||
return fmt.Errorf("command %q is not allowed", c.Cmd)
|
||||
}
|
||||
addObjectData := false
|
||||
if params.Object != nil {
|
||||
for _, k := range c.EnvVars {
|
||||
|
@ -1576,6 +1593,8 @@ func executeEmailRuleAction(c dataprovider.EventActionEmailConfig, params *Event
|
|||
return fmt.Errorf("error getting email attachments, unable to check root fs for user %q: %w", user.Username, err)
|
||||
}
|
||||
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
||||
defer conn.CloseFS() //nolint:errcheck
|
||||
|
||||
res, err := getMailAttachments(conn, fileAttachments, replacer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1637,6 +1656,8 @@ func executeDeleteFsActionForUser(deletes []string, replacer *strings.Replacer,
|
|||
return fmt.Errorf("delete error, unable to check root fs for user %q: %w", user.Username, err)
|
||||
}
|
||||
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
||||
defer conn.CloseFS() //nolint:errcheck
|
||||
|
||||
for _, item := range replacePathsPlaceholders(deletes, replacer) {
|
||||
info, err := conn.DoStat(item, 0, false)
|
||||
if err != nil {
|
||||
|
@ -1705,6 +1726,8 @@ func executeMkDirsFsActionForUser(dirs []string, replacer *strings.Replacer, use
|
|||
return fmt.Errorf("mkdir error, unable to check root fs for user %q: %w", user.Username, err)
|
||||
}
|
||||
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
||||
defer conn.CloseFS() //nolint:errcheck
|
||||
|
||||
for _, item := range replacePathsPlaceholders(dirs, replacer) {
|
||||
if err = conn.CheckParentDirs(path.Dir(item)); err != nil {
|
||||
return fmt.Errorf("unable to check parent dirs for %q, user %q: %w", item, user.Username, err)
|
||||
|
@ -1764,6 +1787,8 @@ func executeRenameFsActionForUser(renames []dataprovider.KeyValue, replacer *str
|
|||
return fmt.Errorf("rename error, unable to check root fs for user %q: %w", user.Username, err)
|
||||
}
|
||||
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
||||
defer conn.CloseFS() //nolint:errcheck
|
||||
|
||||
for _, item := range renames {
|
||||
source := util.CleanPath(replaceWithReplacer(item.Key, replacer))
|
||||
target := util.CleanPath(replaceWithReplacer(item.Value, replacer))
|
||||
|
@ -1775,7 +1800,7 @@ func executeRenameFsActionForUser(renames []dataprovider.KeyValue, replacer *str
|
|||
return nil
|
||||
}
|
||||
|
||||
func executeCopyFsActionForUser(copy []dataprovider.KeyValue, replacer *strings.Replacer,
|
||||
func executeCopyFsActionForUser(keyVals []dataprovider.KeyValue, replacer *strings.Replacer,
|
||||
user dataprovider.User,
|
||||
) error {
|
||||
user, err := getUserForEventAction(user)
|
||||
|
@ -1789,7 +1814,9 @@ func executeCopyFsActionForUser(copy []dataprovider.KeyValue, replacer *strings.
|
|||
return fmt.Errorf("copy error, unable to check root fs for user %q: %w", user.Username, err)
|
||||
}
|
||||
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
||||
for _, item := range copy {
|
||||
defer conn.CloseFS() //nolint:errcheck
|
||||
|
||||
for _, item := range keyVals {
|
||||
source := util.CleanPath(replaceWithReplacer(item.Key, replacer))
|
||||
target := util.CleanPath(replaceWithReplacer(item.Value, replacer))
|
||||
if strings.HasSuffix(item.Key, "/") {
|
||||
|
@ -1820,6 +1847,8 @@ func executeExistFsActionForUser(exist []string, replacer *strings.Replacer,
|
|||
return fmt.Errorf("existence check error, unable to check root fs for user %q: %w", user.Username, err)
|
||||
}
|
||||
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
||||
defer conn.CloseFS() //nolint:errcheck
|
||||
|
||||
for _, item := range replacePathsPlaceholders(exist, replacer) {
|
||||
if _, err = conn.DoStat(item, 0, false); err != nil {
|
||||
return fmt.Errorf("error checking existence for path %q, user %q: %w", item, user.Username, err)
|
||||
|
@ -1863,7 +1892,7 @@ func executeRenameFsRuleAction(renames []dataprovider.KeyValue, replacer *string
|
|||
return nil
|
||||
}
|
||||
|
||||
func executeCopyFsRuleAction(copy []dataprovider.KeyValue, replacer *strings.Replacer,
|
||||
func executeCopyFsRuleAction(keyVals []dataprovider.KeyValue, replacer *strings.Replacer,
|
||||
conditions dataprovider.ConditionOptions, params *EventParams,
|
||||
) error {
|
||||
users, err := params.getUsers()
|
||||
|
@ -1882,7 +1911,7 @@ func executeCopyFsRuleAction(copy []dataprovider.KeyValue, replacer *strings.Rep
|
|||
}
|
||||
}
|
||||
executed++
|
||||
if err = executeCopyFsActionForUser(copy, replacer, user); err != nil {
|
||||
if err = executeCopyFsActionForUser(keyVals, replacer, user); err != nil {
|
||||
failures = append(failures, user.Username)
|
||||
params.AddError(err)
|
||||
}
|
||||
|
@ -1978,6 +2007,8 @@ func executeCompressFsActionForUser(c dataprovider.EventActionFsCompress, replac
|
|||
return fmt.Errorf("compress error, unable to check root fs for user %q: %w", user.Username, err)
|
||||
}
|
||||
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
||||
defer conn.CloseFS() //nolint:errcheck
|
||||
|
||||
name := util.CleanPath(replaceWithReplacer(c.Name, replacer))
|
||||
conn.CheckParentDirs(path.Dir(name)) //nolint:errcheck
|
||||
paths := make([]string, 0, len(c.Paths))
|
||||
|
@ -2505,19 +2536,54 @@ func executeAdminCheckAction(c *dataprovider.EventActionIDPAccountCheck, params
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if newAdmin.Password == "" {
|
||||
newAdmin.Password = util.GenerateUniqueID()
|
||||
}
|
||||
if exists {
|
||||
eventManagerLog(logger.LevelDebug, "updating admin %q after IDP login", params.Name)
|
||||
// Not sure if this makes sense, but it shouldn't hurt.
|
||||
if newAdmin.Password == "" {
|
||||
newAdmin.Password = admin.Password
|
||||
}
|
||||
newAdmin.Filters.TOTPConfig = admin.Filters.TOTPConfig
|
||||
newAdmin.Filters.RecoveryCodes = admin.Filters.RecoveryCodes
|
||||
err = dataprovider.UpdateAdmin(&newAdmin, dataprovider.ActionExecutorSystem, "", "")
|
||||
} else {
|
||||
eventManagerLog(logger.LevelDebug, "creating admin %q after IDP login", params.Name)
|
||||
if newAdmin.Password == "" {
|
||||
newAdmin.Password = util.GenerateUniqueID()
|
||||
}
|
||||
err = dataprovider.AddAdmin(&newAdmin, dataprovider.ActionExecutorSystem, "", "")
|
||||
}
|
||||
return &newAdmin, err
|
||||
}
|
||||
|
||||
func preserveUserProfile(user, newUser *dataprovider.User) {
|
||||
if newUser.CanChangePassword() && user.Password != "" {
|
||||
newUser.Password = user.Password
|
||||
}
|
||||
if newUser.CanManagePublicKeys() && len(user.PublicKeys) > 0 {
|
||||
newUser.PublicKeys = user.PublicKeys
|
||||
}
|
||||
if newUser.CanManageTLSCerts() {
|
||||
if len(user.Filters.TLSCerts) > 0 {
|
||||
newUser.Filters.TLSCerts = user.Filters.TLSCerts
|
||||
}
|
||||
}
|
||||
if newUser.CanChangeInfo() {
|
||||
if user.Description != "" {
|
||||
newUser.Description = user.Description
|
||||
}
|
||||
if user.Email != "" {
|
||||
newUser.Email = user.Email
|
||||
}
|
||||
}
|
||||
if newUser.CanChangeAPIKeyAuth() {
|
||||
newUser.Filters.AllowAPIKeyAuth = user.Filters.AllowAPIKeyAuth
|
||||
}
|
||||
newUser.Filters.RecoveryCodes = user.Filters.RecoveryCodes
|
||||
newUser.Filters.TOTPConfig = user.Filters.TOTPConfig
|
||||
newUser.LastPasswordChange = user.LastPasswordChange
|
||||
newUser.SetEmptySecretsIfNil()
|
||||
}
|
||||
|
||||
func executeUserCheckAction(c *dataprovider.EventActionIDPAccountCheck, params *EventParams) (*dataprovider.User, error) {
|
||||
user, err := dataprovider.UserExists(params.Name, "")
|
||||
exists := err == nil
|
||||
|
@ -2539,6 +2605,7 @@ func executeUserCheckAction(c *dataprovider.EventActionIDPAccountCheck, params *
|
|||
}
|
||||
if exists {
|
||||
eventManagerLog(logger.LevelDebug, "updating user %q after IDP login", params.Name)
|
||||
preserveUserProfile(&user, &newUser)
|
||||
err = dataprovider.UpdateUser(&newUser, dataprovider.ActionExecutorSystem, "", "")
|
||||
} else {
|
||||
eventManagerLog(logger.LevelDebug, "creating user %q after IDP login", params.Name)
|
||||
|
@ -2551,9 +2618,14 @@ func executeUserCheckAction(c *dataprovider.EventActionIDPAccountCheck, params *
|
|||
return &u, err
|
||||
}
|
||||
|
||||
func executeRuleAction(action dataprovider.BaseEventAction, params *EventParams,
|
||||
func executeRuleAction(action dataprovider.BaseEventAction, params *EventParams, //nolint:gocyclo
|
||||
conditions dataprovider.ConditionOptions,
|
||||
) error {
|
||||
if len(conditions.EventStatuses) > 0 && !slices.Contains(conditions.EventStatuses, params.Status) {
|
||||
eventManagerLog(logger.LevelDebug, "skipping action %s, event status %d does not match: %v",
|
||||
action.Name, params.Status, conditions.EventStatuses)
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
|
||||
switch action.Type {
|
||||
|
@ -2585,6 +2657,8 @@ func executeRuleAction(action dataprovider.BaseEventAction, params *EventParams,
|
|||
err = executeUserExpirationCheckRuleAction(conditions, params)
|
||||
case dataprovider.ActionTypeUserInactivityCheck:
|
||||
err = executeUserInactivityCheckRuleAction(action.Options.UserInactivityConfig, conditions, params, time.Now())
|
||||
case dataprovider.ActionTypeRotateLogs:
|
||||
err = logger.RotateLogFile()
|
||||
default:
|
||||
err = fmt.Errorf("unsupported action type: %d", action.Type)
|
||||
}
|
||||
|
|
|
@ -800,6 +800,17 @@ func TestEventManagerErrors(t *testing.T) {
|
|||
stopEventScheduler()
|
||||
}
|
||||
|
||||
func TestDateTimePlaceholder(t *testing.T) {
|
||||
dateTime := time.Now()
|
||||
params := EventParams{
|
||||
Timestamp: dateTime,
|
||||
}
|
||||
replacements := params.getStringReplacements(false, false)
|
||||
r := strings.NewReplacer(replacements...)
|
||||
res := r.Replace("{{DateTime}}")
|
||||
assert.Equal(t, dateTime.UTC().Format(dateTimeMillisFormat), res)
|
||||
}
|
||||
|
||||
func TestEventRuleActions(t *testing.T) {
|
||||
actionName := "test rule action"
|
||||
action := dataprovider.BaseEventAction{
|
||||
|
@ -1386,6 +1397,29 @@ func TestIDPAccountCheckRule(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, username, user.Username)
|
||||
assert.Equal(t, 1, user.Status)
|
||||
assert.Empty(t, user.Password)
|
||||
assert.Len(t, user.PublicKeys, 0)
|
||||
assert.Len(t, user.Filters.TLSCerts, 0)
|
||||
assert.Empty(t, user.Email)
|
||||
assert.Empty(t, user.Description)
|
||||
// Update the profile attribute and make sure they are preserved
|
||||
user.Password = "secret"
|
||||
user.Email = "example@example.com"
|
||||
user.Description = "some desc"
|
||||
user.Filters.TLSCerts = []string{serverCert}
|
||||
user.PublicKeys = []string{"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0= nicola@p1"}
|
||||
err = dataprovider.UpdateUser(user, "", "", "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
user, err = executeUserCheckAction(c, params)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, username, user.Username)
|
||||
assert.Equal(t, 1, user.Status)
|
||||
assert.NotEmpty(t, user.Password)
|
||||
assert.Len(t, user.PublicKeys, 1)
|
||||
assert.Len(t, user.Filters.TLSCerts, 1)
|
||||
assert.NotEmpty(t, user.Email)
|
||||
assert.NotEmpty(t, user.Description)
|
||||
|
||||
err = dataprovider.DeleteUser(username, "", "", "")
|
||||
assert.NoError(t, err)
|
||||
|
|
|
@ -26,10 +26,12 @@ import (
|
|||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
@ -3814,6 +3816,7 @@ func TestEventRule(t *testing.T) {
|
|||
Conditions: dataprovider.EventConditions{
|
||||
FsEvents: []string{"upload"},
|
||||
Options: dataprovider.ConditionOptions{
|
||||
EventStatuses: []int{1},
|
||||
FsPaths: []dataprovider.ConditionPattern{
|
||||
{
|
||||
Pattern: "/subdir/*.dat",
|
||||
|
@ -3925,6 +3928,11 @@ func TestEventRule(t *testing.T) {
|
|||
err = os.WriteFile(uploadScriptPath, getUploadScriptContent(movedPath, "", 0), 0755)
|
||||
assert.NoError(t, err)
|
||||
|
||||
dataprovider.EnabledActionCommands = []string{uploadScriptPath}
|
||||
defer func() {
|
||||
dataprovider.EnabledActionCommands = nil
|
||||
}()
|
||||
|
||||
action1.Type = dataprovider.ActionTypeCommand
|
||||
action1.Options = dataprovider.BaseEventActionOptions{
|
||||
CmdConfig: dataprovider.EventActionCommandConfig{
|
||||
|
@ -4104,6 +4112,251 @@ func TestEventRule(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEventRuleStatues(t *testing.T) {
|
||||
smtpCfg := smtp.Config{
|
||||
Host: "127.0.0.1",
|
||||
Port: 2525,
|
||||
From: "notification@example.com",
|
||||
TemplatesPath: "templates",
|
||||
}
|
||||
err := smtpCfg.Initialize(configDir, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
a1 := dataprovider.BaseEventAction{
|
||||
Name: "a1",
|
||||
Type: dataprovider.ActionTypeEmail,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
EmailConfig: dataprovider.EventActionEmailConfig{
|
||||
Recipients: []string{"test6@example.com"},
|
||||
Subject: `New "{{Event}}" error`,
|
||||
Body: "{{ErrorString}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
r := dataprovider.EventRule{
|
||||
Name: "rule",
|
||||
Status: 1,
|
||||
Trigger: dataprovider.EventTriggerFsEvent,
|
||||
Conditions: dataprovider.EventConditions{
|
||||
FsEvents: []string{"upload"},
|
||||
Options: dataprovider.ConditionOptions{
|
||||
EventStatuses: []int{3},
|
||||
},
|
||||
},
|
||||
Actions: []dataprovider.EventAction{
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action1.Name,
|
||||
},
|
||||
Order: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
rule, resp, err := httpdtest.AddEventRule(r, http.StatusCreated)
|
||||
assert.NoError(t, err, string(resp))
|
||||
|
||||
u := getTestUser()
|
||||
u.UploadDataTransfer = 1
|
||||
u.DownloadDataTransfer = 1
|
||||
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
conn, client, err := getSftpClient(user)
|
||||
if assert.NoError(t, err) {
|
||||
defer conn.Close()
|
||||
defer client.Close()
|
||||
|
||||
testFileSize := int64(999999)
|
||||
err = writeSFTPFile(testFileName, testFileSize, client)
|
||||
assert.NoError(t, err)
|
||||
f, err := client.Open(testFileName)
|
||||
assert.NoError(t, err)
|
||||
contents := make([]byte, testFileSize)
|
||||
n, err := io.ReadFull(f, contents)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(testFileSize), n)
|
||||
assert.Len(t, contents, int(testFileSize))
|
||||
err = f.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
lastReceivedEmail.reset()
|
||||
assert.Eventually(t, func() bool {
|
||||
return lastReceivedEmail.get().From == ""
|
||||
}, 600*time.Millisecond, 500*time.Millisecond)
|
||||
|
||||
err = writeSFTPFile(testFileName, testFileSize, client)
|
||||
assert.Error(t, err)
|
||||
lastReceivedEmail.reset()
|
||||
assert.Eventually(t, func() bool {
|
||||
return lastReceivedEmail.get().From != ""
|
||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||
email := lastReceivedEmail.get()
|
||||
assert.Len(t, email.To, 1)
|
||||
assert.True(t, slices.Contains(email.To, "test6@example.com"))
|
||||
assert.Contains(t, email.Data, `Subject: New "upload" error`)
|
||||
assert.Contains(t, email.Data, common.ErrQuotaExceeded.Error())
|
||||
}
|
||||
|
||||
_, err = httpdtest.RemoveEventRule(rule, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
|
||||
smtpCfg = smtp.Config{}
|
||||
err = smtpCfg.Initialize(configDir, true)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEventRuleDisabledCommand(t *testing.T) {
|
||||
if runtime.GOOS == osWindows {
|
||||
t.Skip("this test is not available on Windows")
|
||||
}
|
||||
smtpCfg := smtp.Config{
|
||||
Host: "127.0.0.1",
|
||||
Port: 2525,
|
||||
From: "notification@example.com",
|
||||
TemplatesPath: "templates",
|
||||
}
|
||||
err := smtpCfg.Initialize(configDir, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
saveObjectScriptPath := filepath.Join(os.TempDir(), "provider.sh")
|
||||
outPath := filepath.Join(os.TempDir(), "provider_out.json")
|
||||
err = os.WriteFile(saveObjectScriptPath, getSaveProviderObjectScriptContent(outPath, 0), 0755)
|
||||
assert.NoError(t, err)
|
||||
|
||||
a1 := dataprovider.BaseEventAction{
|
||||
Name: "a1",
|
||||
Type: dataprovider.ActionTypeCommand,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
CmdConfig: dataprovider.EventActionCommandConfig{
|
||||
Cmd: saveObjectScriptPath,
|
||||
Timeout: 10,
|
||||
EnvVars: []dataprovider.KeyValue{
|
||||
{
|
||||
Key: "SFTPGO_OBJECT_DATA",
|
||||
Value: "{{ObjectData}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
a2 := dataprovider.BaseEventAction{
|
||||
Name: "a2",
|
||||
Type: dataprovider.ActionTypeEmail,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
EmailConfig: dataprovider.EventActionEmailConfig{
|
||||
Recipients: []string{"test3@example.com"},
|
||||
Subject: `New "{{Event}}" from "{{Name}}"`,
|
||||
Body: "Object name: {{ObjectName}} object type: {{ObjectType}} Data: {{ObjectData}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
a3 := dataprovider.BaseEventAction{
|
||||
Name: "a3",
|
||||
Type: dataprovider.ActionTypeEmail,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
EmailConfig: dataprovider.EventActionEmailConfig{
|
||||
Recipients: []string{"failure@example.com"},
|
||||
Subject: `Failed "{{Event}}" from "{{Name}}"`,
|
||||
Body: "Object name: {{ObjectName}} object type: {{ObjectType}}, IP: {{IP}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
_, _, err = httpdtest.AddEventAction(a1, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
// Enable the command to allow saving
|
||||
dataprovider.EnabledActionCommands = []string{a1.Options.CmdConfig.Cmd}
|
||||
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
r := dataprovider.EventRule{
|
||||
Name: "rule",
|
||||
Status: 1,
|
||||
Trigger: dataprovider.EventTriggerProviderEvent,
|
||||
Conditions: dataprovider.EventConditions{
|
||||
ProviderEvents: []string{"add"},
|
||||
Options: dataprovider.ConditionOptions{
|
||||
ProviderObjects: []string{"folder"},
|
||||
},
|
||||
},
|
||||
Actions: []dataprovider.EventAction{
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action1.Name,
|
||||
},
|
||||
Order: 1,
|
||||
Options: dataprovider.EventActionOptions{
|
||||
StopOnFailure: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action2.Name,
|
||||
},
|
||||
Order: 2,
|
||||
},
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action3.Name,
|
||||
},
|
||||
Order: 3,
|
||||
Options: dataprovider.EventActionOptions{
|
||||
IsFailureAction: true,
|
||||
StopOnFailure: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
rule, _, err := httpdtest.AddEventRule(r, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
// restrict command execution
|
||||
dataprovider.EnabledActionCommands = nil
|
||||
|
||||
lastReceivedEmail.reset()
|
||||
// create a folder to trigger the rule
|
||||
folder := vfs.BaseVirtualFolder{
|
||||
Name: "ftest failed command",
|
||||
MappedPath: filepath.Join(os.TempDir(), "p"),
|
||||
}
|
||||
folder, _, err = httpdtest.AddFolder(folder, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoFileExists(t, outPath)
|
||||
assert.Eventually(t, func() bool {
|
||||
return lastReceivedEmail.get().From != ""
|
||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||
email := lastReceivedEmail.get()
|
||||
assert.Len(t, email.To, 1)
|
||||
assert.True(t, slices.Contains(email.To, "failure@example.com"))
|
||||
assert.Contains(t, email.Data, `Subject: Failed "add" from "admin"`)
|
||||
assert.Contains(t, email.Data, fmt.Sprintf("Object name: %s object type: folder", folder.Name))
|
||||
lastReceivedEmail.reset()
|
||||
|
||||
_, err = httpdtest.RemoveFolder(folder, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = httpdtest.RemoveEventRule(rule, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEventRuleProviderEvents(t *testing.T) {
|
||||
if runtime.GOOS == osWindows {
|
||||
t.Skip("this test is not available on Windows")
|
||||
|
@ -4122,6 +4375,11 @@ func TestEventRuleProviderEvents(t *testing.T) {
|
|||
err = os.WriteFile(saveObjectScriptPath, getSaveProviderObjectScriptContent(outPath, 0), 0755)
|
||||
assert.NoError(t, err)
|
||||
|
||||
dataprovider.EnabledActionCommands = []string{saveObjectScriptPath}
|
||||
defer func() {
|
||||
dataprovider.EnabledActionCommands = nil
|
||||
}()
|
||||
|
||||
a1 := dataprovider.BaseEventAction{
|
||||
Name: "a1",
|
||||
Type: dataprovider.ActionTypeCommand,
|
||||
|
@ -4954,6 +5212,11 @@ func TestEventActionCommandEnvVars(t *testing.T) {
|
|||
envName := "MY_ENV"
|
||||
uploadScriptPath := filepath.Join(os.TempDir(), "upload.sh")
|
||||
|
||||
dataprovider.EnabledActionCommands = []string{uploadScriptPath}
|
||||
defer func() {
|
||||
dataprovider.EnabledActionCommands = nil
|
||||
}()
|
||||
|
||||
err := os.WriteFile(uploadScriptPath, getUploadScriptEnvContent(envName), 0755)
|
||||
assert.NoError(t, err)
|
||||
a1 := dataprovider.BaseEventAction{
|
||||
|
@ -5239,6 +5502,138 @@ func TestEventFsActionsGroupFilters(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEventProviderActionGroupFilters(t *testing.T) {
|
||||
smtpCfg := smtp.Config{
|
||||
Host: "127.0.0.1",
|
||||
Port: 2525,
|
||||
From: "notification@example.com",
|
||||
TemplatesPath: "templates",
|
||||
}
|
||||
err := smtpCfg.Initialize(configDir, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
a1 := dataprovider.BaseEventAction{
|
||||
Name: "a1",
|
||||
Type: dataprovider.ActionTypeEmail,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
EmailConfig: dataprovider.EventActionEmailConfig{
|
||||
Recipients: []string{"example@example.net"},
|
||||
Subject: `New "{{Event}}" from "{{Name}}"`,
|
||||
Body: "IP: {{IP}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
r1 := dataprovider.EventRule{
|
||||
Name: "rule1",
|
||||
Status: 1,
|
||||
Trigger: dataprovider.EventTriggerProviderEvent,
|
||||
Conditions: dataprovider.EventConditions{
|
||||
ProviderEvents: []string{"add", "update"},
|
||||
Options: dataprovider.ConditionOptions{
|
||||
GroupNames: []dataprovider.ConditionPattern{
|
||||
{
|
||||
Pattern: "group_*",
|
||||
},
|
||||
},
|
||||
ProviderObjects: []string{"user"},
|
||||
},
|
||||
},
|
||||
Actions: []dataprovider.EventAction{
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action1.Name,
|
||||
},
|
||||
Order: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
g1 := dataprovider.Group{
|
||||
BaseGroup: sdk.BaseGroup{
|
||||
Name: "agroup_1",
|
||||
},
|
||||
}
|
||||
group1, _, err := httpdtest.AddGroup(g1, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
g2 := dataprovider.Group{
|
||||
BaseGroup: sdk.BaseGroup{
|
||||
Name: "group_2",
|
||||
},
|
||||
}
|
||||
group2, _, err := httpdtest.AddGroup(g2, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
u := getTestUser()
|
||||
u.Groups = []sdk.GroupMapping{
|
||||
{
|
||||
Name: group2.Name,
|
||||
Type: sdk.GroupTypePrimary,
|
||||
},
|
||||
}
|
||||
|
||||
lastReceivedEmail.reset()
|
||||
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
assert.Eventually(t, func() bool {
|
||||
return lastReceivedEmail.get().From != ""
|
||||
}, 1500*time.Millisecond, 100*time.Millisecond)
|
||||
email := lastReceivedEmail.get()
|
||||
assert.Len(t, email.To, 1)
|
||||
|
||||
user.Groups = []sdk.GroupMapping{
|
||||
{
|
||||
Name: group1.Name,
|
||||
Type: sdk.GroupTypePrimary,
|
||||
},
|
||||
}
|
||||
|
||||
lastReceivedEmail.reset()
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
email = lastReceivedEmail.get()
|
||||
assert.Len(t, email.To, 0)
|
||||
|
||||
user.Groups = []sdk.GroupMapping{
|
||||
{
|
||||
Name: group2.Name,
|
||||
Type: sdk.GroupTypePrimary,
|
||||
},
|
||||
}
|
||||
|
||||
lastReceivedEmail.reset()
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Eventually(t, func() bool {
|
||||
return lastReceivedEmail.get().From != ""
|
||||
}, 1500*time.Millisecond, 100*time.Millisecond)
|
||||
email = lastReceivedEmail.get()
|
||||
assert.Len(t, email.To, 1)
|
||||
|
||||
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveGroup(group1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveGroup(group2, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
smtpCfg = smtp.Config{}
|
||||
err = smtpCfg.Initialize(configDir, true)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestBackupAsAttachment(t *testing.T) {
|
||||
smtpCfg := smtp.Config{
|
||||
Host: "127.0.0.1",
|
||||
|
@ -5297,7 +5692,7 @@ func TestBackupAsAttachment(t *testing.T) {
|
|||
|
||||
common.HandleCertificateEvent(common.EventParams{
|
||||
Name: "example.com",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Status: 1,
|
||||
Event: renewalEvent,
|
||||
})
|
||||
|
@ -5984,7 +6379,7 @@ func TestEventActionEmailAttachments(t *testing.T) {
|
|||
EmailConfig: dataprovider.EventActionEmailConfig{
|
||||
Recipients: []string{"test@example.com"},
|
||||
Subject: `"{{Event}}" from "{{Name}}"`,
|
||||
Body: "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}}",
|
||||
Body: "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}} {{EscapedVirtualPath}}",
|
||||
Attachments: []string{"/archive/{{VirtualPath}}.zip"},
|
||||
},
|
||||
},
|
||||
|
@ -6043,6 +6438,7 @@ func TestEventActionEmailAttachments(t *testing.T) {
|
|||
assert.Len(t, email.To, 1)
|
||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
||||
assert.Contains(t, email.Data, `Subject: "upload" from`)
|
||||
assert.Contains(t, email.Data, url.QueryEscape("/"+testFileName))
|
||||
assert.Contains(t, email.Data, "Content-Disposition: attachment")
|
||||
}
|
||||
}
|
||||
|
@ -6974,7 +7370,7 @@ func TestEventRuleCertificate(t *testing.T) {
|
|||
Recipients: []string{"test@example.com"},
|
||||
Subject: `"{{Event}} {{StatusString}}"`,
|
||||
ContentType: 0,
|
||||
Body: "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}}",
|
||||
Body: "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}} Date time: {{DateTime}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -7029,7 +7425,7 @@ func TestEventRuleCertificate(t *testing.T) {
|
|||
|
||||
common.HandleCertificateEvent(common.EventParams{
|
||||
Name: "example.com",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Status: 1,
|
||||
Event: renewalEvent,
|
||||
})
|
||||
|
@ -7044,9 +7440,10 @@ func TestEventRuleCertificate(t *testing.T) {
|
|||
assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
|
||||
|
||||
lastReceivedEmail.reset()
|
||||
dateTime := time.Now()
|
||||
params := common.EventParams{
|
||||
Name: "example.com",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: dateTime,
|
||||
Status: 2,
|
||||
Event: renewalEvent,
|
||||
}
|
||||
|
@ -7061,6 +7458,7 @@ func TestEventRuleCertificate(t *testing.T) {
|
|||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s KO"`, renewalEvent))
|
||||
assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
|
||||
assert.Contains(t, email.Data, dateTime.UTC().Format("2006-01-02T15:04:05.000"))
|
||||
assert.Contains(t, email.Data, errRenew.Error())
|
||||
|
||||
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
||||
|
@ -7074,7 +7472,7 @@ func TestEventRuleCertificate(t *testing.T) {
|
|||
// ignored no more certificate rules
|
||||
common.HandleCertificateEvent(common.EventParams{
|
||||
Name: "example.com",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Status: 1,
|
||||
Event: renewalEvent,
|
||||
})
|
||||
|
@ -7209,6 +7607,103 @@ func TestEventRuleIPBlocked(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEventRuleRotateLog(t *testing.T) {
|
||||
smtpCfg := smtp.Config{
|
||||
Host: "127.0.0.1",
|
||||
Port: 2525,
|
||||
From: "notification@example.com",
|
||||
TemplatesPath: "templates",
|
||||
}
|
||||
err := smtpCfg.Initialize(configDir, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
a1 := dataprovider.BaseEventAction{
|
||||
Name: "a1",
|
||||
Type: dataprovider.ActionTypeRotateLogs,
|
||||
}
|
||||
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
a2 := dataprovider.BaseEventAction{
|
||||
Name: "a2",
|
||||
Type: dataprovider.ActionTypeEmail,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
EmailConfig: dataprovider.EventActionEmailConfig{
|
||||
Recipients: []string{"success@example.net"},
|
||||
Subject: `OK`,
|
||||
Body: "OK action",
|
||||
},
|
||||
},
|
||||
}
|
||||
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
r1 := dataprovider.EventRule{
|
||||
Name: "rule1",
|
||||
Status: 1,
|
||||
Trigger: dataprovider.EventTriggerFsEvent,
|
||||
Conditions: dataprovider.EventConditions{
|
||||
FsEvents: []string{"mkdir"},
|
||||
Options: dataprovider.ConditionOptions{
|
||||
Names: []dataprovider.ConditionPattern{
|
||||
{
|
||||
Pattern: user.Username,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Actions: []dataprovider.EventAction{
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action1.Name,
|
||||
},
|
||||
Order: 1,
|
||||
},
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action2.Name,
|
||||
},
|
||||
Order: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
rule1, resp, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
||||
assert.NoError(t, err, string(resp))
|
||||
conn, client, err := getSftpClient(user)
|
||||
if assert.NoError(t, err) {
|
||||
defer conn.Close()
|
||||
defer client.Close()
|
||||
|
||||
lastReceivedEmail.reset()
|
||||
err := client.Mkdir("just a test dir")
|
||||
assert.NoError(t, err)
|
||||
// just check that the action is executed
|
||||
assert.Eventually(t, func() bool {
|
||||
return lastReceivedEmail.get().From != ""
|
||||
}, 1500*time.Millisecond, 100*time.Millisecond)
|
||||
email := lastReceivedEmail.get()
|
||||
assert.Len(t, email.To, 1)
|
||||
assert.Contains(t, email.To, "success@example.net")
|
||||
}
|
||||
|
||||
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
|
||||
smtpCfg = smtp.Config{}
|
||||
err = smtpCfg.Initialize(configDir, true)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEventRuleInactivityCheck(t *testing.T) {
|
||||
smtpCfg := smtp.Config{
|
||||
Host: "127.0.0.1",
|
||||
|
@ -8934,9 +9429,8 @@ func TestHTTPFs(t *testing.T) {
|
|||
|
||||
func TestProxyProtocol(t *testing.T) {
|
||||
resp, err := httpclient.Get(fmt.Sprintf("http://%v", httpProxyAddr))
|
||||
if assert.NoError(t, err) {
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
if !assert.Error(t, err) {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9218,7 +9712,7 @@ func printLatestLogs(maxNumberOfLines int) {
|
|||
return
|
||||
}
|
||||
for _, line := range lines {
|
||||
logger.DebugToConsole(line)
|
||||
logger.DebugToConsole("%s", line)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -352,6 +352,9 @@ func (t *BaseTransfer) checkUploadOutsideHomeDir(err error) int {
|
|||
if err == nil {
|
||||
return 0
|
||||
}
|
||||
if t.ErrTransfer == nil {
|
||||
t.ErrTransfer = err
|
||||
}
|
||||
if Config.TempPath == "" {
|
||||
return 0
|
||||
}
|
||||
|
@ -410,7 +413,8 @@ func (t *BaseTransfer) Close() error {
|
|||
var uploadFileSize int64
|
||||
if t.transferType == TransferDownload {
|
||||
logger.TransferLog(downloadLogSender, t.fsPath, elapsed, t.BytesSent.Load(), t.Connection.User.Username,
|
||||
t.Connection.ID, t.Connection.protocol, t.Connection.localAddr, t.Connection.remoteAddr, t.ftpMode)
|
||||
t.Connection.ID, t.Connection.protocol, t.Connection.localAddr, t.Connection.remoteAddr, t.ftpMode,
|
||||
t.ErrTransfer)
|
||||
ExecuteActionNotification(t.Connection, operationDownload, t.fsPath, t.requestPath, "", "", "", //nolint:errcheck
|
||||
t.BytesSent.Load(), t.ErrTransfer, elapsed, t.metadata)
|
||||
} else {
|
||||
|
@ -431,7 +435,8 @@ func (t *BaseTransfer) Close() error {
|
|||
t.updateQuota(numFiles, uploadFileSize)
|
||||
t.updateTimes()
|
||||
logger.TransferLog(uploadLogSender, t.fsPath, elapsed, t.BytesReceived.Load(), t.Connection.User.Username,
|
||||
t.Connection.ID, t.Connection.protocol, t.Connection.localAddr, t.Connection.remoteAddr, t.ftpMode)
|
||||
t.Connection.ID, t.Connection.protocol, t.Connection.localAddr, t.Connection.remoteAddr, t.ftpMode,
|
||||
t.ErrTransfer)
|
||||
}
|
||||
if t.ErrTransfer != nil {
|
||||
t.Connection.Log(logger.LevelError, "transfer error: %v, path: %q", t.ErrTransfer, t.fsPath)
|
||||
|
|
|
@ -144,6 +144,7 @@ var (
|
|||
ContentSecurityPolicy: "",
|
||||
PermissionsPolicy: "",
|
||||
CrossOriginOpenerPolicy: "",
|
||||
CacheControl: "",
|
||||
},
|
||||
Branding: httpd.Branding{},
|
||||
}
|
||||
|
@ -226,6 +227,10 @@ func Init() {
|
|||
ObservationTime: 30,
|
||||
EntriesSoftLimit: 100,
|
||||
EntriesHardLimit: 150,
|
||||
LoginDelay: common.LoginDelay{
|
||||
Success: 0,
|
||||
PasswordFailed: 1000,
|
||||
},
|
||||
},
|
||||
RateLimitersConfig: []common.RateLimiterConfig{defaultRateLimiter},
|
||||
Umask: "",
|
||||
|
@ -233,6 +238,9 @@ func Init() {
|
|||
Metadata: common.MetadataConfig{
|
||||
Read: 0,
|
||||
},
|
||||
EventManager: common.EventManagerConfig{
|
||||
EnabledCommands: []string{},
|
||||
},
|
||||
},
|
||||
ACME: acme.Configuration{
|
||||
Email: "",
|
||||
|
@ -257,6 +265,7 @@ func Init() {
|
|||
HostCertificates: []string{},
|
||||
HostKeyAlgorithms: []string{},
|
||||
KexAlgorithms: []string{},
|
||||
MinDHGroupExchangeKeySize: 2048,
|
||||
Ciphers: []string{},
|
||||
MACs: []string{},
|
||||
PublicKeyAlgorithms: []string{},
|
||||
|
@ -631,6 +640,15 @@ func getRedactedGlobalConf() globalConfig {
|
|||
binding.OIDC.ClientSecret = getRedactedPassword(binding.OIDC.ClientSecret)
|
||||
conf.HTTPDConfig.Bindings = append(conf.HTTPDConfig.Bindings, binding)
|
||||
}
|
||||
conf.PluginsConfig = nil
|
||||
for _, plugin := range globalConf.PluginsConfig {
|
||||
var args []string
|
||||
for _, arg := range plugin.Args {
|
||||
args = append(args, getRedactedPassword(arg))
|
||||
}
|
||||
plugin.Args = args
|
||||
conf.PluginsConfig = append(conf.PluginsConfig, plugin)
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
|
@ -787,6 +805,12 @@ func resetInvalidConfigs() {
|
|||
logger.WarnToConsole("Non-fatal configuration error: %v", warn)
|
||||
}
|
||||
}
|
||||
if globalConf.Common.RenameMode < 0 || globalConf.Common.RenameMode > 1 {
|
||||
warn := fmt.Sprintf("invalid rename mode %d, reset to 0", globalConf.Common.RenameMode)
|
||||
globalConf.Common.RenameMode = 0
|
||||
logger.Warn(logSender, "", "Non-fatal configuration error: %v", warn)
|
||||
logger.WarnToConsole("Non-fatal configuration error: %v", warn)
|
||||
}
|
||||
}
|
||||
|
||||
func loadBindingsFromEnv() {
|
||||
|
@ -1533,6 +1557,12 @@ func getHTTPDSecurityConfFromEnv(idx int) (httpd.SecurityConf, bool) { //nolint:
|
|||
isSet = true
|
||||
}
|
||||
|
||||
cacheControl, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__CACHE_CONTROL", idx))
|
||||
if ok {
|
||||
result.CacheControl = cacheControl
|
||||
isSet = true
|
||||
}
|
||||
|
||||
return result, isSet
|
||||
}
|
||||
|
||||
|
@ -1645,12 +1675,6 @@ func getHTTPDUIBrandingFromEnv(prefix string, branding httpd.UIBranding) (httpd.
|
|||
isSet = true
|
||||
}
|
||||
|
||||
loginImagePath, ok := os.LookupEnv(fmt.Sprintf("%s__LOGIN_IMAGE_PATH", prefix))
|
||||
if ok {
|
||||
branding.LoginImagePath = loginImagePath
|
||||
isSet = true
|
||||
}
|
||||
|
||||
disclaimerName, ok := os.LookupEnv(fmt.Sprintf("%s__DISCLAIMER_NAME", prefix))
|
||||
if ok {
|
||||
branding.DisclaimerName = disclaimerName
|
||||
|
@ -1995,9 +2019,12 @@ func setViperDefaults() {
|
|||
viper.SetDefault("common.defender.observation_time", globalConf.Common.DefenderConfig.ObservationTime)
|
||||
viper.SetDefault("common.defender.entries_soft_limit", globalConf.Common.DefenderConfig.EntriesSoftLimit)
|
||||
viper.SetDefault("common.defender.entries_hard_limit", globalConf.Common.DefenderConfig.EntriesHardLimit)
|
||||
viper.SetDefault("common.defender.login_delay.success", globalConf.Common.DefenderConfig.LoginDelay.Success)
|
||||
viper.SetDefault("common.defender.login_delay.password_failed", globalConf.Common.DefenderConfig.LoginDelay.PasswordFailed)
|
||||
viper.SetDefault("common.umask", globalConf.Common.Umask)
|
||||
viper.SetDefault("common.server_version", globalConf.Common.ServerVersion)
|
||||
viper.SetDefault("common.metadata.read", globalConf.Common.Metadata.Read)
|
||||
viper.SetDefault("common.event_manager.enabled_commands", globalConf.Common.EventManager.EnabledCommands)
|
||||
viper.SetDefault("acme.email", globalConf.ACME.Email)
|
||||
viper.SetDefault("acme.key_type", globalConf.ACME.KeyType)
|
||||
viper.SetDefault("acme.certs_path", globalConf.ACME.CertsPath)
|
||||
|
@ -2013,6 +2040,7 @@ func setViperDefaults() {
|
|||
viper.SetDefault("sftpd.host_certificates", globalConf.SFTPD.HostCertificates)
|
||||
viper.SetDefault("sftpd.host_key_algorithms", globalConf.SFTPD.HostKeyAlgorithms)
|
||||
viper.SetDefault("sftpd.kex_algorithms", globalConf.SFTPD.KexAlgorithms)
|
||||
viper.SetDefault("sftpd.min_dh_group_exchange_key_size", globalConf.SFTPD.MinDHGroupExchangeKeySize)
|
||||
viper.SetDefault("sftpd.ciphers", globalConf.SFTPD.Ciphers)
|
||||
viper.SetDefault("sftpd.macs", globalConf.SFTPD.MACs)
|
||||
viper.SetDefault("sftpd.public_key_algorithms", globalConf.SFTPD.PublicKeyAlgorithms)
|
||||
|
|
|
@ -243,6 +243,26 @@ func TestInvalidInstallationHint(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestInvalidRenameMode(t *testing.T) {
|
||||
reset()
|
||||
|
||||
confName := tempConfigName + ".json"
|
||||
configFilePath := filepath.Join(configDir, confName)
|
||||
commonConfig := config.GetCommonConfig()
|
||||
commonConfig.RenameMode = 10
|
||||
c := make(map[string]any)
|
||||
c["common"] = commonConfig
|
||||
jsonConf, err := json.Marshal(c)
|
||||
assert.NoError(t, err)
|
||||
err = os.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, config.GetCommonConfig().RenameMode)
|
||||
err = os.Remove(configFilePath)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDefenderProviderDriver(t *testing.T) {
|
||||
if config.GetProviderConf().Driver != dataprovider.SQLiteDataProviderName {
|
||||
t.Skip("this test is not supported with the current database provider")
|
||||
|
@ -1203,11 +1223,11 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CONTENT_SECURITY_POLICY", "script-src $NONCE")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__PERMISSIONS_POLICY", "fullscreen=(), geolocation=()")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CROSS_ORIGIN_OPENER_POLICY", "same-origin")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CACHE_CONTROL", "private")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__0__PATH", "path1")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__1__PATH", "path2")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_ADMIN__FAVICON_PATH", "favicon.ico")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_CLIENT__LOGO_PATH", "logo.png")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_ADMIN__LOGIN_IMAGE_PATH", "login_image.png")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_CLIENT__DISCLAIMER_NAME", "disclaimer")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_ADMIN__DISCLAIMER_PATH", "disclaimer.html")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_CLIENT__DEFAULT_CSS", "default.css")
|
||||
|
@ -1268,11 +1288,11 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CONTENT_SECURITY_POLICY")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__PERMISSIONS_POLICY")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CROSS_ORIGIN_OPENER_POLICY")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CACHE_CONTROL")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__0__PATH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__1__PATH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_ADMIN__FAVICON_PATH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_CLIENT__LOGO_PATH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_ADMIN__LOGIN_IMAGE_PATH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_CLIENT__DISCLAIMER_NAME")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_ADMIN__DISCLAIMER_PATH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_CLIENT__DEFAULT_CSS")
|
||||
|
@ -1378,9 +1398,9 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
require.Equal(t, "script-src $NONCE", bindings[2].Security.ContentSecurityPolicy)
|
||||
require.Equal(t, "fullscreen=(), geolocation=()", bindings[2].Security.PermissionsPolicy)
|
||||
require.Equal(t, "same-origin", bindings[2].Security.CrossOriginOpenerPolicy)
|
||||
require.Equal(t, "private", bindings[2].Security.CacheControl)
|
||||
require.Equal(t, "favicon.ico", bindings[2].Branding.WebAdmin.FaviconPath)
|
||||
require.Equal(t, "logo.png", bindings[2].Branding.WebClient.LogoPath)
|
||||
require.Equal(t, "login_image.png", bindings[2].Branding.WebAdmin.LoginImagePath)
|
||||
require.Equal(t, "disclaimer", bindings[2].Branding.WebClient.DisclaimerName)
|
||||
require.Equal(t, "disclaimer.html", bindings[2].Branding.WebAdmin.DisclaimerPath)
|
||||
require.Equal(t, []string{"default.css"}, bindings[2].Branding.WebClient.DefaultCSS)
|
||||
|
|
|
@ -141,7 +141,7 @@ func executeNotificationCommand(operation, executor, ip, objectType, objectName,
|
|||
|
||||
cmd := exec.CommandContext(ctx, config.Actions.Hook, args...)
|
||||
cmd.Env = append(env,
|
||||
fmt.Sprintf("SFTPGO_PROVIDER_ACTION=%vs", operation),
|
||||
fmt.Sprintf("SFTPGO_PROVIDER_ACTION=%s", operation),
|
||||
fmt.Sprintf("SFTPGO_PROVIDER_OBJECT_TYPE=%s", objectType),
|
||||
fmt.Sprintf("SFTPGO_PROVIDER_OBJECT_NAME=%s", objectName),
|
||||
fmt.Sprintf("SFTPGO_PROVIDER_USERNAME=%s", executor),
|
||||
|
|
|
@ -44,19 +44,12 @@ const (
|
|||
PermAdminViewConnections = "view_conns"
|
||||
PermAdminCloseConnections = "close_conns"
|
||||
PermAdminViewServerStatus = "view_status"
|
||||
PermAdminManageAdmins = "manage_admins"
|
||||
PermAdminManageGroups = "manage_groups"
|
||||
PermAdminManageFolders = "manage_folders"
|
||||
PermAdminManageAPIKeys = "manage_apikeys"
|
||||
PermAdminQuotaScans = "quota_scans"
|
||||
PermAdminManageSystem = "manage_system"
|
||||
PermAdminManageDefender = "manage_defender"
|
||||
PermAdminViewDefender = "view_defender"
|
||||
PermAdminRetentionChecks = "retention_checks"
|
||||
PermAdminViewEvents = "view_events"
|
||||
PermAdminManageEventRules = "manage_event_rules"
|
||||
PermAdminManageRoles = "manage_roles"
|
||||
PermAdminManageIPLists = "manage_ip_lists"
|
||||
PermAdminDisableMFA = "disable_mfa"
|
||||
)
|
||||
|
||||
|
@ -72,12 +65,9 @@ const (
|
|||
var (
|
||||
validAdminPerms = []string{PermAdminAny, PermAdminAddUsers, PermAdminChangeUsers, PermAdminDeleteUsers,
|
||||
PermAdminViewUsers, PermAdminManageFolders, PermAdminManageGroups, PermAdminViewConnections,
|
||||
PermAdminCloseConnections, PermAdminViewServerStatus, PermAdminManageAdmins, PermAdminManageRoles,
|
||||
PermAdminManageEventRules, PermAdminManageAPIKeys, PermAdminQuotaScans, PermAdminManageSystem,
|
||||
PermAdminManageDefender, PermAdminViewDefender, PermAdminManageIPLists, PermAdminRetentionChecks,
|
||||
PermAdminViewEvents, PermAdminDisableMFA}
|
||||
forbiddenPermsForRoleAdmins = []string{PermAdminAny, PermAdminManageAdmins, PermAdminManageSystem,
|
||||
PermAdminManageEventRules, PermAdminManageIPLists, PermAdminManageRoles}
|
||||
PermAdminCloseConnections, PermAdminViewServerStatus, PermAdminQuotaScans,
|
||||
PermAdminManageDefender, PermAdminViewDefender, PermAdminViewEvents, PermAdminDisableMFA}
|
||||
forbiddenPermsForRoleAdmins = []string{PermAdminAny}
|
||||
)
|
||||
|
||||
// AdminTOTPConfig defines the time-based one time password configuration
|
||||
|
@ -265,12 +255,7 @@ type Admin struct {
|
|||
// Last login as unix timestamp in milliseconds
|
||||
LastLogin int64 `json:"last_login"`
|
||||
// Role name. If set the admin can only administer users with the same role.
|
||||
// Role admins cannot have the following permissions:
|
||||
// - manage_admins
|
||||
// - manage_apikeys
|
||||
// - manage_system
|
||||
// - manage_event_rules
|
||||
// - manage_roles
|
||||
// Role admins cannot be super administrators
|
||||
Role string `json:"role,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -346,13 +331,9 @@ func (a *Admin) validatePermissions() error {
|
|||
}
|
||||
if a.Role != "" {
|
||||
if util.Contains(forbiddenPermsForRoleAdmins, perm) {
|
||||
deniedPerms := strings.Join(forbiddenPermsForRoleAdmins, ",")
|
||||
return util.NewI18nError(
|
||||
util.NewValidationError(fmt.Sprintf("a role admin cannot have the following permissions: %q", deniedPerms)),
|
||||
util.NewValidationError("a role admin cannot be a super admin"),
|
||||
util.I18nErrorRoleAdminPerms,
|
||||
util.I18nErrorArgs(map[string]any{
|
||||
"val": deniedPerms,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3135,7 +3135,7 @@ func (p *BoltProvider) migrateDatabase() error {
|
|||
providerLog(logger.LevelDebug, "bolt database is up to date, current version: %d", version)
|
||||
return ErrNoInitRequired
|
||||
case version < 28:
|
||||
err = fmt.Errorf("database schema version %d is too old, please see the upgrading docs", version)
|
||||
err = errSchemaVersionTooOld(version)
|
||||
providerLog(logger.LevelError, "%v", err)
|
||||
logger.ErrorToConsole("%v", err)
|
||||
return err
|
||||
|
|
|
@ -2568,9 +2568,8 @@ func createProvider(basePath string) error {
|
|||
return initializeBoltProvider(basePath)
|
||||
case MemoryDataProviderName:
|
||||
if err := initializeMemoryProvider(basePath); err != nil {
|
||||
msg := fmt.Sprintf("provider initialized but data loading failed: %v", err)
|
||||
logger.Warn(logSender, "", msg)
|
||||
logger.WarnToConsole(msg)
|
||||
logger.Warn(logSender, "", "provider initialized but data loading failed: %v", err)
|
||||
logger.WarnToConsole("provider initialized but data loading failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
|
@ -2870,11 +2869,18 @@ func validatePublicKeys(user *User) error {
|
|||
util.I18nErrorPubKeyInvalid,
|
||||
)
|
||||
}
|
||||
if out.Type() == ssh.InsecureKeyAlgoDSA {
|
||||
providerLog(logger.LevelError, "dsa public key not accepted, position: %d", idx)
|
||||
return util.NewI18nError(
|
||||
util.NewValidationError(fmt.Sprintf("DSA key format is insecure and it is not allowed for key at position %d", idx)),
|
||||
util.I18nErrorKeyInsecure,
|
||||
)
|
||||
}
|
||||
if k, ok := out.(ssh.CryptoPublicKey); ok {
|
||||
cryptoKey := k.CryptoPublicKey()
|
||||
if rsaKey, ok := cryptoKey.(*rsa.PublicKey); ok {
|
||||
if size := rsaKey.N.BitLen(); size < 2048 {
|
||||
providerLog(logger.LevelError, "rsa key with size %d not accepted, minimum 2048", size)
|
||||
providerLog(logger.LevelError, "rsa key with size %d at position %d not accepted, minimum 2048", size, idx)
|
||||
return util.NewI18nError(
|
||||
util.NewValidationError(fmt.Sprintf("invalid size %d for rsa key at position %d, minimum 2048",
|
||||
size, idx)),
|
||||
|
@ -4442,7 +4448,7 @@ func doExternalAuth(username, password string, pubKey []byte, keyboardInteractiv
|
|||
// preserve TOTP config and recovery codes
|
||||
user.Filters.TOTPConfig = u.Filters.TOTPConfig
|
||||
user.Filters.RecoveryCodes = u.Filters.RecoveryCodes
|
||||
err = provider.updateUser(&user)
|
||||
user, err = updateUserAfterExternalAuth(&user)
|
||||
if err == nil {
|
||||
if protocol != protocolWebDAV {
|
||||
webDAVUsersCache.swap(&user, password)
|
||||
|
@ -4514,7 +4520,7 @@ func doPluginAuth(username, password string, pubKey []byte, ip, protocol string,
|
|||
// preserve TOTP config and recovery codes
|
||||
user.Filters.TOTPConfig = u.Filters.TOTPConfig
|
||||
user.Filters.RecoveryCodes = u.Filters.RecoveryCodes
|
||||
err = provider.updateUser(&user)
|
||||
user, err = updateUserAfterExternalAuth(&user)
|
||||
if err == nil {
|
||||
if protocol != protocolWebDAV {
|
||||
webDAVUsersCache.swap(&user, password)
|
||||
|
@ -4530,6 +4536,13 @@ func doPluginAuth(username, password string, pubKey []byte, ip, protocol string,
|
|||
return provider.userExists(user.Username, "")
|
||||
}
|
||||
|
||||
func updateUserAfterExternalAuth(user *User) (User, error) {
|
||||
if err := provider.updateUser(user); err != nil {
|
||||
return *user, err
|
||||
}
|
||||
return provider.userExists(user.Username, "")
|
||||
}
|
||||
|
||||
func getUserForHook(username string, oidcTokenFields *map[string]any) (User, User, error) {
|
||||
u, err := provider.userExists(username, "")
|
||||
if err != nil {
|
||||
|
@ -4618,6 +4631,10 @@ func checkReservedUsernames(username string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func errSchemaVersionTooOld(version int) error {
|
||||
return fmt.Errorf("database schema version %d is too old, please see the upgrading docs: https://docs.sftpgo.com/latest/data-provider/#upgrading", version)
|
||||
}
|
||||
|
||||
func providerLog(level logger.LogLevel, format string, v ...any) {
|
||||
logger.Log(level, logSender, "", format, v...)
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"net/http"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -49,13 +50,17 @@ const (
|
|||
ActionTypeUserExpirationCheck
|
||||
ActionTypeIDPAccountCheck
|
||||
ActionTypeUserInactivityCheck
|
||||
ActionTypeRotateLogs
|
||||
)
|
||||
|
||||
var (
|
||||
supportedEventActions = []int{ActionTypeHTTP, ActionTypeCommand, ActionTypeEmail, ActionTypeFilesystem,
|
||||
ActionTypeBackup, ActionTypeUserQuotaReset, ActionTypeFolderQuotaReset, ActionTypeTransferQuotaReset,
|
||||
ActionTypeDataRetentionCheck, ActionTypePasswordExpirationCheck,
|
||||
ActionTypeUserExpirationCheck, ActionTypeUserInactivityCheck, ActionTypeIDPAccountCheck}
|
||||
ActionTypeDataRetentionCheck, ActionTypePasswordExpirationCheck, ActionTypeUserExpirationCheck,
|
||||
ActionTypeUserInactivityCheck, ActionTypeIDPAccountCheck, ActionTypeRotateLogs}
|
||||
// EnabledActionCommands defines the system commands that can be executed via EventManager,
|
||||
// an empty list means that no command is allowed to be executed.
|
||||
EnabledActionCommands []string
|
||||
)
|
||||
|
||||
func isActionTypeValid(action int) bool {
|
||||
|
@ -88,6 +93,8 @@ func getActionTypeAsString(action int) string {
|
|||
return util.I18nActionTypeUserInactivityCheck
|
||||
case ActionTypeIDPAccountCheck:
|
||||
return util.I18nActionTypeIDPCheck
|
||||
case ActionTypeRotateLogs:
|
||||
return util.I18nActionTypeRotateLogs
|
||||
default:
|
||||
return util.I18nActionTypeCommand
|
||||
}
|
||||
|
@ -398,11 +405,11 @@ func (c *EventActionHTTPConfig) GetContext() (context.Context, context.CancelFun
|
|||
|
||||
// HasObjectData returns true if the {{ObjectData}} placeholder is defined
|
||||
func (c *EventActionHTTPConfig) HasObjectData() bool {
|
||||
if strings.Contains(c.Body, "{{ObjectData}}") {
|
||||
if strings.Contains(c.Body, "{{ObjectData}}") || strings.Contains(c.Body, "{{ObjectDataString}}") {
|
||||
return true
|
||||
}
|
||||
for _, part := range c.Parts {
|
||||
if strings.Contains(part.Body, "{{ObjectData}}") {
|
||||
if strings.Contains(part.Body, "{{ObjectData}}") || strings.Contains(part.Body, "{{ObjectDataString}}") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -446,6 +453,11 @@ func (c *EventActionHTTPConfig) GetHTTPClient() *http.Client {
|
|||
return client
|
||||
}
|
||||
|
||||
// IsActionCommandAllowed returns true if the specified command is allowed
|
||||
func IsActionCommandAllowed(cmd string) bool {
|
||||
return slices.Contains(EnabledActionCommands, cmd)
|
||||
}
|
||||
|
||||
// EventActionCommandConfig defines the configuration for a command event target
|
||||
type EventActionCommandConfig struct {
|
||||
Cmd string `json:"cmd,omitempty"`
|
||||
|
@ -458,6 +470,9 @@ func (c *EventActionCommandConfig) validate() error {
|
|||
if c.Cmd == "" {
|
||||
return util.NewI18nError(util.NewValidationError("command is required"), util.I18nErrorCommandRequired)
|
||||
}
|
||||
if !IsActionCommandAllowed(c.Cmd) {
|
||||
return util.NewValidationError(fmt.Sprintf("command %q is not allowed", c.Cmd))
|
||||
}
|
||||
if !filepath.IsAbs(c.Cmd) {
|
||||
return util.NewI18nError(
|
||||
util.NewValidationError("invalid command, it must be an absolute path"),
|
||||
|
@ -1320,6 +1335,7 @@ type ConditionOptions struct {
|
|||
ProviderObjects []string `json:"provider_objects,omitempty"`
|
||||
MinFileSize int64 `json:"min_size,omitempty"`
|
||||
MaxFileSize int64 `json:"max_size,omitempty"`
|
||||
EventStatuses []int `json:"event_statuses,omitempty"`
|
||||
// allow to execute scheduled tasks concurrently from multiple instances
|
||||
ConcurrentExecution bool `json:"concurrent_execution,omitempty"`
|
||||
}
|
||||
|
@ -1329,6 +1345,8 @@ func (f *ConditionOptions) getACopy() ConditionOptions {
|
|||
copy(protocols, f.Protocols)
|
||||
providerObjects := make([]string, len(f.ProviderObjects))
|
||||
copy(providerObjects, f.ProviderObjects)
|
||||
statuses := make([]int, len(f.EventStatuses))
|
||||
copy(statuses, f.EventStatuses)
|
||||
|
||||
return ConditionOptions{
|
||||
Names: cloneConditionPatterns(f.Names),
|
||||
|
@ -1339,10 +1357,20 @@ func (f *ConditionOptions) getACopy() ConditionOptions {
|
|||
ProviderObjects: providerObjects,
|
||||
MinFileSize: f.MinFileSize,
|
||||
MaxFileSize: f.MaxFileSize,
|
||||
EventStatuses: statuses,
|
||||
ConcurrentExecution: f.ConcurrentExecution,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ConditionOptions) validateStatuses() error {
|
||||
for _, status := range f.EventStatuses {
|
||||
if status < 0 || status > 3 {
|
||||
return util.NewValidationError(fmt.Sprintf("invalid event_status %d", status))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *ConditionOptions) validate() error {
|
||||
if err := validateConditionPatterns(f.Names); err != nil {
|
||||
return err
|
||||
|
@ -1373,6 +1401,9 @@ func (f *ConditionOptions) validate() error {
|
|||
util.ByteCountSI(f.MaxFileSize), util.ByteCountSI(f.MinFileSize)))
|
||||
}
|
||||
}
|
||||
if err := f.validateStatuses(); err != nil {
|
||||
return err
|
||||
}
|
||||
if config.IsShared == 0 {
|
||||
f.ConcurrentExecution = false
|
||||
}
|
||||
|
@ -1472,9 +1503,9 @@ func (c *EventConditions) validate(trigger int) error {
|
|||
case EventTriggerProviderEvent:
|
||||
c.FsEvents = nil
|
||||
c.Schedules = nil
|
||||
c.Options.GroupNames = nil
|
||||
c.Options.FsPaths = nil
|
||||
c.Options.Protocols = nil
|
||||
c.Options.EventStatuses = nil
|
||||
c.Options.MinFileSize = 0
|
||||
c.Options.MaxFileSize = 0
|
||||
c.IDPLoginEvent = 0
|
||||
|
@ -1494,6 +1525,7 @@ func (c *EventConditions) validate(trigger int) error {
|
|||
c.ProviderEvents = nil
|
||||
c.Options.FsPaths = nil
|
||||
c.Options.Protocols = nil
|
||||
c.Options.EventStatuses = nil
|
||||
c.Options.MinFileSize = 0
|
||||
c.Options.MaxFileSize = 0
|
||||
c.Options.ProviderObjects = nil
|
||||
|
@ -1509,6 +1541,7 @@ func (c *EventConditions) validate(trigger int) error {
|
|||
c.Options.RoleNames = nil
|
||||
c.Options.FsPaths = nil
|
||||
c.Options.Protocols = nil
|
||||
c.Options.EventStatuses = nil
|
||||
c.Options.MinFileSize = 0
|
||||
c.Options.MaxFileSize = 0
|
||||
c.Schedules = nil
|
||||
|
@ -1518,6 +1551,7 @@ func (c *EventConditions) validate(trigger int) error {
|
|||
c.ProviderEvents = nil
|
||||
c.Options.FsPaths = nil
|
||||
c.Options.Protocols = nil
|
||||
c.Options.EventStatuses = nil
|
||||
c.Options.MinFileSize = 0
|
||||
c.Options.MaxFileSize = 0
|
||||
c.Options.ProviderObjects = nil
|
||||
|
@ -1531,6 +1565,7 @@ func (c *EventConditions) validate(trigger int) error {
|
|||
c.Options.RoleNames = nil
|
||||
c.Options.FsPaths = nil
|
||||
c.Options.Protocols = nil
|
||||
c.Options.EventStatuses = nil
|
||||
c.Options.MinFileSize = 0
|
||||
c.Options.MaxFileSize = 0
|
||||
c.Schedules = nil
|
||||
|
@ -1544,6 +1579,7 @@ func (c *EventConditions) validate(trigger int) error {
|
|||
c.Options.RoleNames = nil
|
||||
c.Options.FsPaths = nil
|
||||
c.Options.Protocols = nil
|
||||
c.Options.EventStatuses = nil
|
||||
c.Options.MinFileSize = 0
|
||||
c.Options.MaxFileSize = 0
|
||||
c.Schedules = nil
|
||||
|
|
|
@ -794,7 +794,7 @@ func (p *MySQLProvider) migrateDatabase() error {
|
|||
providerLog(logger.LevelDebug, "sql database is up to date, current version: %d", version)
|
||||
return ErrNoInitRequired
|
||||
case version < 28:
|
||||
err = fmt.Errorf("database schema version %d is too old, please see the upgrading docs", version)
|
||||
err = errSchemaVersionTooOld(version)
|
||||
providerLog(logger.LevelError, "%v", err)
|
||||
logger.ErrorToConsole("%v", err)
|
||||
return err
|
||||
|
|
|
@ -17,10 +17,11 @@ package dataprovider
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
@ -99,7 +100,7 @@ func (n *NodeData) validate() error {
|
|||
if n.Proto != NodeProtoHTTP && n.Proto != NodeProtoHTTPS {
|
||||
return util.NewValidationError(fmt.Sprintf("invalid node proto: %s", n.Proto))
|
||||
}
|
||||
n.Key = kms.NewPlainSecret(util.BytesToString(util.GenerateRandomBytes(32)))
|
||||
n.Key = kms.NewPlainSecret(util.GenerateOpaqueString())
|
||||
n.Key.SetAdditionalData(n.Host)
|
||||
if err := n.Key.Encrypt(); err != nil {
|
||||
return fmt.Errorf("unable to encrypt node key: %w", err)
|
||||
|
@ -108,12 +109,12 @@ func (n *NodeData) validate() error {
|
|||
}
|
||||
|
||||
func (n *NodeData) getNodeName() string {
|
||||
h := fnv.New64a()
|
||||
h := sha256.New()
|
||||
var b bytes.Buffer
|
||||
|
||||
b.WriteString(fmt.Sprintf("%s:%d", n.Host, n.Port))
|
||||
h.Write(b.Bytes())
|
||||
return strconv.FormatUint(h.Sum64(), 10)
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// Node defines a cluster node
|
||||
|
|
|
@ -819,7 +819,7 @@ func (p *PGSQLProvider) migrateDatabase() error { //nolint:dupl
|
|||
providerLog(logger.LevelDebug, "sql database is up to date, current version: %d", version)
|
||||
return ErrNoInitRequired
|
||||
case version < 28:
|
||||
err = fmt.Errorf("database schema version %d is too old, please see the upgrading docs", version)
|
||||
err = errSchemaVersionTooOld(version)
|
||||
providerLog(logger.LevelError, "%v", err)
|
||||
logger.ErrorToConsole("%v", err)
|
||||
return err
|
||||
|
|
|
@ -89,6 +89,11 @@ func (s *Share) GetAllowedFromAsString() string {
|
|||
return strings.Join(s.AllowFrom, ",")
|
||||
}
|
||||
|
||||
// IsPasswordHashed returns true if the password is hashed
|
||||
func (s *Share) IsPasswordHashed() bool {
|
||||
return util.IsStringPrefixInSlice(s.Password, hashPwdPrefixes)
|
||||
}
|
||||
|
||||
func (s *Share) getACopy() Share {
|
||||
allowFrom := make([]string, len(s.AllowFrom))
|
||||
copy(allowFrom, s.AllowFrom)
|
||||
|
|
|
@ -710,7 +710,7 @@ func (p *SQLiteProvider) migrateDatabase() error { //nolint:dupl
|
|||
providerLog(logger.LevelDebug, "sql database is up to date, current version: %d", version)
|
||||
return ErrNoInitRequired
|
||||
case version < 28:
|
||||
err = fmt.Errorf("database schema version %d is too old, please see the upgrading docs", version)
|
||||
err = errSchemaVersionTooOld(version)
|
||||
providerLog(logger.LevelError, "%v", err)
|
||||
logger.ErrorToConsole("%v", err)
|
||||
return err
|
||||
|
|
|
@ -2544,14 +2544,14 @@ func TestRename(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = client.MakeDir(path.Join(otherDir, testDir))
|
||||
assert.NoError(t, err)
|
||||
code, response, err := client.SendCommand(fmt.Sprintf("SITE CHMOD 0001 %v", otherDir))
|
||||
code, response, err := client.SendCommand("SITE CHMOD 0001 %v", otherDir)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusCommandOK, code)
|
||||
assert.Equal(t, "SITE CHMOD command successful", response)
|
||||
err = client.Rename(testDir, path.Join(otherDir, testDir))
|
||||
assert.Error(t, err)
|
||||
|
||||
code, response, err = client.SendCommand(fmt.Sprintf("SITE CHMOD 755 %v", otherDir))
|
||||
code, response, err = client.SendCommand("SITE CHMOD 755 %v", otherDir)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusCommandOK, code)
|
||||
assert.Equal(t, "SITE CHMOD command successful", response)
|
||||
|
@ -2611,7 +2611,7 @@ func TestSymlink(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
|
||||
assert.NoError(t, err)
|
||||
code, _, err := client.SendCommand(fmt.Sprintf("SITE SYMLINK %v %v", testFileName, testFileName+".link"))
|
||||
code, _, err := client.SendCommand("SITE SYMLINK %v %v", testFileName, testFileName+".link")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusCommandOK, code)
|
||||
|
||||
|
@ -2622,15 +2622,15 @@ func TestSymlink(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = client.MakeDir(path.Join(otherDir, testDir))
|
||||
assert.NoError(t, err)
|
||||
code, response, err := client.SendCommand(fmt.Sprintf("SITE CHMOD 0001 %v", otherDir))
|
||||
code, response, err := client.SendCommand("SITE CHMOD 0001 %v", otherDir)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusCommandOK, code)
|
||||
assert.Equal(t, "SITE CHMOD command successful", response)
|
||||
code, _, err = client.SendCommand(fmt.Sprintf("SITE SYMLINK %v %v", testDir, path.Join(otherDir, testDir)))
|
||||
code, _, err = client.SendCommand("SITE SYMLINK %v %v", testDir, path.Join(otherDir, testDir))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusFileUnavailable, code)
|
||||
|
||||
code, response, err = client.SendCommand(fmt.Sprintf("SITE CHMOD 755 %v", otherDir))
|
||||
code, response, err = client.SendCommand("SITE CHMOD 755 %v", otherDir)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusCommandOK, code)
|
||||
assert.Equal(t, "SITE CHMOD command successful", response)
|
||||
|
@ -3051,7 +3051,7 @@ func TestChtimes(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
mtime := time.Now().Format("20060102150405")
|
||||
code, response, err := client.SendCommand(fmt.Sprintf("MFMT %v %v", mtime, testFileName))
|
||||
code, response, err := client.SendCommand("MFMT %v %v", mtime, testFileName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusFile, code)
|
||||
assert.Equal(t, fmt.Sprintf("Modify=%v; %v", mtime, testFileName), response)
|
||||
|
@ -3128,7 +3128,7 @@ func TestSTAT(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = ftpUploadFile(testFilePath, path.Join(testDir, testFileName+"_1"), testFileSize, client, 0)
|
||||
assert.NoError(t, err)
|
||||
code, response, err := client.SendCommand(fmt.Sprintf("STAT %s", testDir))
|
||||
code, response, err := client.SendCommand("STAT %s", testDir)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusDirectory, code)
|
||||
assert.Contains(t, response, fmt.Sprintf("STAT %s", testDir))
|
||||
|
@ -3162,7 +3162,7 @@ func TestChown(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
|
||||
assert.NoError(t, err)
|
||||
code, response, err := client.SendCommand(fmt.Sprintf("SITE CHOWN 1000:1000 %v", testFileName))
|
||||
code, response, err := client.SendCommand("SITE CHOWN 1000:1000 %v", testFileName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusFileUnavailable, code)
|
||||
assert.Equal(t, "Couldn't chown: operation unsupported", response)
|
||||
|
@ -3200,7 +3200,7 @@ func TestChmod(t *testing.T) {
|
|||
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
code, response, err := client.SendCommand(fmt.Sprintf("SITE CHMOD 600 %v", testFileName))
|
||||
code, response, err := client.SendCommand("SITE CHMOD 600 %v", testFileName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusCommandOK, code)
|
||||
assert.Equal(t, "SITE CHMOD command successful", response)
|
||||
|
@ -3363,12 +3363,12 @@ func TestHASH(t *testing.T) {
|
|||
err = f.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
code, response, err := client.SendCommand(fmt.Sprintf("XSHA256 %v", testFileName))
|
||||
code, response, err := client.SendCommand("XSHA256 %v", testFileName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusRequestedFileActionOK, code)
|
||||
assert.Contains(t, response, hash)
|
||||
|
||||
code, response, err = client.SendCommand(fmt.Sprintf("HASH %v", testFileName))
|
||||
code, response, err = client.SendCommand("HASH %v", testFileName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusFile, code)
|
||||
assert.Contains(t, response, hash)
|
||||
|
@ -3424,7 +3424,7 @@ func TestCombine(t *testing.T) {
|
|||
err = ftpUploadFile(testFilePath, testFileName+".2", testFileSize, client, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
code, response, err := client.SendCommand(fmt.Sprintf("COMB %v %v %v", testFileName, testFileName+".1", testFileName+".2"))
|
||||
code, response, err := client.SendCommand("COMB %v %v %v", testFileName, testFileName+".1", testFileName+".2")
|
||||
assert.NoError(t, err)
|
||||
if user.Username == defaultUsername {
|
||||
assert.Equal(t, ftp.StatusRequestedFileActionOK, code)
|
||||
|
|
|
@ -885,7 +885,6 @@ func TestTransferErrors(t *testing.T) {
|
|||
fs := newMockOsFs(nil, nil, false, connID, user.GetHomeDir())
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, "", "", user),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
baseTransfer := common.NewBaseTransfer(file, connection.BaseConnection, nil, file.Name(), file.Name(), testfile,
|
||||
common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{})
|
||||
|
|
|
@ -420,9 +420,9 @@ func updateLoginMetrics(user *dataprovider.User, ip, loginMethod string, err err
|
|||
metric.AddLoginAttempt(loginMethod)
|
||||
if err == nil {
|
||||
plugin.Handler.NotifyLogEvent(notifier.LogEventTypeLoginOK, common.ProtocolFTP, user.Username, ip, "", nil)
|
||||
common.DelayLogin(nil)
|
||||
} else if err != common.ErrInternalFailure {
|
||||
logger.ConnectionFailedLog(user.Username, ip, loginMethod,
|
||||
common.ProtocolFTP, err.Error())
|
||||
logger.ConnectionFailedLog(user.Username, ip, loginMethod, common.ProtocolFTP, err.Error())
|
||||
event := common.HostEventLoginFailed
|
||||
logEv := notifier.LogEventTypeLoginFailed
|
||||
if errors.Is(err, util.ErrNotFound) {
|
||||
|
@ -431,6 +431,9 @@ func updateLoginMetrics(user *dataprovider.User, ip, loginMethod string, err err
|
|||
}
|
||||
common.AddDefenderEvent(ip, common.ProtocolFTP, event)
|
||||
plugin.Handler.NotifyLogEvent(logEv, common.ProtocolFTP, user.Username, ip, "", err)
|
||||
if loginMethod != dataprovider.LoginMethodTLSCertificate {
|
||||
common.DelayLogin(err)
|
||||
}
|
||||
}
|
||||
metric.AddLoginResult(loginMethod, err)
|
||||
dataprovider.ExecutePostLoginHook(user, loginMethod, ip, common.ProtocolFTP, err)
|
||||
|
|
|
@ -149,8 +149,8 @@ func updateAdmin(w http.ResponseWriter, r *http.Request) {
|
|||
http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if claims.isCriticalPermRemoved(updatedAdmin.Permissions) {
|
||||
sendAPIResponse(w, r, errors.New("you cannot remove these permissions to yourself"), "", http.StatusBadRequest)
|
||||
if !util.SlicesEqual(admin.Permissions, updatedAdmin.Permissions) {
|
||||
sendAPIResponse(w, r, errors.New("you cannot change your permissions"), "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if updatedAdmin.Status == 0 {
|
||||
|
@ -297,6 +297,7 @@ func changeAdminPassword(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
}
|
||||
invalidateToken(r)
|
||||
sendAPIResponse(w, r, err, "Password updated", http.StatusOK)
|
||||
}
|
||||
|
||||
|
|
|
@ -531,6 +531,7 @@ func changeUserPassword(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
}
|
||||
invalidateToken(r)
|
||||
sendAPIResponse(w, r, err, "Password updated", http.StatusOK)
|
||||
}
|
||||
|
||||
|
|
|
@ -512,6 +512,7 @@ func (s *httpdServer) checkPublicShare(w http.ResponseWriter, r *http.Request, v
|
|||
return share, nil, dataprovider.ErrInvalidCredentials
|
||||
}
|
||||
}
|
||||
common.DelayLogin(nil)
|
||||
}
|
||||
user, err := getUserForShare(share)
|
||||
if err != nil {
|
||||
|
@ -561,6 +562,7 @@ func validateBrowsableShare(share dataprovider.Share, connection *Connection) er
|
|||
basePath := share.Paths[0]
|
||||
info, err := connection.Stat(basePath, 0)
|
||||
if err != nil {
|
||||
connection.CloseFS() //nolint:errcheck
|
||||
return util.NewI18nError(
|
||||
fmt.Errorf("unable to check the share directory: %w", err),
|
||||
util.I18nErrorShareInvalidPath,
|
||||
|
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/rs/xid"
|
||||
"github.com/sftpgo/sdk/plugin/notifier"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/common"
|
||||
|
@ -686,6 +687,7 @@ func handleDefenderEventLoginFailed(ipAddr string, err error) error {
|
|||
err = dataprovider.ErrInvalidCredentials
|
||||
}
|
||||
common.AddDefenderEvent(ipAddr, common.ProtocolHTTP, event)
|
||||
common.DelayLogin(err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -700,6 +702,7 @@ func updateLoginMetrics(user *dataprovider.User, loginMethod, ip string, err err
|
|||
}
|
||||
if err == nil {
|
||||
plugin.Handler.NotifyLogEvent(notifier.LogEventTypeLoginOK, protocol, user.Username, ip, "", nil)
|
||||
common.DelayLogin(nil)
|
||||
} else if err != common.ErrInternalFailure && err != common.ErrNoCredentials {
|
||||
logger.ConnectionFailedLog(user.Username, ip, loginMethod, protocol, err.Error())
|
||||
err = handleDefenderEventLoginFailed(ip, err)
|
||||
|
@ -746,6 +749,31 @@ func checkHTTPClientUser(user *dataprovider.User, r *http.Request, connectionID
|
|||
return nil
|
||||
}
|
||||
|
||||
func getActiveAdmin(username, ipAddr string) (dataprovider.Admin, error) {
|
||||
admin, err := dataprovider.AdminExists(username)
|
||||
if err != nil {
|
||||
return admin, err
|
||||
}
|
||||
if err := admin.CanLogin(ipAddr); err != nil {
|
||||
return admin, util.NewRecordNotFoundError(fmt.Sprintf("admin %q cannot login: %v", username, err))
|
||||
}
|
||||
return admin, nil
|
||||
}
|
||||
|
||||
func getActiveUser(username string, r *http.Request) (dataprovider.User, error) {
|
||||
user, err := dataprovider.GetUserWithGroupSettings(username, "")
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
if err := user.CheckLoginConditions(); err != nil {
|
||||
return user, util.NewRecordNotFoundError(fmt.Sprintf("user %q cannot login: %v", username, err))
|
||||
}
|
||||
if err := checkHTTPClientUser(&user, r, xid.New().String(), false); err != nil {
|
||||
return user, util.NewRecordNotFoundError(fmt.Sprintf("user %q cannot login: %v", username, err))
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func handleForgotPassword(r *http.Request, username string, isAdmin bool) error {
|
||||
var email, subject string
|
||||
var err error
|
||||
|
@ -756,11 +784,11 @@ func handleForgotPassword(r *http.Request, username string, isAdmin bool) error
|
|||
return util.NewI18nError(util.NewValidationError("username is mandatory"), util.I18nErrorUsernameRequired)
|
||||
}
|
||||
if isAdmin {
|
||||
admin, err = dataprovider.AdminExists(username)
|
||||
admin, err = getActiveAdmin(username, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
email = admin.Email
|
||||
subject = fmt.Sprintf("Email Verification Code for admin %q", username)
|
||||
} else {
|
||||
user, err = dataprovider.GetUserWithGroupSettings(username, "")
|
||||
user, err = getActiveUser(username, r)
|
||||
email = user.Email
|
||||
subject = fmt.Sprintf("Email Verification Code for user %q", username)
|
||||
if err == nil {
|
||||
|
@ -775,8 +803,9 @@ func handleForgotPassword(r *http.Request, username string, isAdmin bool) error
|
|||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotFound) {
|
||||
handleDefenderEventLoginFailed(util.GetIPFromRemoteAddress(r.RemoteAddr), err) //nolint:errcheck
|
||||
logger.Debug(logSender, middleware.GetReqID(r.Context()), "username %q does not exists, reset password request silently ignored, is admin? %v",
|
||||
username, isAdmin)
|
||||
logger.Debug(logSender, middleware.GetReqID(r.Context()),
|
||||
"username %q does not exists or cannot login, reset password request silently ignored, is admin? %t, err: %v",
|
||||
username, isAdmin, err)
|
||||
return nil
|
||||
}
|
||||
return util.NewI18nError(util.NewGenericError("Error retrieving your account, please try again later"), util.I18nErrorGetUser)
|
||||
|
@ -836,7 +865,7 @@ func handleResetPassword(r *http.Request, code, newPassword, confirmPassword str
|
|||
return &admin, &user, util.NewValidationError("invalid confirmation code")
|
||||
}
|
||||
if isAdmin {
|
||||
admin, err = dataprovider.AdminExists(resetCode.Username)
|
||||
admin, err = getActiveAdmin(resetCode.Username, ipAddr)
|
||||
if err != nil {
|
||||
return &admin, &user, util.NewValidationError("unable to associate the confirmation code with an existing admin")
|
||||
}
|
||||
|
@ -849,7 +878,7 @@ func handleResetPassword(r *http.Request, code, newPassword, confirmPassword str
|
|||
err = resetCodesMgr.Delete(code)
|
||||
return &admin, &user, err
|
||||
}
|
||||
user, err = dataprovider.GetUserWithGroupSettings(resetCode.Username, "")
|
||||
user, err = getActiveUser(resetCode.Username, r)
|
||||
if err != nil {
|
||||
return &admin, &user, util.NewValidationError("Unable to associate the confirmation code with an existing user")
|
||||
}
|
||||
|
@ -893,7 +922,7 @@ func getProtocolFromRequest(r *http.Request) string {
|
|||
}
|
||||
|
||||
func hideConfidentialData(claims *jwtTokenClaims, r *http.Request) bool {
|
||||
if !claims.hasPerm(dataprovider.PermAdminManageSystem) {
|
||||
if !claims.hasPerm(dataprovider.PermAdminAny) {
|
||||
return true
|
||||
}
|
||||
return r.URL.Query().Get("confidential_data") != "1"
|
||||
|
|
|
@ -66,7 +66,7 @@ const (
|
|||
|
||||
var (
|
||||
tokenDuration = 20 * time.Minute
|
||||
shareTokenDuration = 12 * time.Hour
|
||||
shareTokenDuration = 2 * time.Hour
|
||||
// csrf token duration is greater than normal token duration to reduce issues
|
||||
// with the login form
|
||||
csrfTokenDuration = 6 * time.Hour
|
||||
|
@ -211,19 +211,6 @@ func (c *jwtTokenClaims) Decode(token map[string]any) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *jwtTokenClaims) isCriticalPermRemoved(permissions []string) bool {
|
||||
if util.Contains(permissions, dataprovider.PermAdminAny) {
|
||||
return false
|
||||
}
|
||||
if (util.Contains(c.Permissions, dataprovider.PermAdminManageAdmins) ||
|
||||
util.Contains(c.Permissions, dataprovider.PermAdminAny)) &&
|
||||
!util.Contains(permissions, dataprovider.PermAdminManageAdmins) &&
|
||||
!util.Contains(permissions, dataprovider.PermAdminAny) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *jwtTokenClaims) hasPerm(perm string) bool {
|
||||
if util.Contains(c.Permissions, dataprovider.PermAdminAny) {
|
||||
return true
|
||||
|
|
|
@ -342,7 +342,9 @@ type SecurityConf struct {
|
|||
PermissionsPolicy string `json:"permissions_policy" mapstructure:"permissions_policy"`
|
||||
// CrossOriginOpenerPolicy allows to set the `Cross-Origin-Opener-Policy` header value. Default is "".
|
||||
CrossOriginOpenerPolicy string `json:"cross_origin_opener_policy" mapstructure:"cross_origin_opener_policy"`
|
||||
proxyHeaders []string
|
||||
// CacheControl allow to set the Cache-Control header value.
|
||||
CacheControl string `json:"cache_control" mapstructure:"cache_control"`
|
||||
proxyHeaders []string
|
||||
}
|
||||
|
||||
func (s *SecurityConf) updateProxyHeaders() {
|
||||
|
@ -398,8 +400,6 @@ type UIBranding struct {
|
|||
// For example, if you create a directory named "branding" inside the static dir and
|
||||
// put the "mylogo.png" file in it, you must set "/branding/mylogo.png" as logo path.
|
||||
LogoPath string `json:"logo_path" mapstructure:"logo_path"`
|
||||
// Path to the image to show on the login screen relative to "static_files_path"
|
||||
LoginImagePath string `json:"login_image_path" mapstructure:"login_image_path"`
|
||||
// Path to your favicon relative to "static_files_path"
|
||||
FaviconPath string `json:"favicon_path" mapstructure:"favicon_path"`
|
||||
// DisclaimerName defines the name for the link to your optional disclaimer
|
||||
|
@ -420,11 +420,6 @@ func (b *UIBranding) check() {
|
|||
} else {
|
||||
b.LogoPath = "/img/logo.png"
|
||||
}
|
||||
if b.LoginImagePath != "" {
|
||||
b.LoginImagePath = util.CleanPath(b.LoginImagePath)
|
||||
} else {
|
||||
b.LoginImagePath = "/img/login_image.png"
|
||||
}
|
||||
if b.FaviconPath != "" {
|
||||
b.FaviconPath = util.CleanPath(b.FaviconPath)
|
||||
} else {
|
||||
|
@ -671,6 +666,10 @@ func (b *Binding) showClientLoginURL() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (b *Binding) isMutualTLSEnabled() bool {
|
||||
return b.ClientAuthType == 1
|
||||
}
|
||||
|
||||
type defenderStatus struct {
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
|
|
|
@ -366,6 +366,24 @@ func TestMain(m *testing.M) {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
kmsConfig := config.GetKMSConfig()
|
||||
err = kmsConfig.Initialize()
|
||||
if err != nil {
|
||||
logger.ErrorToConsole("error initializing kms: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = plugin.Initialize(pluginsConfig, "debug")
|
||||
if err != nil {
|
||||
logger.ErrorToConsole("error initializing plugin: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
mfaConfig := config.GetMFAConfig()
|
||||
err = mfaConfig.Initialize()
|
||||
if err != nil {
|
||||
logger.ErrorToConsole("error initializing MFA: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = dataprovider.Initialize(providerConf, configDir, true)
|
||||
if err != nil {
|
||||
logger.WarnToConsole("error initializing data provider: %v", err)
|
||||
|
@ -385,23 +403,6 @@ func TestMain(m *testing.M) {
|
|||
httpConfig.RetryMax = 1
|
||||
httpConfig.Timeout = 5
|
||||
httpConfig.Initialize(configDir) //nolint:errcheck
|
||||
kmsConfig := config.GetKMSConfig()
|
||||
err = kmsConfig.Initialize()
|
||||
if err != nil {
|
||||
logger.ErrorToConsole("error initializing kms: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
mfaConfig := config.GetMFAConfig()
|
||||
err = mfaConfig.Initialize()
|
||||
if err != nil {
|
||||
logger.ErrorToConsole("error initializing MFA: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = plugin.Initialize(pluginsConfig, "debug")
|
||||
if err != nil {
|
||||
logger.ErrorToConsole("error initializing plugin: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
httpdConf := config.GetHTTPDConfig()
|
||||
|
||||
|
@ -414,6 +415,7 @@ func TestMain(m *testing.M) {
|
|||
Value: "https",
|
||||
},
|
||||
},
|
||||
CacheControl: "private",
|
||||
}
|
||||
httpdtest.SetBaseURL(httpBaseURL)
|
||||
// required to test sftpfs
|
||||
|
@ -714,7 +716,7 @@ func TestRoleRelations(t *testing.T) {
|
|||
a.Role = role.Name
|
||||
_, resp, err = httpdtest.AddAdmin(a, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "a role admin cannot have the following permissions")
|
||||
assert.Contains(t, string(resp), "a role admin cannot be a super admin")
|
||||
|
||||
a.Permissions = []string{dataprovider.PermAdminAddUsers, dataprovider.PermAdminChangeUsers,
|
||||
dataprovider.PermAdminDeleteUsers, dataprovider.PermAdminViewUsers}
|
||||
|
@ -1826,6 +1828,10 @@ func TestBasicActionRulesHandling(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
dataprovider.EnabledActionCommands = []string{a.Options.CmdConfig.Cmd}
|
||||
defer func() {
|
||||
dataprovider.EnabledActionCommands = nil
|
||||
}()
|
||||
_, _, err = httpdtest.UpdateEventAction(a, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
// invalid type
|
||||
|
@ -1909,7 +1915,8 @@ func TestBasicActionRulesHandling(t *testing.T) {
|
|||
Conditions: dataprovider.EventConditions{
|
||||
FsEvents: []string{"upload"},
|
||||
Options: dataprovider.ConditionOptions{
|
||||
MinFileSize: 1024 * 1024,
|
||||
EventStatuses: []int{2, 3},
|
||||
MinFileSize: 1024 * 1024,
|
||||
},
|
||||
},
|
||||
Actions: []dataprovider.EventAction{
|
||||
|
@ -2359,13 +2366,24 @@ func TestEventActionValidation(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "command is required")
|
||||
action.Options.CmdConfig.Cmd = "relative"
|
||||
dataprovider.EnabledActionCommands = []string{action.Options.CmdConfig.Cmd}
|
||||
defer func() {
|
||||
dataprovider.EnabledActionCommands = nil
|
||||
}()
|
||||
|
||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "invalid command, it must be an absolute path")
|
||||
action.Options.CmdConfig.Cmd = filepath.Join(os.TempDir(), "cmd")
|
||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "is not allowed")
|
||||
|
||||
dataprovider.EnabledActionCommands = []string{action.Options.CmdConfig.Cmd}
|
||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "invalid command action timeout")
|
||||
|
||||
action.Options.CmdConfig.Timeout = 30
|
||||
action.Options.CmdConfig.EnvVars = []dataprovider.KeyValue{
|
||||
{
|
||||
|
@ -2380,6 +2398,17 @@ func TestEventActionValidation(t *testing.T) {
|
|||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "invalid command args")
|
||||
action.Options.CmdConfig.Args = nil
|
||||
// restrict commands
|
||||
if runtime.GOOS == osWindows {
|
||||
dataprovider.EnabledActionCommands = []string{"C:\\cmd.exe"}
|
||||
} else {
|
||||
dataprovider.EnabledActionCommands = []string{"/bin/sh"}
|
||||
}
|
||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "is not allowed")
|
||||
dataprovider.EnabledActionCommands = nil
|
||||
|
||||
action.Type = dataprovider.ActionTypeEmail
|
||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||
|
@ -2697,6 +2726,21 @@ func TestEventRuleValidation(t *testing.T) {
|
|||
_, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "sync execution is only supported for upload and pre-* events")
|
||||
|
||||
rule.Conditions.FsEvents = []string{"download"}
|
||||
rule.Conditions.Options.EventStatuses = []int{3, 2, 8}
|
||||
rule.Actions = []dataprovider.EventAction{
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: "action",
|
||||
},
|
||||
Order: 1,
|
||||
},
|
||||
}
|
||||
_, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "invalid event_status")
|
||||
|
||||
rule.Trigger = dataprovider.EventTriggerProviderEvent
|
||||
rule.Actions = []dataprovider.EventAction{
|
||||
{
|
||||
|
@ -5183,6 +5227,13 @@ func TestUserPublicKey(t *testing.T) {
|
|||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
// DSA keys are not accepted
|
||||
u = getTestUser()
|
||||
u.Password = ""
|
||||
u.PublicKeys = []string{"ssh-dss AAAAB3NzaC1kc3MAAACBAK+BKLZs1Vd0cWYOquKfp++0ml9hkzB7UDRozT3nhRcyHcuwASsXiVTqsg96oGjBcUUy076CXlsfJEXE2P0dF6tt1wvABPMwKpOn+kIrfJ0j93X2c2KIZNlD4YuNUJjLHu1DvgQHw8NMps6l5D0M5NFCRdD3NYhI5zFVJJ4CzikrAAAAFQCRBagw7gEbs0gd8So7OLMcSVzs/wAAAIBjuo7U9q8npchQ3otgCvj0xIwsQ+Fi9bH0SBceqbCcVzFYY6JXSQ0XmwHs+0AuvRCPIGaBdfcm+w+9YOxREtdEVjcmkYlfJpTaVljjWcWFWTQddbiamZhQ/xLU9CNLK4oYLwIGLZjCcG7nRDdLtLQdBFuzP/faEi3TD2BK114QmAAAAIEAj1n34pH2WKwbSZhzmz/OG0VzqJICFWboiM44LZl2AqcRBvEEycdHlGe2IKaj5lEtLgBKJt9NSFhBIzWh7gcEzSMlkiDecdYSFlDc4snmTiXaoiIehV59nTY6gc8GLWCzuem+WdHxvJ4yOSWF9k+a+Y+/v/35shNLkfokViOlN7k="}
|
||||
_, resp, err := httpdtest.AddUser(u, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "DSA key format is insecure and it is not allowed")
|
||||
}
|
||||
|
||||
func TestUpdateUserEmptyPassword(t *testing.T) {
|
||||
|
@ -11372,11 +11423,17 @@ func TestWebAPIChangeUserPwdMock(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
// invalid json
|
||||
req, err := http.NewRequest(http.MethodPut, userPwdPath, bytes.NewBuffer([]byte("{")))
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, userProfilePath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
// invalid json
|
||||
req, err = http.NewRequest(http.MethodPut, userPwdPath, bytes.NewBuffer([]byte("{")))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusBadRequest, rr)
|
||||
|
||||
pwd := make(map[string]string)
|
||||
|
@ -11399,6 +11456,13 @@ func TestWebAPIChangeUserPwdMock(t *testing.T) {
|
|||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, userProfilePath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusUnauthorized, rr)
|
||||
|
||||
_, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.Error(t, err)
|
||||
token, err = getJWTAPIUserTokenFromTestServer(defaultUsername, altAdminPassword)
|
||||
|
@ -11548,6 +11612,12 @@ func TestChangeAdminPwdMock(t *testing.T) {
|
|||
setBearerForReq(req, altToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
// try using the old token
|
||||
req, err = http.NewRequest(http.MethodGet, versionPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, altToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusUnauthorized, rr)
|
||||
|
||||
_, err = getJWTAPITokenFromTestServer(altAdminUsername, altAdminPassword)
|
||||
assert.Error(t, err)
|
||||
|
@ -11577,7 +11647,7 @@ func TestUpdateAdminMock(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
admin := getTestAdmin()
|
||||
admin.Username = altAdminUsername
|
||||
admin.Permissions = []string{dataprovider.PermAdminManageAdmins}
|
||||
admin.Permissions = []string{dataprovider.PermAdminAny}
|
||||
asJSON, err := json.Marshal(admin)
|
||||
assert.NoError(t, err)
|
||||
req, _ := http.NewRequest(http.MethodPost, adminPath, bytes.NewBuffer(asJSON))
|
||||
|
@ -11619,7 +11689,7 @@ func TestUpdateAdminMock(t *testing.T) {
|
|||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusBadRequest, rr)
|
||||
assert.Contains(t, rr.Body.String(), "you cannot remove these permissions to yourself")
|
||||
assert.Contains(t, rr.Body.String(), "you cannot change your permissions")
|
||||
admin.Permissions = []string{dataprovider.PermAdminAny}
|
||||
admin.Role = "missing role"
|
||||
asJSON, err = json.Marshal(admin)
|
||||
|
@ -11634,7 +11704,7 @@ func TestUpdateAdminMock(t *testing.T) {
|
|||
altToken, err := getJWTAPITokenFromTestServer(altAdminUsername, defaultTokenAuthPass)
|
||||
assert.NoError(t, err)
|
||||
admin.Password = "" // it must remain unchanged
|
||||
admin.Permissions = []string{dataprovider.PermAdminManageAdmins, dataprovider.PermAdminCloseConnections}
|
||||
admin.Permissions = []string{dataprovider.PermAdminAny}
|
||||
asJSON, err = json.Marshal(admin)
|
||||
assert.NoError(t, err)
|
||||
req, _ = http.NewRequest(http.MethodPut, path.Join(adminPath, altAdminUsername), bytes.NewBuffer(asJSON))
|
||||
|
@ -12403,8 +12473,7 @@ func TestStartQuotaScanNonExistentFolderMock(t *testing.T) {
|
|||
token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
|
||||
assert.NoError(t, err)
|
||||
folder := vfs.BaseVirtualFolder{
|
||||
MappedPath: os.TempDir(),
|
||||
Name: "afolder",
|
||||
Name: "afolder",
|
||||
}
|
||||
req, _ := http.NewRequest(http.MethodPost, path.Join(quotasBasePath, "folders", folder.Name, "scan"), nil)
|
||||
setBearerForReq(req, token)
|
||||
|
@ -13007,12 +13076,14 @@ func TestDefender(t *testing.T) {
|
|||
req.RemoteAddr = remoteAddr
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Empty(t, rr.Header().Get("Cache-Control"))
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, "/.well-known/acme-challenge/foo", nil)
|
||||
assert.NoError(t, err)
|
||||
req.RemoteAddr = remoteAddr
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
assert.Equal(t, "no-cache, no-store, max-age=0, must-revalidate, private", rr.Header().Get("Cache-Control"))
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
@ -13599,6 +13670,13 @@ func TestWebClientChangePwd(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusFound, rr)
|
||||
assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, webClientPingPath, nil)
|
||||
assert.NoError(t, err)
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusFound, rr)
|
||||
|
||||
_, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.Error(t, err)
|
||||
_, err = getJWTWebClientTokenFromTestServer(defaultUsername+"1", defaultPassword+"1")
|
||||
|
@ -18850,6 +18928,12 @@ func TestWebAdminLoginMock(t *testing.T) {
|
|||
cookie := rr.Header().Get("Cookie")
|
||||
assert.Empty(t, cookie)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, webStatusPath, nil)
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusFound, rr)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, logoutPath, nil)
|
||||
setBearerForReq(req, apiToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -23069,7 +23153,6 @@ func TestWebEventAction(t *testing.T) {
|
|||
csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
|
||||
assert.NoError(t, err)
|
||||
action := dataprovider.BaseEventAction{
|
||||
ID: 81,
|
||||
Name: "web_action_http",
|
||||
Description: "http web action",
|
||||
Type: dataprovider.ActionTypeHTTP,
|
||||
|
@ -23257,6 +23340,10 @@ func TestWebEventAction(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
dataprovider.EnabledActionCommands = []string{action.Options.CmdConfig.Cmd}
|
||||
defer func() {
|
||||
dataprovider.EnabledActionCommands = nil
|
||||
}()
|
||||
form.Set("type", fmt.Sprintf("%d", action.Type))
|
||||
req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
|
||||
bytes.NewBuffer([]byte(form.Encode())))
|
||||
|
@ -24002,7 +24089,8 @@ func TestWebIPListEntries(t *testing.T) {
|
|||
form.Set("protocols", "a")
|
||||
form.Add("protocols", "1")
|
||||
form.Add("protocols", "4")
|
||||
req, err = http.NewRequest(http.MethodPost, webIPListPath+"/2", bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, err = http.NewRequest(http.MethodPost, webIPListPath+"/"+strconv.Itoa(int(entry.Type)),
|
||||
bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, webToken)
|
||||
|
@ -25148,6 +25236,23 @@ func TestAdminForgotPassword(t *testing.T) {
|
|||
|
||||
lastResetCode = ""
|
||||
form.Set("username", altAdminUsername)
|
||||
// disable the admin
|
||||
admin.Status = 0
|
||||
admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req, err = http.NewRequest(http.MethodPost, webAdminForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr = executeRequest(req)
|
||||
assert.Equal(t, http.StatusFound, rr.Code)
|
||||
assert.GreaterOrEqual(t, len(lastResetCode), 0)
|
||||
|
||||
admin.Status = 1
|
||||
admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req, err = http.NewRequest(http.MethodPost, webAdminForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
|
@ -25182,13 +25287,28 @@ func TestAdminForgotPassword(t *testing.T) {
|
|||
rr = executeRequest(req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), util.I18nErrorChangePwdGeneric)
|
||||
// ok
|
||||
// disable the admin
|
||||
admin.Status = 0
|
||||
admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
form.Set("code", lastResetCode)
|
||||
req, err = http.NewRequest(http.MethodPost, webAdminResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr = executeRequest(req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), util.I18nErrorChangePwdGeneric)
|
||||
|
||||
admin.Status = 1
|
||||
admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
// ok
|
||||
req, err = http.NewRequest(http.MethodPost, webAdminResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr = executeRequest(req)
|
||||
assert.Equal(t, http.StatusFound, rr.Code)
|
||||
|
||||
form.Set("username", altAdminUsername)
|
||||
|
@ -25313,10 +25433,11 @@ func TestUserForgotPassword(t *testing.T) {
|
|||
rr = executeRequest(req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), util.I18nErrorPwdResetForbidded)
|
||||
user.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now().Add(-1 * time.Hour))
|
||||
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled}
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// user is expired
|
||||
lastResetCode = ""
|
||||
req, err = http.NewRequest(http.MethodPost, webClientForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
|
@ -25324,6 +25445,17 @@ func TestUserForgotPassword(t *testing.T) {
|
|||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr = executeRequest(req)
|
||||
assert.Equal(t, http.StatusFound, rr.Code)
|
||||
assert.GreaterOrEqual(t, len(lastResetCode), 0)
|
||||
|
||||
user.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now().Add(24 * time.Hour))
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, webClientForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr = executeRequest(req)
|
||||
assert.Equal(t, http.StatusFound, rr.Code)
|
||||
assert.GreaterOrEqual(t, len(lastResetCode), 20)
|
||||
// no csrf token
|
||||
form = make(url.Values)
|
||||
|
@ -25364,8 +25496,22 @@ func TestUserForgotPassword(t *testing.T) {
|
|||
rr = executeRequest(req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), util.I18nErrorChangePwdGeneric)
|
||||
// ok
|
||||
// Invalid login condition
|
||||
form.Set("code", lastResetCode)
|
||||
user.Filters.DeniedProtocols = []string{common.ProtocolHTTP}
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, webClientResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr = executeRequest(req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), util.I18nErrorChangePwdGeneric)
|
||||
// ok
|
||||
user.Filters.DeniedProtocols = []string{common.ProtocolFTP}
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, webClientResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
|
|
|
@ -330,9 +330,8 @@ func TestBrandingValidation(t *testing.T) {
|
|||
b := Binding{
|
||||
Branding: Branding{
|
||||
WebAdmin: UIBranding{
|
||||
LogoPath: "path1",
|
||||
LoginImagePath: "login1.png",
|
||||
DefaultCSS: []string{"my.css"},
|
||||
LogoPath: "path1",
|
||||
DefaultCSS: []string{"my.css"},
|
||||
},
|
||||
WebClient: UIBranding{
|
||||
FaviconPath: "favicon1.ico",
|
||||
|
@ -344,12 +343,10 @@ func TestBrandingValidation(t *testing.T) {
|
|||
b.checkBranding()
|
||||
assert.Equal(t, "/favicon.ico", b.Branding.WebAdmin.FaviconPath)
|
||||
assert.Equal(t, "/path1", b.Branding.WebAdmin.LogoPath)
|
||||
assert.Equal(t, "/login1.png", b.Branding.WebAdmin.LoginImagePath)
|
||||
assert.Equal(t, []string{"/my.css"}, b.Branding.WebAdmin.DefaultCSS)
|
||||
assert.Len(t, b.Branding.WebAdmin.ExtraCSS, 0)
|
||||
assert.Equal(t, "/favicon1.ico", b.Branding.WebClient.FaviconPath)
|
||||
assert.Equal(t, path.Join(webStaticFilesPath, "/path2"), b.Branding.WebClient.DisclaimerPath)
|
||||
assert.Equal(t, "/img/login_image.png", b.Branding.WebClient.LoginImagePath)
|
||||
if assert.Len(t, b.Branding.WebClient.ExtraCSS, 1) {
|
||||
assert.Equal(t, "/1.css", b.Branding.WebClient.ExtraCSS[0])
|
||||
}
|
||||
|
@ -1744,6 +1741,29 @@ func TestCookieExpiration(t *testing.T) {
|
|||
cookie = rr.Header().Get("Set-Cookie")
|
||||
assert.NotEmpty(t, cookie)
|
||||
|
||||
// test a disabled user
|
||||
user.Status = 0
|
||||
err = dataprovider.UpdateUser(&user, "", "", "")
|
||||
assert.NoError(t, err)
|
||||
user, err = dataprovider.UserExists(user.Username, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
claims = make(map[string]any)
|
||||
claims[claimUsernameKey] = user.Username
|
||||
claims[claimPermissionsKey] = user.Filters.WebClient
|
||||
claims[jwt.SubjectKey] = user.GetSignature()
|
||||
claims[jwt.ExpirationKey] = time.Now().Add(1 * time.Minute)
|
||||
claims[jwt.AudienceKey] = []string{tokenAudienceWebClient}
|
||||
token, _, err = server.tokenAuth.Encode(claims)
|
||||
assert.NoError(t, err)
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
|
||||
ctx = jwtauth.NewContext(req.Context(), token, nil)
|
||||
server.checkCookieExpiration(rr, req.WithContext(ctx))
|
||||
cookie = rr.Header().Get("Set-Cookie")
|
||||
assert.Empty(t, cookie)
|
||||
|
||||
err = dataprovider.DeleteUser(user.Username, "", "", "")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -2095,7 +2115,7 @@ func TestProxyHeaders(t *testing.T) {
|
|||
rr := httptest.NewRecorder()
|
||||
testServer.Config.Handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "login from IP 127.0.0.1 not allowed")
|
||||
assert.NotContains(t, rr.Body.String(), "login from IP 127.0.0.1 not allowed")
|
||||
|
||||
req.RemoteAddr = testIP
|
||||
rr = httptest.NewRecorder()
|
||||
|
@ -2554,7 +2574,6 @@ func TestHTTPDFile(t *testing.T) {
|
|||
user.Permissions["/"] = []string{dataprovider.PermAny}
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(xid.New().String(), common.ProtocolHTTP, "", "", user),
|
||||
request: nil,
|
||||
}
|
||||
|
||||
fs, err := user.GetFilesystem("")
|
||||
|
@ -2923,6 +2942,7 @@ func TestSecureMiddlewareIntegration(t *testing.T) {
|
|||
STSIncludeSubdomains: true,
|
||||
STSPreload: true,
|
||||
ContentTypeNosniff: true,
|
||||
CacheControl: "private",
|
||||
},
|
||||
},
|
||||
enableWebAdmin: true,
|
||||
|
@ -2942,6 +2962,7 @@ func TestSecureMiddlewareIntegration(t *testing.T) {
|
|||
r.Host = "127.0.0.1"
|
||||
server.router.ServeHTTP(rr, r)
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
assert.Equal(t, "no-cache, no-store, max-age=0, must-revalidate, private", rr.Header().Get("Cache-Control"))
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
r.Header.Set(forwardedHostHeader, "www.sftpgo.com")
|
||||
|
|
|
@ -440,6 +440,7 @@ func checkAPIKeyAuth(tokenAuth *jwtauth.JWTAuth, scope dataprovider.APIKeyScope)
|
|||
"", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
common.DelayLogin(nil)
|
||||
} else {
|
||||
if k.User != "" {
|
||||
apiUser = k.User
|
||||
|
@ -512,6 +513,7 @@ func authenticateAdminWithAPIKey(username, keyID string, tokenAuth *jwtauth.JWTA
|
|||
}
|
||||
r.Header.Set("Authorization", fmt.Sprintf("Bearer %v", resp["access_token"]))
|
||||
dataprovider.UpdateAdminLastLogin(&admin)
|
||||
common.DelayLogin(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -584,3 +586,17 @@ func checkPartialAuth(w http.ResponseWriter, r *http.Request, audience string, t
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cacheControlMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, private")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func cleanCacheControlMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Del("Cache-Control")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,8 +20,6 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/xid"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
"github.com/drakkan/sftpgo/v2/internal/kms"
|
||||
"github.com/drakkan/sftpgo/v2/internal/logger"
|
||||
|
@ -54,7 +52,7 @@ type oauth2PendingAuth struct {
|
|||
|
||||
func newOAuth2PendingAuth(provider int, redirectURL, clientID string, clientSecret *kms.Secret) oauth2PendingAuth {
|
||||
return oauth2PendingAuth{
|
||||
State: xid.New().String(),
|
||||
State: util.GenerateOpaqueString(),
|
||||
Provider: provider,
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
|
|
|
@ -202,8 +202,8 @@ type oidcPendingAuth struct {
|
|||
|
||||
func newOIDCPendingAuth(audience tokenAudience) oidcPendingAuth {
|
||||
return oidcPendingAuth{
|
||||
State: xid.New().String(),
|
||||
Nonce: xid.New().String(),
|
||||
State: util.GenerateOpaqueString(),
|
||||
Nonce: util.GenerateOpaqueString(),
|
||||
Audience: audience,
|
||||
IssuedAt: util.GetTimeAsMsSinceEpoch(time.Now()),
|
||||
}
|
||||
|
@ -345,14 +345,15 @@ func (t *oidcToken) refresh(ctx context.Context, config OAuth2Config, verifier O
|
|||
logger.Debug(logSender, "", "unable to verify refreshed id token for cookie %q: %v", t.Cookie, err)
|
||||
return err
|
||||
}
|
||||
if idToken.Nonce != t.Nonce {
|
||||
logger.Debug(logSender, "", "unable to verify refreshed id token for cookie %q: nonce mismatch", t.Cookie)
|
||||
if idToken.Nonce != "" && idToken.Nonce != t.Nonce {
|
||||
logger.Warn(logSender, "", "unable to verify refreshed id token for cookie %q: nonce mismatch, expected: %q, actual: %q",
|
||||
t.Cookie, t.Nonce, idToken.Nonce)
|
||||
return errors.New("the refreshed token nonce mismatch")
|
||||
}
|
||||
claims := make(map[string]any)
|
||||
err = idToken.Claims(&claims)
|
||||
if err != nil {
|
||||
logger.Debug(logSender, "", "unable to get refreshed id token claims for cookie %q: %v", t.Cookie, err)
|
||||
logger.Warn(logSender, "", "unable to get refreshed id token claims for cookie %q: %v", t.Cookie, err)
|
||||
return err
|
||||
}
|
||||
sid, ok := claims["sid"].(string)
|
||||
|
@ -405,7 +406,7 @@ func (t *oidcToken) getUser(r *http.Request) error {
|
|||
Name: t.Username,
|
||||
IP: ipAddr,
|
||||
Protocol: common.ProtocolOIDC,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Status: 1,
|
||||
}
|
||||
if t.isAdmin() {
|
||||
|
@ -428,6 +429,7 @@ func (t *oidcToken) getUser(r *http.Request) error {
|
|||
t.TokenRole = admin.Role
|
||||
t.HideUserPageSections = admin.Filters.Preferences.HideUserPageSections
|
||||
dataprovider.UpdateAdminLastLogin(admin)
|
||||
common.DelayLogin(nil)
|
||||
return nil
|
||||
}
|
||||
params.Event = common.IDPLoginUser
|
||||
|
@ -593,6 +595,7 @@ func (s *httpdServer) handleOIDCRedirect(w http.ResponseWriter, r *http.Request)
|
|||
authReq, err := oidcMgr.getPendingAuth(state)
|
||||
if err != nil {
|
||||
logger.Debug(logSender, "", "oidc authentication state did not match")
|
||||
oidcMgr.removePendingAuth(state)
|
||||
s.renderClientMessagePage(w, r, util.I18nInvalidAuthReqTitle, http.StatusBadRequest,
|
||||
util.NewI18nError(err, util.I18nInvalidAuth), "")
|
||||
return
|
||||
|
@ -660,7 +663,7 @@ func (s *httpdServer) handleOIDCRedirect(w http.ResponseWriter, r *http.Request)
|
|||
RefreshToken: oauth2Token.RefreshToken,
|
||||
IDToken: rawIDToken,
|
||||
Nonce: idToken.Nonce,
|
||||
Cookie: xid.New().String(),
|
||||
Cookie: util.GenerateOpaqueString(),
|
||||
}
|
||||
if !oauth2Token.Expiry.IsZero() {
|
||||
token.ExpiresAt = util.GetTimeAsMsSinceEpoch(oauth2Token.Expiry)
|
||||
|
|
|
@ -151,8 +151,8 @@ func TestOIDCLoginLogout(t *testing.T) {
|
|||
assert.Contains(t, rr.Body.String(), util.I18nInvalidAuth)
|
||||
|
||||
expiredAuthReq := oidcPendingAuth{
|
||||
State: xid.New().String(),
|
||||
Nonce: xid.New().String(),
|
||||
State: util.GenerateOpaqueString(),
|
||||
Nonce: util.GenerateOpaqueString(),
|
||||
Audience: tokenAudienceWebClient,
|
||||
IssuedAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-10 * time.Minute)),
|
||||
}
|
||||
|
@ -561,7 +561,7 @@ func TestOIDCRefreshToken(t *testing.T) {
|
|||
r, err := http.NewRequest(http.MethodGet, webUsersPath, nil)
|
||||
assert.NoError(t, err)
|
||||
token := oidcToken{
|
||||
Cookie: xid.New().String(),
|
||||
Cookie: util.GenerateOpaqueString(),
|
||||
AccessToken: xid.New().String(),
|
||||
TokenType: "Bearer",
|
||||
ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-1 * time.Minute)),
|
||||
|
@ -627,7 +627,9 @@ func TestOIDCRefreshToken(t *testing.T) {
|
|||
},
|
||||
}
|
||||
verifier = mockOIDCVerifier{
|
||||
token: &oidc.IDToken{},
|
||||
token: &oidc.IDToken{
|
||||
Nonce: xid.New().String(), // nonce is different from the expected one
|
||||
},
|
||||
}
|
||||
err = token.refresh(context.Background(), &config, &verifier, r)
|
||||
if assert.Error(t, err) {
|
||||
|
@ -635,7 +637,7 @@ func TestOIDCRefreshToken(t *testing.T) {
|
|||
}
|
||||
verifier = mockOIDCVerifier{
|
||||
token: &oidc.IDToken{
|
||||
Nonce: token.Nonce,
|
||||
Nonce: "", // empty token is fine on refresh but claims are not set
|
||||
},
|
||||
}
|
||||
err = token.refresh(context.Background(), &config, &verifier, r)
|
||||
|
@ -663,7 +665,7 @@ func TestOIDCRefreshToken(t *testing.T) {
|
|||
|
||||
func TestOIDCRefreshUser(t *testing.T) {
|
||||
token := oidcToken{
|
||||
Cookie: xid.New().String(),
|
||||
Cookie: util.GenerateOpaqueString(),
|
||||
AccessToken: xid.New().String(),
|
||||
TokenType: "Bearer",
|
||||
ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(1 * time.Minute)),
|
||||
|
@ -777,7 +779,7 @@ func TestValidateOIDCToken(t *testing.T) {
|
|||
},
|
||||
}
|
||||
token := oidcToken{
|
||||
Cookie: xid.New().String(),
|
||||
Cookie: util.GenerateOpaqueString(),
|
||||
AccessToken: xid.New().String(),
|
||||
ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-2 * time.Minute)),
|
||||
}
|
||||
|
@ -793,8 +795,8 @@ func TestValidateOIDCToken(t *testing.T) {
|
|||
|
||||
server.tokenAuth = jwtauth.New("PS256", util.GenerateRandomBytes(32), nil)
|
||||
token = oidcToken{
|
||||
Cookie: xid.New().String(),
|
||||
AccessToken: xid.New().String(),
|
||||
Cookie: util.GenerateOpaqueString(),
|
||||
AccessToken: util.GenerateUniqueID(),
|
||||
}
|
||||
oidcMgr.addToken(token)
|
||||
rr = httptest.NewRecorder()
|
||||
|
@ -808,7 +810,7 @@ func TestValidateOIDCToken(t *testing.T) {
|
|||
assert.Len(t, oidcMgr.tokens, 0)
|
||||
|
||||
token = oidcToken{
|
||||
Cookie: xid.New().String(),
|
||||
Cookie: util.GenerateOpaqueString(),
|
||||
AccessToken: xid.New().String(),
|
||||
Role: "admin",
|
||||
}
|
||||
|
@ -1102,7 +1104,7 @@ func TestMemoryOIDCManager(t *testing.T) {
|
|||
AccessToken: xid.New().String(),
|
||||
Nonce: xid.New().String(),
|
||||
SessionID: xid.New().String(),
|
||||
Cookie: xid.New().String(),
|
||||
Cookie: util.GenerateOpaqueString(),
|
||||
Username: xid.New().String(),
|
||||
Role: "admin",
|
||||
Permissions: []string{dataprovider.PermAdminAny},
|
||||
|
@ -1152,7 +1154,7 @@ func TestMemoryOIDCManager(t *testing.T) {
|
|||
token.UsedAt = usedAt
|
||||
oidcMgr.tokens[token.Cookie] = token
|
||||
newToken := oidcToken{
|
||||
Cookie: xid.New().String(),
|
||||
Cookie: util.GenerateOpaqueString(),
|
||||
}
|
||||
oidcMgr.addToken(newToken)
|
||||
oidcMgr.cleanup()
|
||||
|
@ -1661,7 +1663,7 @@ func TestDbOIDCManager(t *testing.T) {
|
|||
}
|
||||
|
||||
token := oidcToken{
|
||||
Cookie: xid.New().String(),
|
||||
Cookie: util.GenerateOpaqueString(),
|
||||
AccessToken: xid.New().String(),
|
||||
TokenType: "Bearer",
|
||||
RefreshToken: xid.New().String(),
|
||||
|
|
|
@ -120,7 +120,7 @@ func (s *httpdServer) listenAndServe() error {
|
|||
httpServer.TLSConfig = config
|
||||
logger.Debug(logSender, "", "configured TLS cipher suites for binding %q: %v, certID: %v",
|
||||
s.binding.GetAddress(), httpServer.TLSConfig.CipherSuites, certID)
|
||||
if s.binding.ClientAuthType == 1 {
|
||||
if s.binding.isMutualTLSEnabled() {
|
||||
httpServer.TLSConfig.ClientCAs = certMgr.GetRootCAs()
|
||||
httpServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
httpServer.TLSConfig.VerifyConnection = s.verifyTLSConnection
|
||||
|
@ -309,7 +309,7 @@ func (s *httpdServer) handleWebClientPasswordResetPost(w http.ResponseWriter, r
|
|||
}
|
||||
connectionID := fmt.Sprintf("%v_%v", getProtocolFromRequest(r), xid.New().String())
|
||||
if err := checkHTTPClientUser(user, r, connectionID, true); err != nil {
|
||||
s.renderClientResetPwdPage(w, r, util.NewI18nError(err, util.I18nErrorDirList403), ipAddr)
|
||||
s.renderClientResetPwdPage(w, r, util.NewI18nError(err, util.I18nErrorLoginAfterReset), ipAddr)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -530,7 +530,7 @@ func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http
|
|||
return
|
||||
}
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil {
|
||||
err = handleDefenderEventLoginFailed(ipAddr, err)
|
||||
handleDefenderEventLoginFailed(ipAddr, err) //nolint:errcheck
|
||||
s.renderTwoFactorPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF), ipAddr)
|
||||
return
|
||||
}
|
||||
|
@ -821,6 +821,7 @@ func (s *httpdServer) loginAdmin(
|
|||
return
|
||||
}
|
||||
dataprovider.UpdateAdminLastLogin(admin)
|
||||
common.DelayLogin(nil)
|
||||
redirectURL := webUsersPath
|
||||
if errorFunc == nil {
|
||||
redirectURL = webAdminMFAPath
|
||||
|
@ -947,9 +948,10 @@ func (s *httpdServer) getToken(w http.ResponseWriter, r *http.Request) {
|
|||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
admin, err := dataprovider.CheckAdminAndPass(username, password, ipAddr)
|
||||
if err != nil {
|
||||
err = handleDefenderEventLoginFailed(ipAddr, err)
|
||||
handleDefenderEventLoginFailed(ipAddr, err) //nolint:errcheck
|
||||
w.Header().Set(common.HTTPAuthenticationHeader, basicRealm)
|
||||
sendAPIResponse(w, r, err, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
sendAPIResponse(w, r, dataprovider.ErrInvalidCredentials, http.StatusText(http.StatusUnauthorized),
|
||||
http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if admin.Filters.TOTPConfig.Enabled {
|
||||
|
@ -1000,6 +1002,7 @@ func (s *httpdServer) generateAndSendToken(w http.ResponseWriter, r *http.Reques
|
|||
}
|
||||
|
||||
dataprovider.UpdateAdminLastLogin(&admin)
|
||||
common.DelayLogin(nil)
|
||||
render.JSON(w, r, resp)
|
||||
}
|
||||
|
||||
|
@ -1035,6 +1038,10 @@ func (s *httpdServer) refreshClientToken(w http.ResponseWriter, r *http.Request,
|
|||
logger.Debug(logSender, "", "signature mismatch for user %q, unable to refresh cookie", user.Username)
|
||||
return
|
||||
}
|
||||
if err := user.CheckLoginConditions(); err != nil {
|
||||
logger.Debug(logSender, "", "unable to refresh cookie for user %q: %v", user.Username, err)
|
||||
return
|
||||
}
|
||||
if err := checkHTTPClientUser(&user, r, xid.New().String(), true); err != nil {
|
||||
logger.Debug(logSender, "", "unable to refresh cookie for user %q: %v", user.Username, err)
|
||||
return
|
||||
|
@ -1051,17 +1058,13 @@ func (s *httpdServer) refreshAdminToken(w http.ResponseWriter, r *http.Request,
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
if admin.Status != 1 {
|
||||
logger.Debug(logSender, "", "admin %q is disabled, unable to refresh cookie", admin.Username)
|
||||
return
|
||||
}
|
||||
if admin.GetSignature() != tokenClaims.Signature {
|
||||
logger.Debug(logSender, "", "signature mismatch for admin %q, unable to refresh cookie", admin.Username)
|
||||
return
|
||||
}
|
||||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
if !admin.CanLoginFromIP(ipAddr) {
|
||||
logger.Debug(logSender, "", "admin %q cannot login from %v, unable to refresh cookie", admin.Username, r.RemoteAddr)
|
||||
if err := admin.CanLogin(ipAddr); err != nil {
|
||||
logger.Debug(logSender, "", "unable to refresh cookie for admin %q, err: %v", admin.Username, err)
|
||||
return
|
||||
}
|
||||
tokenClaims.Permissions = admin.Permissions
|
||||
|
@ -1240,7 +1243,6 @@ func (s *httpdServer) initializeRouter() {
|
|||
s.router.Use(s.parseHeaders)
|
||||
s.router.Use(logger.NewStructuredLogger(logger.GetLogger()))
|
||||
s.router.Use(middleware.Recoverer)
|
||||
s.router.Use(middleware.Maybe(s.checkConnection, s.mustCheckPath))
|
||||
if s.binding.Security.Enabled {
|
||||
secureMiddleware := secure.New(secure.Options{
|
||||
AllowedHosts: s.binding.Security.AllowedHosts,
|
||||
|
@ -1256,6 +1258,9 @@ func (s *httpdServer) initializeRouter() {
|
|||
CrossOriginOpenerPolicy: s.binding.Security.CrossOriginOpenerPolicy,
|
||||
})
|
||||
secureMiddleware.SetBadHostHandler(http.HandlerFunc(s.badHostHandler))
|
||||
if s.binding.Security.CacheControl == "private" {
|
||||
s.router.Use(cacheControlMiddleware)
|
||||
}
|
||||
s.router.Use(secureMiddleware.Handler)
|
||||
if s.binding.Security.HTTPSRedirect {
|
||||
s.router.Use(s.binding.Security.redirectHandler)
|
||||
|
@ -1276,6 +1281,7 @@ func (s *httpdServer) initializeRouter() {
|
|||
})
|
||||
s.router.Use(c.Handler)
|
||||
}
|
||||
s.router.Use(middleware.Maybe(s.checkConnection, s.mustCheckPath))
|
||||
s.router.Use(middleware.GetHead)
|
||||
s.router.Use(middleware.Maybe(middleware.StripSlashes, s.mustStripSlash))
|
||||
|
||||
|
@ -1328,15 +1334,15 @@ func (s *httpdServer) initializeRouter() {
|
|||
router.With(forbidAPIKeyAuthentication).Get(admin2FARecoveryCodesPath, getRecoveryCodes)
|
||||
router.With(forbidAPIKeyAuthentication).Post(admin2FARecoveryCodesPath, generateRecoveryCodes)
|
||||
|
||||
router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)).
|
||||
router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminAny)).
|
||||
Get(apiKeysPath, getAPIKeys)
|
||||
router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)).
|
||||
router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminAny)).
|
||||
Post(apiKeysPath, addAPIKey)
|
||||
router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)).
|
||||
router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminAny)).
|
||||
Get(apiKeysPath+"/{id}", getAPIKeyByID)
|
||||
router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)).
|
||||
router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminAny)).
|
||||
Put(apiKeysPath+"/{id}", updateAPIKey)
|
||||
router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)).
|
||||
router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminAny)).
|
||||
Delete(apiKeysPath+"/{id}", deleteAPIKey)
|
||||
|
||||
router.Group(func(router chi.Router) {
|
||||
|
@ -1371,9 +1377,9 @@ func (s *httpdServer) initializeRouter() {
|
|||
router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Post(groupPath, addGroup)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Put(groupPath+"/{name}", updateGroup)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Delete(groupPath+"/{name}", deleteGroup)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(dumpDataPath, dumpData)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(loadDataPath, loadData)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(loadDataPath, loadDataFromRequest)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(dumpDataPath, dumpData)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(loadDataPath, loadData)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(loadDataPath, loadDataFromRequest)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/users/{username}/usage",
|
||||
updateUserQuotaUsage)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/users/{username}/transfer-usage",
|
||||
|
@ -1383,14 +1389,14 @@ func (s *httpdServer) initializeRouter() {
|
|||
router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderHosts, getDefenderHosts)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderHosts+"/{id}", getDefenderHostByID)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageDefender)).Delete(defenderHosts+"/{id}", deleteDefenderHostByID)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Get(adminPath, getAdmins)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Post(adminPath, addAdmin)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Get(adminPath+"/{username}", getAdminByUsername)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Put(adminPath+"/{username}", updateAdmin)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Delete(adminPath+"/{username}", deleteAdmin)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(adminPath, getAdmins)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(adminPath, addAdmin)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(adminPath+"/{username}", getAdminByUsername)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Put(adminPath+"/{username}", updateAdmin)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Delete(adminPath+"/{username}", deleteAdmin)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminDisableMFA)).Put(adminPath+"/{username}/2fa/disable", disableAdmin2FA)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminRetentionChecks)).Get(retentionChecksPath, getRetentionChecks)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminRetentionChecks)).Post(retentionBasePath+"/{username}/check",
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(retentionChecksPath, getRetentionChecks)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(retentionBasePath+"/{username}/check",
|
||||
startRetentionCheck)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler).
|
||||
Get(fsEventsPath, searchFsEvents)
|
||||
|
@ -1398,27 +1404,27 @@ func (s *httpdServer) initializeRouter() {
|
|||
Get(providerEventsPath, searchProviderEvents)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler).
|
||||
Get(logEventsPath, searchLogEvents)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventActionsPath, getEventActions)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventActionsPath+"/{name}", getEventActionByName)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(eventActionsPath, addEventAction)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Put(eventActionsPath+"/{name}", updateEventAction)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Delete(eventActionsPath+"/{name}", deleteEventAction)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventRulesPath, getEventRules)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventRulesPath+"/{name}", getEventRuleByName)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(eventRulesPath, addEventRule)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Put(eventRulesPath+"/{name}", updateEventRule)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Delete(eventRulesPath+"/{name}", deleteEventRule)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(eventRulesPath+"/run/{name}", runOnDemandRule)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Get(rolesPath, getRoles)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Post(rolesPath, addRole)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Get(rolesPath+"/{name}", getRoleByName)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Put(rolesPath+"/{name}", updateRole)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Delete(rolesPath+"/{name}", deleteRole)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), compressor.Handler).Get(ipListsPath+"/{type}", getIPListEntries) //nolint:goconst
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Post(ipListsPath+"/{type}", addIPListEntry)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Get(ipListsPath+"/{type}/{ipornet}", getIPListEntry) //nolint:goconst
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Put(ipListsPath+"/{type}/{ipornet}", updateIPListEntry)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Delete(ipListsPath+"/{type}/{ipornet}", deleteIPListEntry)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(eventActionsPath, getEventActions)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(eventActionsPath+"/{name}", getEventActionByName)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(eventActionsPath, addEventAction)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Put(eventActionsPath+"/{name}", updateEventAction)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Delete(eventActionsPath+"/{name}", deleteEventAction)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(eventRulesPath, getEventRules)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(eventRulesPath+"/{name}", getEventRuleByName)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(eventRulesPath, addEventRule)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Put(eventRulesPath+"/{name}", updateEventRule)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Delete(eventRulesPath+"/{name}", deleteEventRule)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(eventRulesPath+"/run/{name}", runOnDemandRule)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(rolesPath, getRoles)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(rolesPath, addRole)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(rolesPath+"/{name}", getRoleByName)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Put(rolesPath+"/{name}", updateRole)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Delete(rolesPath+"/{name}", deleteRole)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), compressor.Handler).Get(ipListsPath+"/{type}", getIPListEntries) //nolint:goconst
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(ipListsPath+"/{type}", addIPListEntry)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(ipListsPath+"/{type}/{ipornet}", getIPListEntry) //nolint:goconst
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Put(ipListsPath+"/{type}/{ipornet}", updateIPListEntry)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Delete(ipListsPath+"/{type}/{ipornet}", deleteIPListEntry)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1485,6 +1491,7 @@ func (s *httpdServer) initializeRouter() {
|
|||
|
||||
if s.renderOpenAPI {
|
||||
s.router.Group(func(router chi.Router) {
|
||||
router.Use(cleanCacheControlMiddleware)
|
||||
router.Use(compressor.Handler)
|
||||
serveStaticDir(router, webOpenAPIPath, s.openAPIPath, false)
|
||||
})
|
||||
|
@ -1493,6 +1500,7 @@ func (s *httpdServer) initializeRouter() {
|
|||
|
||||
if s.enableWebAdmin || s.enableWebClient {
|
||||
s.router.Group(func(router chi.Router) {
|
||||
router.Use(cleanCacheControlMiddleware)
|
||||
router.Use(compressor.Handler)
|
||||
serveStaticDir(router, webStaticFilesPath, s.staticFilesPath, true)
|
||||
})
|
||||
|
@ -1737,18 +1745,18 @@ func (s *httpdServer) setupWebAdminRoutes() {
|
|||
router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Post(webFolderPath, s.handleWebAddFolderPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminViewServerStatus), s.refreshCookie).
|
||||
Get(webStatusPath, s.handleWebGetStatus)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webAdminsPath, s.handleGetWebAdmins)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), compressor.Handler, s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), compressor.Handler, s.refreshCookie).
|
||||
Get(webAdminsPath+jsonAPISuffix, getAllAdmins)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webAdminPath, s.handleWebAddAdminGet)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webAdminPath+"/{username}", s.handleWebUpdateAdminGet)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Post(webAdminPath, s.handleWebAddAdminPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Post(webAdminPath+"/{username}",
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webAdminPath, s.handleWebAddAdminPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webAdminPath+"/{username}",
|
||||
s.handleWebUpdateAdminPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), verifyCSRFHeader).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), verifyCSRFHeader).
|
||||
Delete(webAdminPath+"/{username}", deleteAdmin)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminDisableMFA), verifyCSRFHeader).
|
||||
Put(webAdminPath+"/{username}/2fa/disable", disableAdmin2FA)
|
||||
|
@ -1768,61 +1776,61 @@ func (s *httpdServer) setupWebAdminRoutes() {
|
|||
Put(webUserPath+"/{username}/2fa/disable", disableUser2FA)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminQuotaScans), verifyCSRFHeader).
|
||||
Post(webQuotaScanPath+"/{username}", startUserQuotaScan)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(webMaintenancePath, s.handleWebMaintenance)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(webBackupPath, dumpData)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webRestorePath, s.handleWebRestore)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(webMaintenancePath, s.handleWebMaintenance)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(webBackupPath, dumpData)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webRestorePath, s.handleWebRestore)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webTemplateUser, s.handleWebTemplateUserGet)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webTemplateUser, s.handleWebTemplateUserPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webTemplateUser, s.handleWebTemplateUserPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webTemplateFolder, s.handleWebTemplateFolderGet)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webTemplateFolder, s.handleWebTemplateFolderPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webTemplateFolder, s.handleWebTemplateFolderPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(webDefenderPath, s.handleWebDefenderPage)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(webDefenderHostsPath, getDefenderHosts)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageDefender)).Delete(webDefenderHostsPath+"/{id}",
|
||||
deleteDefenderHostByID)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), compressor.Handler, s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageDefender), verifyCSRFHeader).
|
||||
Delete(webDefenderHostsPath+"/{id}", deleteDefenderHostByID)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), compressor.Handler, s.refreshCookie).
|
||||
Get(webAdminEventActionsPath+jsonAPISuffix, getAllActions)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webAdminEventActionsPath, s.handleWebGetEventActions)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webAdminEventActionPath, s.handleWebAddEventActionGet)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(webAdminEventActionPath,
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webAdminEventActionPath,
|
||||
s.handleWebAddEventActionPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webAdminEventActionPath+"/{name}", s.handleWebUpdateEventActionGet)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(webAdminEventActionPath+"/{name}",
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webAdminEventActionPath+"/{name}",
|
||||
s.handleWebUpdateEventActionPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), verifyCSRFHeader).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), verifyCSRFHeader).
|
||||
Delete(webAdminEventActionPath+"/{name}", deleteEventAction)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), compressor.Handler, s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), compressor.Handler, s.refreshCookie).
|
||||
Get(webAdminEventRulesPath+jsonAPISuffix, getAllRules)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webAdminEventRulesPath, s.handleWebGetEventRules)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webAdminEventRulePath, s.handleWebAddEventRuleGet)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(webAdminEventRulePath,
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webAdminEventRulePath,
|
||||
s.handleWebAddEventRulePost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webAdminEventRulePath+"/{name}", s.handleWebUpdateEventRuleGet)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(webAdminEventRulePath+"/{name}",
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webAdminEventRulePath+"/{name}",
|
||||
s.handleWebUpdateEventRulePost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), verifyCSRFHeader).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), verifyCSRFHeader).
|
||||
Delete(webAdminEventRulePath+"/{name}", deleteEventRule)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), verifyCSRFHeader).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), verifyCSRFHeader).
|
||||
Post(webAdminEventRulePath+"/run/{name}", runOnDemandRule)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageRoles), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webAdminRolesPath, s.handleWebGetRoles)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageRoles), compressor.Handler, s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), compressor.Handler, s.refreshCookie).
|
||||
Get(webAdminRolesPath+jsonAPISuffix, getAllRoles)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageRoles), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webAdminRolePath, s.handleWebAddRoleGet)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Post(webAdminRolePath, s.handleWebAddRolePost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageRoles), s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webAdminRolePath, s.handleWebAddRolePost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).
|
||||
Get(webAdminRolePath+"/{name}", s.handleWebUpdateRoleGet)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Post(webAdminRolePath+"/{name}",
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webAdminRolePath+"/{name}",
|
||||
s.handleWebUpdateRolePost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageRoles), verifyCSRFHeader).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), verifyCSRFHeader).
|
||||
Delete(webAdminRolePath+"/{name}", deleteRole)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminViewEvents), s.refreshCookie).Get(webEventsPath,
|
||||
s.handleWebGetEvents)
|
||||
|
@ -1832,24 +1840,24 @@ func (s *httpdServer) setupWebAdminRoutes() {
|
|||
Get(webEventsProviderSearchPath, searchProviderEvents)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler, s.refreshCookie).
|
||||
Get(webEventsLogSearchPath, searchLogEvents)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Get(webIPListsPath, s.handleWebIPListsPage)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), compressor.Handler, s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Get(webIPListsPath, s.handleWebIPListsPage)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), compressor.Handler, s.refreshCookie).
|
||||
Get(webIPListsPath+"/{type}", getIPListEntries)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), s.refreshCookie).Get(webIPListPath+"/{type}",
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).Get(webIPListPath+"/{type}",
|
||||
s.handleWebAddIPListEntryGet)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Post(webIPListPath+"/{type}",
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webIPListPath+"/{type}",
|
||||
s.handleWebAddIPListEntryPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), s.refreshCookie).Get(webIPListPath+"/{type}/{ipornet}",
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).Get(webIPListPath+"/{type}/{ipornet}",
|
||||
s.handleWebUpdateIPListEntryGet)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Post(webIPListPath+"/{type}/{ipornet}",
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webIPListPath+"/{type}/{ipornet}",
|
||||
s.handleWebUpdateIPListEntryPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), verifyCSRFHeader).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), verifyCSRFHeader).
|
||||
Delete(webIPListPath+"/{type}/{ipornet}", deleteIPListEntry)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem), s.refreshCookie).Get(webConfigsPath, s.handleWebConfigs)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webConfigsPath, s.handleWebConfigsPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem), verifyCSRFHeader, s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), s.refreshCookie).Get(webConfigsPath, s.handleWebConfigs)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny)).Post(webConfigsPath, s.handleWebConfigsPost)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), verifyCSRFHeader, s.refreshCookie).
|
||||
Post(webConfigsPath+"/smtp/test", testSMTPConfig)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageSystem), verifyCSRFHeader, s.refreshCookie).
|
||||
router.With(s.checkPerm(dataprovider.PermAdminAny), verifyCSRFHeader, s.refreshCookie).
|
||||
Post(webOAuth2TokenPath, handleSMTPOAuth2TokenRequestPost)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -73,32 +73,35 @@ type loginPage struct {
|
|||
|
||||
type twoFactorPage struct {
|
||||
commonBasePage
|
||||
CurrentURL string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
RecoveryURL string
|
||||
Title string
|
||||
Branding UIBranding
|
||||
CurrentURL string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
RecoveryURL string
|
||||
Title string
|
||||
Branding UIBranding
|
||||
CheckRedirect bool
|
||||
}
|
||||
|
||||
type forgotPwdPage struct {
|
||||
commonBasePage
|
||||
CurrentURL string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
LoginURL string
|
||||
Title string
|
||||
Branding UIBranding
|
||||
CurrentURL string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
LoginURL string
|
||||
Title string
|
||||
Branding UIBranding
|
||||
CheckRedirect bool
|
||||
}
|
||||
|
||||
type resetPwdPage struct {
|
||||
commonBasePage
|
||||
CurrentURL string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
LoginURL string
|
||||
Title string
|
||||
Branding UIBranding
|
||||
CurrentURL string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
LoginURL string
|
||||
Title string
|
||||
Branding UIBranding
|
||||
CheckRedirect bool
|
||||
}
|
||||
|
||||
func getSliceFromDelimitedValues(values, delimiter string) []string {
|
||||
|
|
|
@ -257,6 +257,7 @@ type setupPage struct {
|
|||
HideSupportLink bool
|
||||
Title string
|
||||
Branding UIBranding
|
||||
CheckRedirect bool
|
||||
}
|
||||
|
||||
type folderPage struct {
|
||||
|
@ -290,13 +291,14 @@ type rolePage struct {
|
|||
|
||||
type eventActionPage struct {
|
||||
basePage
|
||||
Action dataprovider.BaseEventAction
|
||||
ActionTypes []dataprovider.EnumMapping
|
||||
FsActions []dataprovider.EnumMapping
|
||||
HTTPMethods []string
|
||||
RedactedSecret string
|
||||
Error *util.I18nError
|
||||
Mode genericPageMode
|
||||
Action dataprovider.BaseEventAction
|
||||
ActionTypes []dataprovider.EnumMapping
|
||||
FsActions []dataprovider.EnumMapping
|
||||
HTTPMethods []string
|
||||
EnabledCommands []string
|
||||
RedactedSecret string
|
||||
Error *util.I18nError
|
||||
Mode genericPageMode
|
||||
}
|
||||
|
||||
type eventRulePage struct {
|
||||
|
@ -1078,14 +1080,15 @@ func (s *httpdServer) renderEventActionPage(w http.ResponseWriter, r *http.Reque
|
|||
}
|
||||
|
||||
data := eventActionPage{
|
||||
basePage: s.getBasePageData(title, currentURL, r),
|
||||
Action: action,
|
||||
ActionTypes: dataprovider.EventActionTypes,
|
||||
FsActions: dataprovider.FsActionTypes,
|
||||
HTTPMethods: dataprovider.SupportedHTTPActionMethods,
|
||||
RedactedSecret: redactedSecret,
|
||||
Error: getI18nError(err),
|
||||
Mode: mode,
|
||||
basePage: s.getBasePageData(title, currentURL, r),
|
||||
Action: action,
|
||||
ActionTypes: dataprovider.EventActionTypes,
|
||||
FsActions: dataprovider.FsActionTypes,
|
||||
HTTPMethods: dataprovider.SupportedHTTPActionMethods,
|
||||
EnabledCommands: dataprovider.EnabledActionCommands,
|
||||
RedactedSecret: redactedSecret,
|
||||
Error: getI18nError(err),
|
||||
Mode: mode,
|
||||
}
|
||||
renderAdminTemplate(w, templateEventAction, data)
|
||||
}
|
||||
|
@ -2516,6 +2519,13 @@ func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventCo
|
|||
if err != nil {
|
||||
return dataprovider.EventConditions{}, util.NewI18nError(fmt.Errorf("invalid max file size: %w", err), util.I18nErrorInvalidMaxSize)
|
||||
}
|
||||
var eventStatuses []int
|
||||
for _, s := range r.Form["fs_statuses"] {
|
||||
status, err := strconv.ParseInt(s, 10, 32)
|
||||
if err == nil {
|
||||
eventStatuses = append(eventStatuses, int(status))
|
||||
}
|
||||
}
|
||||
conditions := dataprovider.EventConditions{
|
||||
FsEvents: r.Form["fs_events"],
|
||||
ProviderEvents: r.Form["provider_events"],
|
||||
|
@ -2527,6 +2537,7 @@ func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventCo
|
|||
RoleNames: roleNames,
|
||||
FsPaths: fsPaths,
|
||||
Protocols: r.Form["fs_protocols"],
|
||||
EventStatuses: eventStatuses,
|
||||
ProviderObjects: r.Form["provider_objects"],
|
||||
MinFileSize: minFileSize,
|
||||
MaxFileSize: maxFileSize,
|
||||
|
@ -3035,9 +3046,9 @@ func (s *httpdServer) handleWebUpdateAdminPost(w http.ResponseWriter, r *http.Re
|
|||
return
|
||||
}
|
||||
if username == claims.Username {
|
||||
if claims.isCriticalPermRemoved(updatedAdmin.Permissions) {
|
||||
if !util.SlicesEqual(admin.Permissions, updatedAdmin.Permissions) {
|
||||
s.renderAddUpdateAdminPage(w, r, &updatedAdmin,
|
||||
util.NewI18nError(errors.New("you cannot remove these permissions to yourself"),
|
||||
util.NewI18nError(errors.New("you cannot change your permissions"),
|
||||
util.I18nErrorAdminSelfPerms,
|
||||
), false)
|
||||
return
|
||||
|
@ -3259,7 +3270,7 @@ func (s *httpdServer) handleWebTemplateUserPost(w http.ResponseWriter, r *http.R
|
|||
s.renderMessagePage(w, r, util.I18nTemplateUserTitle, http.StatusBadRequest, err, "")
|
||||
return
|
||||
}
|
||||
// to create a template the "manage_system" permission is required, so role admins cannot use
|
||||
// to create a template the "*" permission is required, so role admins cannot use
|
||||
// this method, we don't need to force the role
|
||||
dump.Users = append(dump.Users, u)
|
||||
for _, folder := range u.VirtualFolders {
|
||||
|
@ -4268,14 +4279,15 @@ func (s *httpdServer) handleOAuth2TokenRedirect(w http.ResponseWriter, r *http.R
|
|||
return
|
||||
}
|
||||
|
||||
defer oauth2Mgr.removePendingAuth(state)
|
||||
|
||||
pendingAuth, err := oauth2Mgr.getPendingAuth(state)
|
||||
if err != nil {
|
||||
oauth2Mgr.removePendingAuth(state)
|
||||
s.renderMessagePage(w, r, util.I18nOAuth2ErrorTitle, http.StatusInternalServerError,
|
||||
util.NewI18nError(err, util.I18nOAuth2ErrorValidateState), "")
|
||||
return
|
||||
}
|
||||
oauth2Mgr.removePendingAuth(state)
|
||||
|
||||
oauth2Config := smtp.OAuth2Config{
|
||||
Provider: pendingAuth.Provider,
|
||||
ClientID: pendingAuth.ClientID,
|
||||
|
@ -4306,6 +4318,9 @@ func (s *httpdServer) handleOAuth2TokenRedirect(w http.ResponseWriter, r *http.R
|
|||
}
|
||||
|
||||
func updateSMTPSecrets(newConfigs, currentConfigs *dataprovider.SMTPConfigs) {
|
||||
if currentConfigs == nil {
|
||||
currentConfigs = &dataprovider.SMTPConfigs{}
|
||||
}
|
||||
if newConfigs.Password.IsNotPlainAndNotEmpty() {
|
||||
newConfigs.Password = currentConfigs.Password
|
||||
}
|
||||
|
|
|
@ -701,6 +701,9 @@ func (s *httpdServer) renderAddUpdateSharePage(w http.ResponseWriter, r *http.Re
|
|||
currentURL = fmt.Sprintf("%v/%v", webClientSharePath, url.PathEscape(share.ShareID))
|
||||
title = util.I18nShareUpdateTitle
|
||||
}
|
||||
if share.IsPasswordHashed() {
|
||||
share.Password = redactedSecret
|
||||
}
|
||||
data := clientSharePage{
|
||||
baseClientPage: s.getBaseClientPageData(title, currentURL, r),
|
||||
Share: share,
|
||||
|
@ -1418,7 +1421,6 @@ func (s *httpdServer) handleClientUpdateShareGet(w http.ResponseWriter, r *http.
|
|||
shareID := getURLParam(r, "id")
|
||||
share, err := dataprovider.ShareExists(shareID, claims.Username)
|
||||
if err == nil {
|
||||
share.HideConfidentialData()
|
||||
s.renderAddUpdateSharePage(w, r, &share, nil, false)
|
||||
} else if errors.Is(err, util.ErrNotFound) {
|
||||
s.renderClientNotFoundPage(w, r, err)
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -1662,7 +1663,7 @@ func compareConditionPatternOptions(expected, actual []dataprovider.ConditionPat
|
|||
return nil
|
||||
}
|
||||
|
||||
func checkEventConditionOptions(expected, actual dataprovider.ConditionOptions) error {
|
||||
func checkEventConditionOptions(expected, actual dataprovider.ConditionOptions) error { //nolint:gocyclo
|
||||
if err := compareConditionPatternOptions(expected.Names, actual.Names); err != nil {
|
||||
return errors.New("condition names mismatch")
|
||||
}
|
||||
|
@ -1683,6 +1684,14 @@ func checkEventConditionOptions(expected, actual dataprovider.ConditionOptions)
|
|||
return errors.New("condition protocols content mismatch")
|
||||
}
|
||||
}
|
||||
if len(expected.EventStatuses) != len(actual.EventStatuses) {
|
||||
return errors.New("condition statuses mismatch")
|
||||
}
|
||||
for _, v := range expected.EventStatuses {
|
||||
if !slices.Contains(actual.EventStatuses, v) {
|
||||
return errors.New("condition statuses content mismatch")
|
||||
}
|
||||
}
|
||||
if len(expected.ProviderObjects) != len(actual.ProviderObjects) {
|
||||
return errors.New("condition provider objects mismatch")
|
||||
}
|
||||
|
|
|
@ -73,7 +73,8 @@ var (
|
|||
// ErrInvalidSecret defines the error to return if a secret is not valid
|
||||
ErrInvalidSecret = errors.New("invalid secret")
|
||||
validSecretStatuses = []string{sdkkms.SecretStatusPlain, sdkkms.SecretStatusAES256GCM, sdkkms.SecretStatusSecretBox,
|
||||
sdkkms.SecretStatusVaultTransit, sdkkms.SecretStatusAWS, sdkkms.SecretStatusGCP, sdkkms.SecretStatusRedacted}
|
||||
sdkkms.SecretStatusVaultTransit, sdkkms.SecretStatusAWS, sdkkms.SecretStatusGCP, sdkkms.SecretStatusAzureKeyVault,
|
||||
"OracleKeyVault", sdkkms.SecretStatusRedacted}
|
||||
config Configuration
|
||||
secretProviders = make(map[string]registeredSecretProvider)
|
||||
)
|
||||
|
|
|
@ -29,6 +29,11 @@ type HCLogAdapter struct {
|
|||
|
||||
// Log emits a message and key/value pairs at a provided log level
|
||||
func (l *HCLogAdapter) Log(level hclog.Level, msg string, args ...any) {
|
||||
// Workaround to avoid logging plugin arguments that may contain sensitive data.
|
||||
// Check everytime we update go-plugin library.
|
||||
if msg == "starting plugin" {
|
||||
return
|
||||
}
|
||||
var ev *zerolog.Event
|
||||
switch level {
|
||||
case hclog.Info:
|
||||
|
|
|
@ -28,10 +28,10 @@ type LegoAdapter struct {
|
|||
// Fatal emits a log at Error level
|
||||
func (l *LegoAdapter) Fatal(args ...any) {
|
||||
if l.LogToConsole {
|
||||
ErrorToConsole(fmt.Sprint(args...))
|
||||
ErrorToConsole("%s", fmt.Sprint(args...))
|
||||
return
|
||||
}
|
||||
Log(LevelError, legoLogSender, "", fmt.Sprint(args...))
|
||||
Log(LevelError, legoLogSender, "", "%s", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Fatalln is the same as Fatal
|
||||
|
@ -51,10 +51,10 @@ func (l *LegoAdapter) Fatalf(format string, args ...any) {
|
|||
// Print emits a log at Info level
|
||||
func (l *LegoAdapter) Print(args ...any) {
|
||||
if l.LogToConsole {
|
||||
InfoToConsole(fmt.Sprint(args...))
|
||||
InfoToConsole("%s", fmt.Sprint(args...))
|
||||
return
|
||||
}
|
||||
Log(LevelInfo, legoLogSender, "", fmt.Sprint(args...))
|
||||
Log(LevelInfo, legoLogSender, "", "%s", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Println is the same as Print
|
||||
|
|
|
@ -203,9 +203,15 @@ func ErrorToConsole(format string, v ...any) {
|
|||
|
||||
// TransferLog logs uploads or downloads
|
||||
func TransferLog(operation, path string, elapsed int64, size int64, user, connectionID, protocol, localAddr,
|
||||
remoteAddr, ftpMode string,
|
||||
remoteAddr, ftpMode string, err error,
|
||||
) {
|
||||
ev := logger.Info().
|
||||
var ev *zerolog.Event
|
||||
if err != nil {
|
||||
ev = logger.Error()
|
||||
} else {
|
||||
ev = logger.Info()
|
||||
}
|
||||
ev.
|
||||
Timestamp().
|
||||
Str("sender", operation).
|
||||
Str("local_addr", localAddr).
|
||||
|
@ -219,7 +225,7 @@ func TransferLog(operation, path string, elapsed int64, size int64, user, connec
|
|||
if ftpMode != "" {
|
||||
ev.Str("ftp_mode", ftpMode)
|
||||
}
|
||||
ev.Send()
|
||||
ev.AnErr("error", err).Send()
|
||||
}
|
||||
|
||||
// CommandLog logs an SFTP/SCP/SSH command
|
||||
|
@ -284,7 +290,7 @@ func (l *StdLoggerWrapper) Write(p []byte) (n int, err error) {
|
|||
p = p[0 : n-1]
|
||||
}
|
||||
|
||||
Log(LevelError, l.Sender, "", bytesToString(p))
|
||||
Log(LevelError, l.Sender, "", "%s", bytesToString(p))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -29,9 +29,10 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
validKMSSchemes = []string{sdkkms.SchemeAWS, sdkkms.SchemeGCP, sdkkms.SchemeVaultTransit, sdkkms.SchemeAzureKeyVault}
|
||||
validKMSSchemes = []string{sdkkms.SchemeAWS, sdkkms.SchemeGCP, sdkkms.SchemeVaultTransit,
|
||||
sdkkms.SchemeAzureKeyVault, "ocikeyvault"}
|
||||
validKMSEncryptedStatuses = []string{sdkkms.SecretStatusVaultTransit, sdkkms.SecretStatusAWS, sdkkms.SecretStatusGCP,
|
||||
sdkkms.SecretStatusAzureKeyVault}
|
||||
sdkkms.SecretStatusAzureKeyVault, "OracleKeyVault"}
|
||||
)
|
||||
|
||||
// KMSConfig defines configuration parameters for kms plugins
|
||||
|
|
|
@ -118,7 +118,9 @@ func (c *Config) getEnvVarPrefix() string {
|
|||
return c.EnvPrefix
|
||||
}
|
||||
|
||||
prefix := strings.ToUpper(filepath.Base(c.Cmd)) + "_"
|
||||
baseName := filepath.Base(c.Cmd)
|
||||
name := strings.TrimSuffix(baseName, filepath.Ext(baseName))
|
||||
prefix := strings.ToUpper(name) + "_"
|
||||
return strings.ReplaceAll(prefix, "-", "_")
|
||||
}
|
||||
|
||||
|
@ -141,7 +143,7 @@ func (c *Config) getCommand() *exec.Cmd {
|
|||
}
|
||||
logger.Debug(logSender, "", "additional env vars for plugin %q: %+v", c.Cmd, c.EnvVars)
|
||||
for _, key := range c.EnvVars {
|
||||
cmd.Env = append(cmd.Env, os.Getenv(key))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, os.Getenv(key)))
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
@ -225,7 +227,7 @@ func initializePlugins() error {
|
|||
kmsID++
|
||||
kms.RegisterSecretProvider(config.KMSOptions.Scheme, config.KMSOptions.EncryptedStatus,
|
||||
Handler.Configs[idx].newKMSPluginSecretProvider)
|
||||
logger.Info(logSender, "", "registered secret provider for scheme: %v, encrypted status: %v",
|
||||
logger.Info(logSender, "", "registered secret provider for scheme %q, encrypted status %q",
|
||||
config.KMSOptions.Scheme, config.KMSOptions.EncryptedStatus)
|
||||
case auth.PluginName:
|
||||
plugin, err := newAuthPlugin(config)
|
||||
|
@ -338,7 +340,7 @@ func (m *Manager) NotifyLogEvent(event notifier.LogEventType, protocol, username
|
|||
if e == nil {
|
||||
message := ""
|
||||
if err != nil {
|
||||
message = err.Error()
|
||||
message = strings.Trim(err.Error(), "\x00")
|
||||
}
|
||||
|
||||
e = ¬ifier.LogEvent{
|
||||
|
|
|
@ -104,7 +104,7 @@ func (s *Service) Start(disableAWSInstallationCode bool) error {
|
|||
}
|
||||
}
|
||||
if !config.HasServicesToStart() {
|
||||
infoString := "no service configured, nothing to do"
|
||||
const infoString = "no service configured, nothing to do"
|
||||
logger.Info(logSender, "", infoString)
|
||||
logger.InfoToConsole(infoString)
|
||||
return errors.New(infoString)
|
||||
|
@ -129,6 +129,13 @@ func (s *Service) initializeServices(disableAWSInstallationCode bool) error {
|
|||
logger.ErrorToConsole("unable to initialize KMS: %v", err)
|
||||
return err
|
||||
}
|
||||
// We may have KMS plugins and their schema needs to be registered before
|
||||
// initializing the data provider which may contain KMS secrets.
|
||||
if err := plugin.Initialize(config.GetPluginsConfig(), s.LogLevel); err != nil {
|
||||
logger.Error(logSender, "", "unable to initialize plugin system: %v", err)
|
||||
logger.ErrorToConsole("unable to initialize plugin system: %v", err)
|
||||
return err
|
||||
}
|
||||
mfaConfig := config.GetMFAConfig()
|
||||
err = mfaConfig.Initialize()
|
||||
if err != nil {
|
||||
|
@ -142,11 +149,6 @@ func (s *Service) initializeServices(disableAWSInstallationCode bool) error {
|
|||
logger.ErrorToConsole("error initializing data provider: %v", err)
|
||||
return err
|
||||
}
|
||||
if err := plugin.Initialize(config.GetPluginsConfig(), s.LogLevel); err != nil {
|
||||
logger.Error(logSender, "", "unable to initialize plugin system: %v", err)
|
||||
logger.ErrorToConsole("unable to initialize plugin system: %v", err)
|
||||
return err
|
||||
}
|
||||
smtpConfig := config.GetSMTPConfig()
|
||||
err = smtpConfig.Initialize(s.ConfigDir, s.PortableMode != 1)
|
||||
if err != nil {
|
||||
|
|
|
@ -37,11 +37,10 @@ type Connection struct {
|
|||
// client's version string
|
||||
ClientVersion string
|
||||
// Remote address for this connection
|
||||
RemoteAddr net.Addr
|
||||
LocalAddr net.Addr
|
||||
channel io.ReadWriteCloser
|
||||
command string
|
||||
folderPrefix string
|
||||
RemoteAddr net.Addr
|
||||
LocalAddr net.Addr
|
||||
channel io.ReadWriteCloser
|
||||
command string
|
||||
}
|
||||
|
||||
// GetClientVersion returns the connected client's version
|
||||
|
@ -221,7 +220,7 @@ func (c *Connection) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
|
|||
return nil, err
|
||||
}
|
||||
modTime := time.Unix(0, 0)
|
||||
if request.Filepath != "/" || c.folderPrefix != "" {
|
||||
if request.Filepath != "/" {
|
||||
lister.Add(vfs.NewFileInfo("..", true, 0, modTime, false))
|
||||
}
|
||||
lister.Add(vfs.NewFileInfo(".", true, 0, modTime, false))
|
||||
|
|
|
@ -128,6 +128,10 @@ type Configuration struct {
|
|||
// KexAlgorithms specifies the available KEX (Key Exchange) algorithms in
|
||||
// preference order.
|
||||
KexAlgorithms []string `json:"kex_algorithms" mapstructure:"kex_algorithms"`
|
||||
// MinDHGroupExchangeKeySize defines the minimum key size to allow for the
|
||||
// key exchanges when using diffie-ellman-group-exchange-sha1 or sha256 key
|
||||
// exchange algorithms.
|
||||
MinDHGroupExchangeKeySize int `json:"min_dh_group_exchange_key_size" mapstructure:"min_dh_group_exchange_key_size"`
|
||||
// Ciphers specifies the ciphers allowed
|
||||
Ciphers []string `json:"ciphers" mapstructure:"ciphers"`
|
||||
// MACs Specifies the available MAC (message authentication code) algorithms
|
||||
|
@ -321,8 +325,11 @@ func (c *Configuration) Initialize(configDir string) error {
|
|||
return common.ErrNoBinding
|
||||
}
|
||||
|
||||
ssh.SetDHKexServerMinBits(uint32(c.MinDHGroupExchangeKeySize))
|
||||
logger.Debug(logSender, "", "minimum key size allowed for diffie-ellman-group-exchange: %d",
|
||||
ssh.GetDHKexServerMinBits())
|
||||
sftp.SetSFTPExtensions(sftpExtensions...) //nolint:errcheck // we configure valid SFTP Extensions so we cannot get an error
|
||||
sftp.MaxFilelist = vfs.ListerBatchSize
|
||||
sftp.MaxFilelist = 250
|
||||
|
||||
if err := c.configureSecurityOptions(serverConfig); err != nil {
|
||||
return err
|
||||
|
@ -392,8 +399,8 @@ func (c *Configuration) serve(listener net.Listener, serverConfig *ssh.ServerCon
|
|||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
if max := 1 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
if maxDelay := 1 * time.Second; tempDelay > maxDelay {
|
||||
tempDelay = maxDelay
|
||||
}
|
||||
logger.Warn(logSender, "", "accept error: %v; retrying in %v", err, tempDelay)
|
||||
time.Sleep(tempDelay)
|
||||
|
@ -686,7 +693,7 @@ func (c *Configuration) handleSftpConnection(channel ssh.Channel, connection *Co
|
|||
defer common.Connections.Remove(connection.GetID())
|
||||
|
||||
// Create the server instance for the channel using the handler we created above.
|
||||
server := sftp.NewRequestServer(channel, c.createHandlers(connection), sftp.WithRSAllocator(),
|
||||
server := sftp.NewRequestServer(channel, c.createHandlers(connection),
|
||||
sftp.WithStartDirectory(connection.User.Filters.StartDirectory))
|
||||
|
||||
defer server.Close()
|
||||
|
@ -1216,6 +1223,7 @@ func updateLoginMetrics(user *dataprovider.User, ip, method string, err error) {
|
|||
metric.AddLoginAttempt(method)
|
||||
if err == nil {
|
||||
plugin.Handler.NotifyLogEvent(notifier.LogEventTypeLoginOK, common.ProtocolSSH, user.Username, ip, "", err)
|
||||
common.DelayLogin(nil)
|
||||
} else {
|
||||
logger.ConnectionFailedLog(user.Username, ip, method, common.ProtocolSSH, err.Error())
|
||||
if method != dataprovider.SSHLoginMethodPublicKey {
|
||||
|
@ -1230,6 +1238,9 @@ func updateLoginMetrics(user *dataprovider.User, ip, method string, err error) {
|
|||
}
|
||||
common.AddDefenderEvent(ip, common.ProtocolSSH, event)
|
||||
plugin.Handler.NotifyLogEvent(logEv, common.ProtocolSSH, user.Username, ip, "", err)
|
||||
if method != dataprovider.SSHLoginMethodPublicKey {
|
||||
common.DelayLogin(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
metric.AddLoginResult(method, err)
|
||||
|
|
|
@ -1196,11 +1196,8 @@ func TestProxyProtocol(t *testing.T) {
|
|||
defer client.Close()
|
||||
assert.NoError(t, checkBasicSFTP(client))
|
||||
}
|
||||
conn, client, err = getSftpClientWithAddr(user, usePubKey, "127.0.0.1:2224")
|
||||
if !assert.Error(t, err) {
|
||||
client.Close()
|
||||
conn.Close()
|
||||
}
|
||||
_, _, err = getSftpClientWithAddr(user, usePubKey, "127.0.0.1:2224")
|
||||
assert.Error(t, err)
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
|
@ -11788,7 +11785,7 @@ func printLatestLogs(maxNumberOfLines int) {
|
|||
return
|
||||
}
|
||||
for _, line := range lines {
|
||||
logger.DebugToConsole(line)
|
||||
logger.DebugToConsole("%s", line)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ func ServeSubSystemConnection(user *dataprovider.User, connectionID string, read
|
|||
FilePut: connection,
|
||||
FileCmd: connection,
|
||||
FileList: connection,
|
||||
}, sftp.WithRSAllocator())
|
||||
})
|
||||
|
||||
defer server.Close()
|
||||
return server.Serve()
|
||||
|
|
|
@ -324,7 +324,7 @@ func (c *Config) getMailClientOptions() []mail.Option {
|
|||
func (c *Config) getSMTPClientAndMsg(to, bcc []string, subject, body string, contentType EmailContentType,
|
||||
attachments ...*mail.File) (*mail.Client, *mail.Msg, error) {
|
||||
msg := mail.NewMsg()
|
||||
msg.SetUserAgent(version.GetServerVersion(" ", true))
|
||||
msg.SetUserAgent(version.GetServerVersion(" ", false))
|
||||
|
||||
var from string
|
||||
if c.From != "" {
|
||||
|
@ -346,7 +346,7 @@ func (c *Config) getSMTPClientAndMsg(to, bcc []string, subject, body string, con
|
|||
msg.Subject(subject)
|
||||
msg.SetDate()
|
||||
msg.SetMessageID()
|
||||
msg.SetAttachements(attachments)
|
||||
msg.SetAttachments(attachments)
|
||||
|
||||
switch contentType {
|
||||
case EmailContentTypeTextPlain:
|
||||
|
|
|
@ -59,9 +59,9 @@ func (e *ValidationError) Is(target error) bool {
|
|||
}
|
||||
|
||||
// NewValidationError returns a validation errors
|
||||
func NewValidationError(error string) *ValidationError {
|
||||
func NewValidationError(errorString string) *ValidationError {
|
||||
return &ValidationError{
|
||||
err: error,
|
||||
err: errorString,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,9 +81,9 @@ func (e *RecordNotFoundError) Is(target error) bool {
|
|||
}
|
||||
|
||||
// NewRecordNotFoundError returns a not found error
|
||||
func NewRecordNotFoundError(error string) *RecordNotFoundError {
|
||||
func NewRecordNotFoundError(errorString string) *RecordNotFoundError {
|
||||
return &RecordNotFoundError{
|
||||
err: error,
|
||||
err: errorString,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,9 +106,9 @@ func (e *MethodDisabledError) Is(target error) bool {
|
|||
}
|
||||
|
||||
// NewMethodDisabledError returns a method disabled error
|
||||
func NewMethodDisabledError(error string) *MethodDisabledError {
|
||||
func NewMethodDisabledError(errorString string) *MethodDisabledError {
|
||||
return &MethodDisabledError{
|
||||
err: error,
|
||||
err: errorString,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,8 +128,8 @@ func (e *GenericError) Is(target error) bool {
|
|||
}
|
||||
|
||||
// NewGenericError returns a generic error
|
||||
func NewGenericError(error string) *GenericError {
|
||||
func NewGenericError(errorString string) *GenericError {
|
||||
return &GenericError{
|
||||
err: error,
|
||||
err: errorString,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,6 +121,7 @@ const (
|
|||
I18nErrorPubKeyInvalid = "user.pub_key_invalid"
|
||||
I18nErrorPrivKeyInvalid = "user.priv_key_invalid"
|
||||
I18nErrorKeySizeInvalid = "user.key_invalid_size"
|
||||
I18nErrorKeyInsecure = "user.key_insecure"
|
||||
I18nErrorPrimaryGroup = "user.err_primary_group"
|
||||
I18nErrorDuplicateGroup = "user.err_duplicate_group"
|
||||
I18nErrorNoPermission = "user.no_permissions"
|
||||
|
@ -274,6 +275,7 @@ const (
|
|||
I18nActionTypeUserInactivityCheck = "actions.types.user_inactivity_check"
|
||||
I18nActionTypeIDPCheck = "actions.types.idp_check"
|
||||
I18nActionTypeCommand = "actions.types.command"
|
||||
I18nActionTypeRotateLogs = "actions.types.rotate_logs"
|
||||
I18nActionFsTypeRename = "actions.fs_types.rename"
|
||||
I18nActionFsTypeDelete = "actions.fs_types.delete"
|
||||
I18nActionFsTypePathExists = "actions.fs_types.path_exists"
|
||||
|
|
|
@ -22,8 +22,10 @@ import (
|
|||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
|
@ -40,6 +42,7 @@ import (
|
|||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -48,7 +51,6 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
"github.com/rs/xid"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/logger"
|
||||
|
@ -138,18 +140,6 @@ func Contains[T comparable](elems []T, v T) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// Remove removes an element from a string slice and
|
||||
// returns the modified slice
|
||||
func Remove(elems []string, val string) []string {
|
||||
for idx, v := range elems {
|
||||
if v == val {
|
||||
elems[idx] = elems[len(elems)-1]
|
||||
return elems[:len(elems)-1]
|
||||
}
|
||||
}
|
||||
return elems
|
||||
}
|
||||
|
||||
// IsStringPrefixInSlice searches a string prefix in a slice and returns true
|
||||
// if a matching prefix is found
|
||||
func IsStringPrefixInSlice(obj string, list []string) bool {
|
||||
|
@ -380,7 +370,7 @@ func GenerateRSAKeys(file string) error {
|
|||
if err := createDirPathIfMissing(file, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
key, err := rsa.GenerateKey(rand.Reader, 3072)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -572,27 +562,27 @@ func createDirPathIfMissing(file string, perm os.FileMode) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GenerateRandomBytes generates the secret to use for JWT auth
|
||||
// GenerateRandomBytes generates random bytes with the specified length
|
||||
func GenerateRandomBytes(length int) []byte {
|
||||
b := make([]byte, length)
|
||||
_, err := io.ReadFull(rand.Reader, b)
|
||||
if err == nil {
|
||||
return b
|
||||
if err != nil {
|
||||
PanicOnError(fmt.Errorf("failed to read random data (see https://go.dev/issue/66821): %w", err))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
b = xid.New().Bytes()
|
||||
for len(b) < length {
|
||||
b = append(b, xid.New().Bytes()...)
|
||||
}
|
||||
|
||||
return b[:length]
|
||||
// GenerateOpaqueString generates a cryptographically secure opaque string
|
||||
func GenerateOpaqueString() string {
|
||||
randomBytes := sha256.Sum256(GenerateRandomBytes(32))
|
||||
return hex.EncodeToString(randomBytes[:])
|
||||
}
|
||||
|
||||
// GenerateUniqueID retuens an unique ID
|
||||
func GenerateUniqueID() string {
|
||||
u, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return xid.New().String()
|
||||
PanicOnError(fmt.Errorf("failed to read random data (see https://go.dev/issue/66821): %w", err))
|
||||
}
|
||||
return shortuuid.DefaultEncoder.Encode(u)
|
||||
}
|
||||
|
@ -809,15 +799,6 @@ func GetRedactedURL(rawurl string) string {
|
|||
return u.Redacted()
|
||||
}
|
||||
|
||||
// PrependFileInfo prepends a file info to a slice in an efficient way.
|
||||
// We, optimistically, assume that the slice has enough capacity
|
||||
func PrependFileInfo(files []os.FileInfo, info os.FileInfo) []os.FileInfo {
|
||||
files = append(files, nil)
|
||||
copy(files[1:], files)
|
||||
files[0] = info
|
||||
return files
|
||||
}
|
||||
|
||||
// GetTLSVersion returns the TLS version for integer:
|
||||
// - 12 means TLS 1.2
|
||||
// - 13 means TLS 1.3
|
||||
|
@ -928,3 +909,18 @@ func ReadConfigFromFile(name, configDir string) (string, error) {
|
|||
}
|
||||
return strings.TrimSpace(BytesToString(val)), nil
|
||||
}
|
||||
|
||||
// SlicesEqual checks if the provided slices contain the same elements,
|
||||
// also in different order.
|
||||
func SlicesEqual(s1, s2 []string) bool {
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
for _, v := range s1 {
|
||||
if !slices.Contains(s2, v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ package version
|
|||
import "strings"
|
||||
|
||||
const (
|
||||
version = "2.6.0"
|
||||
version = "2.6.4"
|
||||
appName = "SFTPGo"
|
||||
)
|
||||
|
||||
|
|
|
@ -394,7 +394,17 @@ func (fs *AzureBlobFs) Chtimes(name string, _, mtime time.Time, isUploading bool
|
|||
if metadata == nil {
|
||||
metadata = make(map[string]*string)
|
||||
}
|
||||
metadata[lastModifiedField] = to.Ptr(strconv.FormatInt(mtime.UnixMilli(), 10))
|
||||
found := false
|
||||
for k := range metadata {
|
||||
if strings.ToLower(k) == lastModifiedField {
|
||||
metadata[k] = to.Ptr(strconv.FormatInt(mtime.UnixMilli(), 10))
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
metadata[lastModifiedField] = to.Ptr(strconv.FormatInt(mtime.UnixMilli(), 10))
|
||||
}
|
||||
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
|
||||
defer cancelFn()
|
||||
|
|
|
@ -18,9 +18,10 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
|
@ -277,8 +278,8 @@ func (c *SFTPFsConfig) ValidateAndEncryptCredentials(additionalData string) erro
|
|||
}
|
||||
|
||||
// getUniqueID returns an hash of the settings used to connect to the SFTP server
|
||||
func (c *SFTPFsConfig) getUniqueID(partition int) uint64 {
|
||||
h := fnv.New64a()
|
||||
func (c *SFTPFsConfig) getUniqueID(partition int) string {
|
||||
h := sha256.New()
|
||||
var b bytes.Buffer
|
||||
|
||||
b.WriteString(c.Endpoint)
|
||||
|
@ -295,7 +296,7 @@ func (c *SFTPFsConfig) getUniqueID(partition int) uint64 {
|
|||
b.WriteString(strconv.Itoa(partition))
|
||||
|
||||
h.Write(b.Bytes())
|
||||
return h.Sum64()
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// SFTPFs is a Fs implementation for SFTP backends
|
||||
|
@ -1142,13 +1143,13 @@ func (c *sftpConnection) GetLastActivity() time.Time {
|
|||
type sftpConnectionsCache struct {
|
||||
scheduler *cron.Cron
|
||||
sync.RWMutex
|
||||
items map[uint64]*sftpConnection
|
||||
items map[string]*sftpConnection
|
||||
}
|
||||
|
||||
func newSFTPConnectionCache() *sftpConnectionsCache {
|
||||
c := &sftpConnectionsCache{
|
||||
scheduler: cron.New(cron.WithLocation(time.UTC), cron.WithLogger(cron.DiscardLogger)),
|
||||
items: make(map[uint64]*sftpConnection),
|
||||
items: make(map[string]*sftpConnection),
|
||||
}
|
||||
_, err := c.scheduler.AddFunc("@every 1m", c.Cleanup)
|
||||
util.PanicOnError(err)
|
||||
|
@ -1163,13 +1164,13 @@ func (c *sftpConnectionsCache) Get(config *SFTPFsConfig, sessionID string) *sftp
|
|||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
var oldKey uint64
|
||||
var oldKey string
|
||||
for {
|
||||
if val, ok := c.items[key]; ok {
|
||||
activeSessions := val.ActiveSessions()
|
||||
if activeSessions < maxSessionsPerConnection || key == oldKey {
|
||||
logger.Debug(logSenderSFTPCache, "",
|
||||
"reusing connection for session ID %q, key: %d, active sessions %d, active connections: %d",
|
||||
"reusing connection for session ID %q, key %s, active sessions %d, active connections: %d",
|
||||
sessionID, key, activeSessions+1, len(c.items))
|
||||
val.AddSession(sessionID)
|
||||
return val
|
||||
|
@ -1178,26 +1179,26 @@ func (c *sftpConnectionsCache) Get(config *SFTPFsConfig, sessionID string) *sftp
|
|||
oldKey = key
|
||||
key = config.getUniqueID(partition)
|
||||
logger.Debug(logSenderSFTPCache, "",
|
||||
"connection full, generated new key for partition: %d, active sessions: %d, key: %d, old key: %d",
|
||||
"connection full, generated new key for partition: %d, active sessions: %d, key: %s, old key: %s",
|
||||
partition, activeSessions, oldKey, key)
|
||||
} else {
|
||||
conn := newSFTPConnection(config, sessionID)
|
||||
c.items[key] = conn
|
||||
logger.Debug(logSenderSFTPCache, "",
|
||||
"adding new connection for session ID %q, partition: %d, key: %d, active connections: %d",
|
||||
"adding new connection for session ID %q, partition: %d, key: %s, active connections: %d",
|
||||
sessionID, partition, key, len(c.items))
|
||||
return conn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sftpConnectionsCache) Remove(key uint64) {
|
||||
func (c *sftpConnectionsCache) Remove(key string) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if conn, ok := c.items[key]; ok {
|
||||
delete(c.items, key)
|
||||
logger.Debug(logSenderSFTPCache, "", "removed connection with key %d, active connections: %d", key, len(c.items))
|
||||
logger.Debug(logSenderSFTPCache, "", "removed connection with key %s, active connections: %d", key, len(c.items))
|
||||
|
||||
defer conn.Close()
|
||||
}
|
||||
|
@ -1210,7 +1211,7 @@ func (c *sftpConnectionsCache) Cleanup() {
|
|||
if val := conn.GetLastActivity(); val.Before(time.Now().Add(-30 * time.Second)) {
|
||||
logger.Debug(conn.logSender, "", "removing inactive connection, last activity %s", val)
|
||||
|
||||
defer func(key uint64) {
|
||||
defer func(key string) {
|
||||
c.Remove(key)
|
||||
}(k)
|
||||
}
|
||||
|
|
|
@ -426,6 +426,7 @@ func updateLoginMetrics(user *dataprovider.User, ip, loginMethod string, err err
|
|||
metric.AddLoginAttempt(loginMethod)
|
||||
if err == nil {
|
||||
plugin.Handler.NotifyLogEvent(notifier.LogEventTypeLoginOK, common.ProtocolWebDAV, user.Username, ip, "", nil)
|
||||
common.DelayLogin(nil)
|
||||
} else if err != common.ErrInternalFailure && err != common.ErrNoCredentials {
|
||||
logger.ConnectionFailedLog(user.Username, ip, loginMethod, common.ProtocolWebDAV, err.Error())
|
||||
event := common.HostEventLoginFailed
|
||||
|
@ -436,6 +437,9 @@ func updateLoginMetrics(user *dataprovider.User, ip, loginMethod string, err err
|
|||
}
|
||||
common.AddDefenderEvent(ip, common.ProtocolWebDAV, event)
|
||||
plugin.Handler.NotifyLogEvent(logEv, common.ProtocolWebDAV, user.Username, ip, "", err)
|
||||
if loginMethod != dataprovider.LoginMethodTLSCertificate {
|
||||
common.DelayLogin(err)
|
||||
}
|
||||
}
|
||||
metric.AddLoginResult(loginMethod, err)
|
||||
dataprovider.ExecutePostLoginHook(user, loginMethod, ip, common.ProtocolWebDAV, err)
|
||||
|
|
|
@ -667,6 +667,8 @@ func TestBasicHandlingCryptFs(t *testing.T) {
|
|||
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
|
||||
err = downloadFile(testFileName, localDownloadPath, testFileSize, client)
|
||||
assert.NoError(t, err)
|
||||
assert.Eventually(t, func() bool { return len(common.Connections.GetStats("")) == 0 },
|
||||
1*time.Second, 100*time.Millisecond)
|
||||
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
|
||||
|
@ -3545,6 +3547,6 @@ func printLatestLogs(maxNumberOfLines int) {
|
|||
return
|
||||
}
|
||||
for _, line := range lines {
|
||||
logger.DebugToConsole(line)
|
||||
logger.DebugToConsole("%s", line)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ info:
|
|||
SFTPGo supports groups to simplify the administration of multiple accounts by letting you assign settings once to a group, instead of multiple times to each individual user.
|
||||
The SFTPGo WebClient allows end users to change their credentials, browse and manage their files in the browser and setup two-factor authentication which works with Authy, Google Authenticator and other compatible apps.
|
||||
From the WebClient each authorized user can also create HTTP/S links to externally share files and folders securely, by setting limits to the number of downloads/uploads, protecting the share with a password, limiting access by source IP address, setting an automatic expiration date.
|
||||
version: 2.6.0
|
||||
version: 2.6.4
|
||||
contact:
|
||||
name: API support
|
||||
url: 'https://github.com/drakkan/sftpgo'
|
||||
|
@ -1517,7 +1517,7 @@ paths:
|
|||
name: confidential_data
|
||||
schema:
|
||||
type: integer
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the manage_system permission is not granted.'
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the * permission is not granted.'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
|
@ -1565,7 +1565,7 @@ paths:
|
|||
name: confidential_data
|
||||
schema:
|
||||
type: integer
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the manage_system permission is not granted.'
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the * permission is not granted.'
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
|
@ -1709,7 +1709,7 @@ paths:
|
|||
name: confidential_data
|
||||
schema:
|
||||
type: integer
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the manage_system permission is not granted.'
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the * permission is not granted.'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
|
@ -1757,7 +1757,7 @@ paths:
|
|||
name: confidential_data
|
||||
schema:
|
||||
type: integer
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the manage_system permission is not granted.'
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the * permission is not granted.'
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
|
@ -2081,7 +2081,7 @@ paths:
|
|||
name: confidential_data
|
||||
schema:
|
||||
type: integer
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the manage_system permission is not granted.'
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the * permission is not granted.'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
|
@ -2129,7 +2129,7 @@ paths:
|
|||
name: confidential_data
|
||||
schema:
|
||||
type: integer
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the manage_system permission is not granted.'
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the * permission is not granted.'
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
|
@ -2273,7 +2273,7 @@ paths:
|
|||
name: confidential_data
|
||||
schema:
|
||||
type: integer
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the manage_system permission is not granted.'
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the * permission is not granted.'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
|
@ -2321,7 +2321,7 @@ paths:
|
|||
name: confidential_data
|
||||
schema:
|
||||
type: integer
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the manage_system permission is not granted.'
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the * permission is not granted.'
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
|
@ -3416,7 +3416,7 @@ paths:
|
|||
name: confidential_data
|
||||
schema:
|
||||
type: integer
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the hash of the password and the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the manage_system permission is not granted.'
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the hash of the password and the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the * permission is not granted.'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
|
@ -3464,7 +3464,7 @@ paths:
|
|||
name: confidential_data
|
||||
schema:
|
||||
type: integer
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the hash of the password and the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the manage_system permission is not granted.'
|
||||
description: 'If set to 1 confidential data will not be hidden. This means that the response will contain the hash of the password and the key and additional data for secrets. If a master key is not set or an external KMS is used, the data returned are enough to get the secrets in cleartext. Ignored if the * permission is not granted.'
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
|
@ -4935,23 +4935,16 @@ components:
|
|||
- view_conns
|
||||
- close_conns
|
||||
- view_status
|
||||
- manage_admins
|
||||
- manage_folders
|
||||
- manage_groups
|
||||
- manage_apikeys
|
||||
- quota_scans
|
||||
- manage_system
|
||||
- manage_defender
|
||||
- view_defender
|
||||
- retention_checks
|
||||
- view_events
|
||||
- manage_event_rules
|
||||
- manage_roles
|
||||
- manage_ip_lists
|
||||
- disable_mfa
|
||||
description: |
|
||||
Admin permissions:
|
||||
* `*` - all permissions are granted
|
||||
* `*` - super admin permissions are granted
|
||||
* `add_users` - add new users is allowed
|
||||
* `edit_users` - change existing users is allowed
|
||||
* `del_users` - remove users is allowed
|
||||
|
@ -4959,19 +4952,12 @@ components:
|
|||
* `view_conns` - list active connections is allowed
|
||||
* `close_conns` - close active connections is allowed
|
||||
* `view_status` - view the server status is allowed
|
||||
* `manage_admins` - manage other admins is allowed
|
||||
* `manage_folders` - manage folders is allowed
|
||||
* `manage_groups` - manage groups is allowed
|
||||
* `manage_apikeys` - manage API keys is allowed
|
||||
* `quota_scans` - view and start quota scans is allowed
|
||||
* `manage_system` - backups and restores are allowed
|
||||
* `manage_defender` - remove ip from the dynamic blocklist is allowed
|
||||
* `view_defender` - list the dynamic blocklist is allowed
|
||||
* `retention_checks` - view and start retention checks is allowed
|
||||
* `view_events` - view and search filesystem and provider events is allowed
|
||||
* `manage_event_rules` - manage event actions and rules is allowed
|
||||
* `manage_roles` - manage roles is allowed
|
||||
* `manage_ip_lists` - manage global and ratelimter allow lists and defender block and safe lists is allowed
|
||||
* `disable_mfa` - allow to disable two-factor authentication for users and admins
|
||||
FsProviders:
|
||||
type: integer
|
||||
|
@ -5008,6 +4994,7 @@ components:
|
|||
- 12
|
||||
- 13
|
||||
- 14
|
||||
- 15
|
||||
description: |
|
||||
Supported event action types:
|
||||
* `1` - HTTP
|
||||
|
@ -5023,6 +5010,7 @@ components:
|
|||
* `12` - User expiration check
|
||||
* `13` - Identity Provider account check
|
||||
* `14` - User inactivity check
|
||||
* `15` - Rotate log file
|
||||
FilesystemActionTypes:
|
||||
type: integer
|
||||
enum:
|
||||
|
@ -5517,6 +5505,9 @@ components:
|
|||
password_expiration:
|
||||
type: integer
|
||||
description: 'The password expires after the defined number of days. 0 means no expiration'
|
||||
password_strength:
|
||||
type: integer
|
||||
description: 'Defines the minimum password strength. 0 means disabled, any password will be accepted. Values in the 50-70 range are suggested for common use cases'
|
||||
access_time:
|
||||
type: array
|
||||
items:
|
||||
|
@ -6106,7 +6097,7 @@ components:
|
|||
description: Last user login as unix timestamp in milliseconds. It is saved at most once every 10 minutes
|
||||
role:
|
||||
type: string
|
||||
description: 'If set the admin can only administer users with the same role. Role admins cannot have the following permissions: "manage_admins", "manage_apikeys", "manage_system", "manage_event_rules", "manage_roles", "manage_ip_lists"'
|
||||
description: 'If set the admin can only administer users with the same role. Role admins cannot have the "*" permission'
|
||||
AdminProfile:
|
||||
type: object
|
||||
properties:
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
NFPM_VERSION=2.37.1
|
||||
NFPM_VERSION=2.41.1
|
||||
NFPM_ARCH=${NFPM_ARCH:-amd64}
|
||||
if [ -z ${SFTPGO_VERSION} ]
|
||||
then
|
||||
|
@ -83,6 +83,12 @@ contents:
|
|||
- src: "${BASE_DIR}/openapi/*"
|
||||
dst: "/usr/share/sftpgo/openapi"
|
||||
|
||||
- src: "${BASE_DIR}/LICENSE"
|
||||
dst: "/usr/share/licenses/sftpgo/LICENSE"
|
||||
|
||||
- src: "${BASE_DIR}/NOTICE"
|
||||
dst: "/usr/share/licenses/sftpgo/NOTICE"
|
||||
|
||||
- src: "./sftpgo.json"
|
||||
dst: "/etc/sftpgo/sftpgo.json"
|
||||
type: "config|noreplace"
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>sftpgo</id>
|
||||
<version>2.5.6</version>
|
||||
<version>2.6.2</version>
|
||||
<packageSourceUrl>https://github.com/drakkan/sftpgo/tree/main/pkgs/choco</packageSourceUrl>
|
||||
<owners>asheroto</owners>
|
||||
<title>SFTPGo</title>
|
||||
<authors>Nicola Murino</authors>
|
||||
<projectUrl>https://github.com/drakkan/sftpgo</projectUrl>
|
||||
<iconUrl>https://cdn.statically.io/gh/drakkan/sftpgo/v2.5.6/static/img/logo.png</iconUrl>
|
||||
<iconUrl>https://cdn.statically.io/gh/drakkan/sftpgo/v2.6.2/static/img/logo.png</iconUrl>
|
||||
<licenseUrl>https://github.com/drakkan/sftpgo/blob/main/LICENSE</licenseUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<projectSourceUrl>https://github.com/drakkan/sftpgo</projectSourceUrl>
|
||||
<docsUrl>https://github.com/drakkan/sftpgo/tree/v2.5.6/docs</docsUrl>
|
||||
<docsUrl>https://docs.sftpgo.com/2.6/</docsUrl>
|
||||
<bugTrackerUrl>https://github.com/drakkan/sftpgo/issues</bugTrackerUrl>
|
||||
<tags>sftp sftp-server ftp webdav s3 azure-blob google-cloud-storage cloud-storage scp data-at-rest-encryption multi-factor-authentication multi-step-authentication</tags>
|
||||
<summary>Full-featured and highly configurable file transfer server: SFTP, HTTP/S,FTP/S, WebDAV.</summary>
|
||||
|
@ -26,13 +26,13 @@ SFTPGo allows to create HTTP/S links to externally share files and folders secur
|
|||
|
||||
SFTPGo is highly customizable and extensible to suit your needs.
|
||||
|
||||
You can find more info [here](https://github.com/drakkan/sftpgo).
|
||||
You can find more info [here](https://docs.sftpgo.com/2.6/).
|
||||
|
||||
### Notes
|
||||
|
||||
* This package installs SFTPGo as Windows Service.
|
||||
* After the first installation please take a look at the [Getting Started Guide](https://sftpgo.github.io/latest/initial-configuration/).</description>
|
||||
<releaseNotes>https://github.com/drakkan/sftpgo/releases/tag/v2.5.6</releaseNotes>
|
||||
* After the first installation please take a look at the [Getting Started Guide](https://docs.sftpgo.com/2.6/initial-configuration/).</description>
|
||||
<releaseNotes>https://github.com/drakkan/sftpgo/releases/tag/v2.6.2</releaseNotes>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="**" exclude="**\*.md;**\icon.png;**\icon.jpg;**\icon.svg" />
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
$ErrorActionPreference = 'Stop'
|
||||
$packageName = 'sftpgo'
|
||||
$softwareName = 'SFTPGo'
|
||||
$url = 'https://github.com/drakkan/sftpgo/releases/download/v2.5.6/sftpgo_v2.5.6_windows_x86_64.exe'
|
||||
$checksum = 'C20BB051D3EA2ACBF05231ECB94410D01648E15DE304CA6240D37FDC34007DBE'
|
||||
$url = 'https://github.com/drakkan/sftpgo/releases/download/v2.6.2/sftpgo_v2.6.2_windows_x86_64.exe'
|
||||
$checksum = '40905AED44A7189C5DF6164631DB07BCBB53FAB7A63503A6BE7AD1328D9986D5'
|
||||
$silentArgs = '/VERYSILENT'
|
||||
$validExitCodes = @(0)
|
||||
|
||||
|
@ -47,6 +47,8 @@ Write-Output ""
|
|||
Write-Output "General information (README) location:"
|
||||
Write-Output "`thttps://github.com/drakkan/sftpgo"
|
||||
Write-Output "Documentation location:"
|
||||
Write-Output "`thttps://sftpgo.github.io/"
|
||||
Write-Output "`thttps://docs.sftpgo.com/"
|
||||
Write-Output "Commercial support:"
|
||||
Write-Output "`thttps://sftpgo.com/"
|
||||
Write-Output ""
|
||||
Write-Output "---------------------------"
|
|
@ -3,8 +3,8 @@ Upstream-Name: SFTPGo
|
|||
Source: https://github.com/drakkan/sftpgo
|
||||
|
||||
Files: *
|
||||
Copyright: 2019-2023 Nicola Murino <nicola.murino@gmail.com>
|
||||
License: AGPL-3
|
||||
Copyright: 2019 Nicola Murino <nicola.murino@gmail.com>
|
||||
License: AGPL-3 with additional terms
|
||||
|
||||
License: AGPL-3
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
|
@ -668,3 +668,18 @@ specific requirements.
|
|||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
ADDITIONAL TERMS
|
||||
|
||||
Additional terms under GNU AGPL version 3 section 7.3(b) and 13.1:
|
||||
|
||||
If you have included SFTPGo so that it is offered through any network
|
||||
interactions, including by means of an external user interface, or
|
||||
any other integration, even without modifying its source code and then
|
||||
SFTPGo is partially, fully or optionally configured via your frontend,
|
||||
you must provide reasonable but clear attribution to the SFTPGo project
|
||||
and its author(s), not imply any endorsement by or affiliation with the
|
||||
SFTPGo project, and you must prominently offer all users interacting
|
||||
with it remotely through a computer network an opportunity to receive
|
||||
the Corresponding Source of the SFTPGo version you include by providing
|
||||
a link to the Corresponding Source in the SFTPGo source code repository.
|
||||
|
|
18
sftpgo.json
18
sftpgo.json
|
@ -39,7 +39,11 @@
|
|||
"score_no_auth": 0,
|
||||
"observation_time": 30,
|
||||
"entries_soft_limit": 100,
|
||||
"entries_hard_limit": 150
|
||||
"entries_hard_limit": 150,
|
||||
"login_delay": {
|
||||
"success": 0,
|
||||
"password_failed": 1000
|
||||
}
|
||||
},
|
||||
"rate_limiters": [
|
||||
{
|
||||
|
@ -57,7 +61,10 @@
|
|||
"entries_soft_limit": 100,
|
||||
"entries_hard_limit": 150
|
||||
}
|
||||
]
|
||||
],
|
||||
"event_manager": {
|
||||
"enabled_commands": []
|
||||
}
|
||||
},
|
||||
"acme": {
|
||||
"domains": [],
|
||||
|
@ -87,8 +94,8 @@
|
|||
"host_keys": [],
|
||||
"host_certificates": [],
|
||||
"host_key_algorithms": [],
|
||||
"moduli": [],
|
||||
"kex_algorithms": [],
|
||||
"min_dh_group_exchange_key_size": 2048,
|
||||
"ciphers": [],
|
||||
"macs": [],
|
||||
"public_key_algorithms": [],
|
||||
|
@ -307,7 +314,8 @@
|
|||
"content_type_nosniff": false,
|
||||
"content_security_policy": "",
|
||||
"permissions_policy": "",
|
||||
"cross_origin_opener_policy": ""
|
||||
"cross_origin_opener_policy": "",
|
||||
"cache_control": ""
|
||||
},
|
||||
"branding": {
|
||||
"web_admin": {
|
||||
|
@ -315,7 +323,6 @@
|
|||
"short_name": "",
|
||||
"favicon_path": "",
|
||||
"logo_path": "",
|
||||
"login_image_path": "",
|
||||
"disclaimer_name": "",
|
||||
"disclaimer_path": "",
|
||||
"default_css": [],
|
||||
|
@ -326,7 +333,6 @@
|
|||
"short_name": "",
|
||||
"favicon_path": "",
|
||||
"logo_path": "",
|
||||
"login_image_path": "",
|
||||
"disclaimer_name": "",
|
||||
"disclaimer_path": "",
|
||||
"default_css": [],
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue