diff --git a/.cirrus.yml b/.cirrus.yml
index d26a8bd9..7c45f1fc 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -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'
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 8cd157ff..0c90b458 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -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: "/"
diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml
index 1349f87e..4b73b415 100644
--- a/.github/workflows/development.yml
+++ b/.github/workflows/development.yml
@@ -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
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 98617201..33782ead 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -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
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a752efed..9d304a64 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -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:
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 00000000..f683d86f
--- /dev/null
+++ b/NOTICE
@@ -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.
diff --git a/README.md b/README.md
index ef80d315..7971c843 100644
--- a/README.md
+++ b/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
[
](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:
diff --git a/examples/backup/README.md b/examples/backup/README.md
index d00ce1d8..3190390c 100644
--- a/examples/backup/README.md
+++ b/examples/backup/README.md
@@ -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.
diff --git a/examples/data-retention/README.md b/examples/data-retention/README.md
index 6508c295..ca1c7679 100644
--- a/examples/data-retention/README.md
+++ b/examples/data-retention/README.md
@@ -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.
diff --git a/examples/quotascan/README.md b/examples/quotascan/README.md
index 0e7c0a04..0830a308 100644
--- a/examples/quotascan/README.md
+++ b/examples/quotascan/README.md
@@ -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.
diff --git a/go.mod b/go.mod
index 667e32ed..cf10d58f 100644
--- a/go.mod
+++ b/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.47.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.5
+ github.com/aws/aws-sdk-go-v2/config v1.28.5
+ github.com/aws/aws-sdk-go-v2/credentials v1.17.46
+ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20
+ github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.41
+ github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.25.6
+ github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0
+ github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6
+ github.com/aws/aws-sdk-go-v2/service/sts v1.33.1
+ 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.24.1
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-acme/lego/v4 v4.20.4
+ github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/jwtauth/v5 v5.3.1
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/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.9.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.29.0
+ golang.org/x/net v0.31.0
+ golang.org/x/oauth2 v0.24.0
+ golang.org/x/sys v0.27.0
+ golang.org/x/term v0.26.0
+ golang.org/x/time v0.8.0
+ google.golang.org/api v0.209.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.18.0 // indirect
+ cloud.google.com/go v0.116.0 // indirect
+ cloud.google.com/go/auth v0.11.0 // indirect
+ cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
+ cloud.google.com/go/compute/metadata v0.5.2 // indirect
+ cloud.google.com/go/iam v1.2.2 // indirect
+ cloud.google.com/go/monitoring v1.21.2 // 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.24 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 // 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.24 // 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.5 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect
+ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // 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-20240905190251-b4127c9b8d78 // 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.5 // 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/goccy/go-json v0.10.3 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // 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.0 // 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/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
github.com/magiconair/properties v1.8.7 // 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.60.1 // 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.0 // 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/contrib/detectors/gcp v1.32.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect
+ go.opentelemetry.io/otel v1.32.0 // indirect
+ go.opentelemetry.io/otel/metric v1.32.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.32.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect
+ go.opentelemetry.io/otel/trace v1.32.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-20241108190413-2d47ceb2692f // indirect
+ golang.org/x/mod v0.22.0 // indirect
+ golang.org/x/sync v0.9.0 // indirect
+ golang.org/x/text v0.20.0 // indirect
+ golang.org/x/tools v0.27.0 // indirect
+ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
+ google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect
+ google.golang.org/grpc v1.68.0 // indirect
+ google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 // indirect
+ google.golang.org/protobuf v1.35.2 // 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-20241109174329-ca456f56018f
)
diff --git a/go.sum b/go.sum
index 7e7a0ad5..a5991389 100644
--- a/go.sum
+++ b/go.sum
@@ -1,116 +1,131 @@
+cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo=
+cel.dev/expr v0.18.0/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.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
+cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
+cloud.google.com/go/auth v0.11.0 h1:Ic5SZz2lsvbYcWT5dfjNWgw6tTlGi2Wc8hyQSC9BstA=
+cloud.google.com/go/auth v0.11.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI=
+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.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
+cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
+cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA=
+cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY=
+cloud.google.com/go/kms v1.20.1 h1:og29Wv59uf2FVaZlesaiDAqHFzHaoUyHI3HYp9VUHVg=
+cloud.google.com/go/kms v1.20.1/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.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc=
+cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI=
+cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU=
+cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU=
+cloud.google.com/go/storage v1.47.0 h1:ajqgt30fnOMmLfWfu1PWcb+V9Dxz6n+9WKjdNg5R4HM=
+cloud.google.com/go/storage v1.47.0/go.mod h1:Ks0vP374w0PW6jOUameJbapbQKXqkjGd/OJRp2fb9IQ=
+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.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo=
+github.com/aws/aws-sdk-go-v2 v1.32.5/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.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0=
+github.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.41 h1:hqcxMc2g/MwwnRMod9n6Bd+t+9Nf7d5qRg7RaXKPd6o=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.41/go.mod h1:d1eH0VrttvPmrCraU68LOyNdu26zFxQFjrVSb5vdhog=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 h1:4usbeaes3yJnCFC7kfeyhkdkPtoRYPa/hTmCqMpKpLI=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24/go.mod h1:5CI1JemjVwde8m2WG3cz23qHKPOxbpkq0HaoreEgLIY=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 h1:N1zsICrQglfzaBnrfM0Ys00860C+QFwu6u/5+LomP+o=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24/go.mod h1:dCn9HbJ8+K31i8IQ8EWmWj0EiIk0+vKiHNMxTTYveAg=
+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.24 h1:JX70yGKLj25+lMC5Yyh8wBtvB01GDilyRuJvXJ4piD0=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24/go.mod h1:+Ln60j9SUTD0LEwnhEB0Xhg61DHqplBrbZpLgyjoEHg=
+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.5 h1:gvZOjQKPxFXy1ft3QnEyXmT+IqneM9QAUWlM3r0mfqw=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5/go.mod h1:DLWnfvIcm9IET/mmjdxeXbBKmTCm0ZB8p1za9BVteM8=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 h1:wtpJ4zcwrSbwhECWQoI/g6WM9zqCcSpHDJIWSbMLOu4=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5/go.mod h1:qu/W9HXQbbQ4+1+JcZp0ZNPV31ym537ZJN+fiS7Ti8E=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 h1:P1doBzv5VEg1ONxnJss1Kh5ZG/ewoIE4MQtKKc6Crgg=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5/go.mod h1:NOP+euMW7W3Ukt28tAxPuoWao4rhhqJD3QEBk7oCg7w=
+github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.25.6 h1:3aPcXE6EUx7D+/mzEsp1vVBG+OVO4QsyTsyoLfAUzj4=
+github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.25.6/go.mod h1:capelnANRLuXXVcT3oPQvDhKDn6unq1Ve2k9b8M12/o=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0 h1:Q2ax8S21clKOnHhhr933xm3JxdJebql+R7aNo7p7GBQ=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0/go.mod h1:ralv4XawHjEMaHOWnTFushl0WRqim/gQWesAMF6hTow=
+github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6 h1:1KDMKvOKNrpD667ORbZ/+4OgvUoaok1gg/MLzrHF9fw=
+github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6/go.mod h1:DmtyfCfONhOyVAJ6ZMTrDSFIeyCBlEO93Qkfhxwbxu0=
+github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM=
+github.com/aws/aws-sdk-go-v2/service/sso v1.24.6/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc=
+github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 h1:6SZUVRQNvExYlMLbHdlKB48x0fLbc2iVROyaNEwBHbU=
+github.com/aws/aws-sdk-go-v2/service/sts v1.33.1/go.mod h1:GqWyYCwLXnlUB1lOAXQyNSPqPLQJvmo8J0DWBzp9mtg=
+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-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI=
+github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/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.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
+github.com/cpuguy83/go-md2man/v2 v2.0.5/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=
@@ -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-20241109174329-ca456f56018f h1:XcWqEK+oBZv7o3+JdziHprvQwgGlr9rk7sgIYPwy/C8=
+github.com/drakkan/crypto v0.0.0-20241109174329-ca456f56018f/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
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/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.20.4 h1:yCQGBX9jOfMbriEQUocdYm7EBapdTp8nLXYG8k6SqSU=
+github.com/go-acme/lego/v4 v4.20.4/go.mod h1:foauPlhnhoq8WUphaWx5U04uDc+JGhk4ZZtPz/Vqsjg=
+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/jwtauth/v5 v5.3.1 h1:1ePWrjVctvp1tyBq5b/2ER8Th/+RbYc7x4qNsc5rh5A=
github.com/go-chi/jwtauth/v5 v5.3.1/go.mod h1:6Fl2RRmWXs3tJYE1IQGX81FsPoGqDwq9c15j52R5q80=
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,11 +187,11 @@ 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.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
+github.com/goccy/go-json v0.10.3/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=
@@ -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.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o=
+github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk=
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,21 +277,20 @@ 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/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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -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/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc=
+github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
+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.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/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.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.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,68 @@ 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/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.9.0 h1:1j1sc9gQnNxbXXM4M/CebPOX4aXYtr7MojAVcN4dHjU=
github.com/studio-b12/gowebdav v0.9.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/contrib/detectors/gcp v1.32.0 h1:P78qWqkLSShicHmAzfECaTgvslqHxblNE9j62Ws1NK8=
+go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 h1:qtFISDHKolvIxzSs0gIaiPUPR0Cucb0F2coHC7ZLdps=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0/go.mod h1:Y+Pop1Q6hCOnETWTW4NROK/q1hv50hM7yDaUTjG8lp8=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94=
+go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
+go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
+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.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
+go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
+go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
+go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
+go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
+go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
+go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
+go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
+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-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
+golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
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 +469,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.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
+golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
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.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
+golang.org/x/sync v0.9.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 +506,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.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
+golang.org/x/sys v0.27.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.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
+golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
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.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
+golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
+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 +539,36 @@ 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.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
+golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
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.209.0 h1:Ja2OXNlyRlWCWu8o+GgI4yUn/wz9h/5ZfFbKz+dQX+w=
+google.golang.org/api v0.209.0/go.mod h1:I53S168Yr/PNDNMi5yPnDc0/LGRZO6o7PoEbl/HY3CM=
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-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
+google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
+google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ=
+google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
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.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
+google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
+google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 h1:hUfOButuEtpc0UvYiaYRbNwxVYr0mQQOWq6X8beJ9Gc=
+google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3/go.mod h1:jzYlkSMbKypzuu6xoAEijsNVo9ZeDF1u/zCfFgsx7jg=
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.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
+google.golang.org/protobuf v1.35.2/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=
diff --git a/internal/acme/acme.go b/internal/acme/acme.go
index 409ef260..6ac395f0 100644
--- a/internal/acme/acme.go
+++ b/internal/acme/acme.go
@@ -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
diff --git a/internal/cmd/resetpwd.go b/internal/cmd/resetpwd.go
index b46740af..fec8c4ea 100644
--- a/internal/cmd/resetpwd.go
+++ b/internal/cmd/resetpwd.go
@@ -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)
diff --git a/internal/cmd/serve.go b/internal/cmd/serve.go
index 2bc9e004..519fa78f 100644
--- a/internal/cmd/serve.go
+++ b/internal/cmd/serve.go
@@ -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)
diff --git a/internal/cmd/start_windows.go b/internal/cmd/start_windows.go
index be34dec2..5e4dde64 100644
--- a/internal/cmd/start_windows.go
+++ b/internal/cmd/start_windows.go
@@ -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,
diff --git a/internal/cmd/startsubsys.go b/internal/cmd/startsubsys.go
index 322a62e2..6f7c5751 100644
--- a/internal/cmd/startsubsys.go
+++ b/internal/cmd/startsubsys.go
@@ -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 {
diff --git a/internal/common/actions.go b/internal/common/actions.go
index 446068c1..01206b93 100644
--- a/internal/common/actions.go
+++ b/internal/common/actions.go
@@ -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,
}
diff --git a/internal/common/actions_test.go b/internal/common/actions_test.go
index 28eea408..02666e6c 100644
--- a/internal/common/actions_test.go
+++ b/internal/common/actions_test.go
@@ -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)
diff --git a/internal/common/common.go b/internal/common/common.go
index e35316bc..9933f041 100644
--- a/internal/common/common.go
+++ b/internal/common/common.go
@@ -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
}
}
diff --git a/internal/common/common_test.go b/internal/common/common_test.go
index c7d4ba51..46f66aec 100644
--- a/internal/common/common_test.go
+++ b/internal/common/common_test.go
@@ -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) {
diff --git a/internal/common/connection.go b/internal/common/connection.go
index e7f9c9a8..db04ab67 100644
--- a/internal/common/connection.go
+++ b/internal/common/connection.go
@@ -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
}
@@ -1814,10 +1814,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 +1826,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 +1839,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 +1859,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 {
diff --git a/internal/common/connection_test.go b/internal/common/connection_test.go
index 8b9d44c2..5f3c26dc 100644
--- a/internal/common/connection_test.go
+++ b/internal/common/connection_test.go
@@ -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)
diff --git a/internal/common/dataretention.go b/internal/common/dataretention.go
index 4f0f9aca..d97369c2 100644
--- a/internal/common/dataretention.go
+++ b/internal/common/dataretention.go
@@ -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()
diff --git a/internal/common/defender.go b/internal/common/defender.go
index d3f38221..18ea0ea7 100644
--- a/internal/common/defender.go
+++ b/internal/common/defender.go
@@ -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
diff --git a/internal/common/defender_test.go b/internal/common/defender_test.go
index 9465a770..f7cb9ad4 100644
--- a/internal/common/defender_test.go
+++ b/internal/common/defender_test.go
@@ -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()
diff --git a/internal/common/defenderdb.go b/internal/common/defenderdb.go
index d60879b4..3f0a4849 100644
--- a/internal/common/defenderdb.go
+++ b/internal/common/defenderdb.go
@@ -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,
})
}
diff --git a/internal/common/defendermem.go b/internal/common/defendermem.go
index 6a908d04..b5642c4e 100644
--- a/internal/common/defendermem.go
+++ b/internal/common/defendermem.go
@@ -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 {
diff --git a/internal/common/eventmanager.go b/internal/common/eventmanager.go
index de9cbf64..fcb517ee 100644
--- a/internal/common/eventmanager.go
+++ b/internal/common/eventmanager.go
@@ -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
@@ -88,11 +90,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 +316,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
}
@@ -555,7 +561,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 +645,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 +784,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 +796,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 +1324,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 +1370,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 +1487,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 +1590,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 +1653,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 +1723,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 +1784,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 +1797,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 +1811,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 +1844,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 +1889,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 +1908,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 +2004,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 +2533,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 +2602,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 +2615,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 +2654,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)
}
diff --git a/internal/common/eventmanager_test.go b/internal/common/eventmanager_test.go
index 47ddd08c..44f401d4 100644
--- a/internal/common/eventmanager_test.go
+++ b/internal/common/eventmanager_test.go
@@ -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)
diff --git a/internal/common/protocol_test.go b/internal/common/protocol_test.go
index e8f590b2..7d9f13ff 100644
--- a/internal/common/protocol_test.go
+++ b/internal/common/protocol_test.go
@@ -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)
}
}
diff --git a/internal/common/transfer.go b/internal/common/transfer.go
index 9bf354fb..f4e78e30 100644
--- a/internal/common/transfer.go
+++ b/internal/common/transfer.go
@@ -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)
diff --git a/internal/config/config.go b/internal/config/config.go
index 97a0da7b..00f8e1ba 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -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
}
@@ -1533,6 +1551,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 +1669,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 +2013,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 +2034,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)
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 17fc4702..625bdcdc 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -1203,11 +1203,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 +1268,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 +1378,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)
diff --git a/internal/dataprovider/admin.go b/internal/dataprovider/admin.go
index b88cbe00..aca524f2 100644
--- a/internal/dataprovider/admin.go
+++ b/internal/dataprovider/admin.go
@@ -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,
- }),
)
}
}
diff --git a/internal/dataprovider/dataprovider.go b/internal/dataprovider/dataprovider.go
index 16d1dada..26cfc977 100644
--- a/internal/dataprovider/dataprovider.go
+++ b/internal/dataprovider/dataprovider.go
@@ -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:
@@ -4442,7 +4441,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 +4513,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 +4529,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 {
diff --git a/internal/dataprovider/eventrule.go b/internal/dataprovider/eventrule.go
index 2780860c..0c7d0e35 100644
--- a/internal/dataprovider/eventrule.go
+++ b/internal/dataprovider/eventrule.go
@@ -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
diff --git a/internal/dataprovider/node.go b/internal/dataprovider/node.go
index a1c8b2c4..5bdf0c25 100644
--- a/internal/dataprovider/node.go
+++ b/internal/dataprovider/node.go
@@ -99,7 +99,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)
diff --git a/internal/ftpd/ftpd_test.go b/internal/ftpd/ftpd_test.go
index d59d0c3d..ffc9f3f8 100644
--- a/internal/ftpd/ftpd_test.go
+++ b/internal/ftpd/ftpd_test.go
@@ -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)
diff --git a/internal/ftpd/internal_test.go b/internal/ftpd/internal_test.go
index 646968cc..6d9dd8f0 100644
--- a/internal/ftpd/internal_test.go
+++ b/internal/ftpd/internal_test.go
@@ -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{})
diff --git a/internal/ftpd/server.go b/internal/ftpd/server.go
index ecd1edb3..8baf631c 100644
--- a/internal/ftpd/server.go
+++ b/internal/ftpd/server.go
@@ -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)
diff --git a/internal/httpd/api_admin.go b/internal/httpd/api_admin.go
index b232362c..2a8c7ec6 100644
--- a/internal/httpd/api_admin.go
+++ b/internal/httpd/api_admin.go
@@ -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)
}
diff --git a/internal/httpd/api_http_user.go b/internal/httpd/api_http_user.go
index a65ddda5..26bc0063 100644
--- a/internal/httpd/api_http_user.go
+++ b/internal/httpd/api_http_user.go
@@ -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)
}
diff --git a/internal/httpd/api_shares.go b/internal/httpd/api_shares.go
index c45f6f1f..6704a515 100644
--- a/internal/httpd/api_shares.go
+++ b/internal/httpd/api_shares.go
@@ -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,
diff --git a/internal/httpd/api_utils.go b/internal/httpd/api_utils.go
index 78d7219f..fa4bac08 100644
--- a/internal/httpd/api_utils.go
+++ b/internal/httpd/api_utils.go
@@ -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"
diff --git a/internal/httpd/auth_utils.go b/internal/httpd/auth_utils.go
index 4650e2f6..3cfaeb53 100644
--- a/internal/httpd/auth_utils.go
+++ b/internal/httpd/auth_utils.go
@@ -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
diff --git a/internal/httpd/httpd.go b/internal/httpd/httpd.go
index 62a4cdfb..ae81b7af 100644
--- a/internal/httpd/httpd.go
+++ b/internal/httpd/httpd.go
@@ -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"`
}
diff --git a/internal/httpd/httpd_test.go b/internal/httpd/httpd_test.go
index c22d6075..8da9018b 100644
--- a/internal/httpd/httpd_test.go
+++ b/internal/httpd/httpd_test.go
@@ -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{
{
@@ -11372,11 +11416,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 +11449,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 +11605,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 +11640,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 +11682,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 +11697,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 +12466,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 +13069,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 +13663,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 +18921,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 +23146,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 +23333,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 +24082,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 +25229,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 +25280,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 +25426,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 +25438,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 +25489,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
diff --git a/internal/httpd/internal_test.go b/internal/httpd/internal_test.go
index 4bc2399f..782a5c84 100644
--- a/internal/httpd/internal_test.go
+++ b/internal/httpd/internal_test.go
@@ -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)
}
@@ -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")
diff --git a/internal/httpd/middleware.go b/internal/httpd/middleware.go
index 8c98f02b..2feea4ec 100644
--- a/internal/httpd/middleware.go
+++ b/internal/httpd/middleware.go
@@ -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)
+ })
+}
diff --git a/internal/httpd/oauth2.go b/internal/httpd/oauth2.go
index fa0c3c62..88a599d3 100644
--- a/internal/httpd/oauth2.go
+++ b/internal/httpd/oauth2.go
@@ -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,
diff --git a/internal/httpd/oidc.go b/internal/httpd/oidc.go
index b686caab..93068387 100644
--- a/internal/httpd/oidc.go
+++ b/internal/httpd/oidc.go
@@ -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)
diff --git a/internal/httpd/oidc_test.go b/internal/httpd/oidc_test.go
index be04d2ab..636c9cfa 100644
--- a/internal/httpd/oidc_test.go
+++ b/internal/httpd/oidc_test.go
@@ -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(),
diff --git a/internal/httpd/server.go b/internal/httpd/server.go
index 0584344f..d0f9073e 100644
--- a/internal/httpd/server.go
+++ b/internal/httpd/server.go
@@ -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
}
@@ -821,6 +821,7 @@ func (s *httpdServer) loginAdmin(
return
}
dataprovider.UpdateAdminLastLogin(admin)
+ common.DelayLogin(nil)
redirectURL := webUsersPath
if errorFunc == nil {
redirectURL = webAdminMFAPath
@@ -1000,6 +1001,7 @@ func (s *httpdServer) generateAndSendToken(w http.ResponseWriter, r *http.Reques
}
dataprovider.UpdateAdminLastLogin(&admin)
+ common.DelayLogin(nil)
render.JSON(w, r, resp)
}
@@ -1035,6 +1037,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 +1057,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 +1242,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 +1257,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 +1280,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 +1333,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 +1376,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 +1388,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 +1403,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 +1490,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 +1499,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 +1744,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 +1775,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 +1839,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)
})
})
diff --git a/internal/httpd/web.go b/internal/httpd/web.go
index d4bc68f5..35b2cd7b 100644
--- a/internal/httpd/web.go
+++ b/internal/httpd/web.go
@@ -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 {
diff --git a/internal/httpd/webadmin.go b/internal/httpd/webadmin.go
index f8ffa63f..c4158a6b 100644
--- a/internal/httpd/webadmin.go
+++ b/internal/httpd/webadmin.go
@@ -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
}
diff --git a/internal/httpdtest/httpdtest.go b/internal/httpdtest/httpdtest.go
index 382e2ca0..991e8774 100644
--- a/internal/httpdtest/httpdtest.go
+++ b/internal/httpdtest/httpdtest.go
@@ -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")
}
diff --git a/internal/kms/kms.go b/internal/kms/kms.go
index acd9f84e..cf6cf5ed 100644
--- a/internal/kms/kms.go
+++ b/internal/kms/kms.go
@@ -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)
)
diff --git a/internal/logger/hclog.go b/internal/logger/hclog.go
index 87216389..b05afe15 100644
--- a/internal/logger/hclog.go
+++ b/internal/logger/hclog.go
@@ -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:
diff --git a/internal/logger/lego.go b/internal/logger/lego.go
index 4827d022..0503e536 100644
--- a/internal/logger/lego.go
+++ b/internal/logger/lego.go
@@ -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
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
index a5191ee1..853c7f09 100644
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -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
}
diff --git a/internal/plugin/kms.go b/internal/plugin/kms.go
index 964e0cdb..2da9acf9 100644
--- a/internal/plugin/kms.go
+++ b/internal/plugin/kms.go
@@ -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
diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go
index 91f65a8b..d36f0926 100644
--- a/internal/plugin/plugin.go
+++ b/internal/plugin/plugin.go
@@ -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{
diff --git a/internal/service/service.go b/internal/service/service.go
index 62bc17df..3a8d5c81 100644
--- a/internal/service/service.go
+++ b/internal/service/service.go
@@ -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 {
diff --git a/internal/sftpd/handler.go b/internal/sftpd/handler.go
index c551da1b..310e908a 100644
--- a/internal/sftpd/handler.go
+++ b/internal/sftpd/handler.go
@@ -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))
diff --git a/internal/sftpd/server.go b/internal/sftpd/server.go
index 5b4455a3..fdad2d0f 100644
--- a/internal/sftpd/server.go
+++ b/internal/sftpd/server.go
@@ -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)
diff --git a/internal/sftpd/sftpd_test.go b/internal/sftpd/sftpd_test.go
index 59c2fdfc..f8fe779c 100644
--- a/internal/sftpd/sftpd_test.go
+++ b/internal/sftpd/sftpd_test.go
@@ -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)
}
}
diff --git a/internal/sftpd/subsystem.go b/internal/sftpd/subsystem.go
index cda0ecdc..b17bd62e 100644
--- a/internal/sftpd/subsystem.go
+++ b/internal/sftpd/subsystem.go
@@ -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()
diff --git a/internal/smtp/smtp.go b/internal/smtp/smtp.go
index 308721bb..893534de 100644
--- a/internal/smtp/smtp.go
+++ b/internal/smtp/smtp.go
@@ -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:
diff --git a/internal/util/errors.go b/internal/util/errors.go
index c2bdfbce..79e54265 100644
--- a/internal/util/errors.go
+++ b/internal/util/errors.go
@@ -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,
}
}
diff --git a/internal/util/i18n.go b/internal/util/i18n.go
index fd8dba70..56db5b60 100644
--- a/internal/util/i18n.go
+++ b/internal/util/i18n.go
@@ -274,6 +274,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"
diff --git a/internal/util/util.go b/internal/util/util.go
index 3014db55..fc61c00d 100644
--- a/internal/util/util.go
+++ b/internal/util/util.go
@@ -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
+}
diff --git a/internal/version/version.go b/internal/version/version.go
index d0c46d01..cd2e96f4 100644
--- a/internal/version/version.go
+++ b/internal/version/version.go
@@ -18,7 +18,7 @@ package version
import "strings"
const (
- version = "2.6.0"
+ version = "2.6.4"
appName = "SFTPGo"
)
diff --git a/internal/vfs/azblobfs.go b/internal/vfs/azblobfs.go
index 067d612c..3d4e92c1 100644
--- a/internal/vfs/azblobfs.go
+++ b/internal/vfs/azblobfs.go
@@ -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()
diff --git a/internal/webdavd/server.go b/internal/webdavd/server.go
index 88ba6a08..aad07040 100644
--- a/internal/webdavd/server.go
+++ b/internal/webdavd/server.go
@@ -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)
diff --git a/internal/webdavd/webdavd_test.go b/internal/webdavd/webdavd_test.go
index b6b2bbdb..2c09d516 100644
--- a/internal/webdavd/webdavd_test.go
+++ b/internal/webdavd/webdavd_test.go
@@ -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)
}
}
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 94c1fbb6..e8d19819 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -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:
diff --git a/openapi/swagger-ui/swagger-ui-bundle.js b/openapi/swagger-ui/swagger-ui-bundle.js
index 7c6f45b8..45262199 100644
--- a/openapi/swagger-ui/swagger-ui-bundle.js
+++ b/openapi/swagger-ui/swagger-ui-bundle.js
@@ -1,2 +1,2 @@
/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */
-!function webpackUniversalModuleDefinition(s,i){"object"==typeof exports&&"object"==typeof module?module.exports=i():"function"==typeof define&&define.amd?define([],i):"object"==typeof exports?exports.SwaggerUIBundle=i():s.SwaggerUIBundle=i()}(this,(()=>(()=>{var s,i,u={69119:(s,i)=>{"use strict";Object.defineProperty(i,"__esModule",{value:!0}),i.BLANK_URL=i.relativeFirstCharacters=i.urlSchemeRegex=i.ctrlCharactersRegex=i.htmlCtrlEntityRegex=i.htmlEntitiesRegex=i.invalidProtocolRegex=void 0,i.invalidProtocolRegex=/^([^\w]*)(javascript|data|vbscript)/im,i.htmlEntitiesRegex=/(\w+)(^\w|;)?/g,i.htmlCtrlEntityRegex=/&(newline|tab);/gi,i.ctrlCharactersRegex=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,i.urlSchemeRegex=/^.+(:|:)/gim,i.relativeFirstCharacters=[".","/"],i.BLANK_URL="about:blank"},16750:(s,i,u)=>{"use strict";i.J=void 0;var _=u(69119);i.J=function sanitizeUrl(s){if(!s)return _.BLANK_URL;var i,u,w=s;do{i=(w=(u=w,u.replace(_.ctrlCharactersRegex,"").replace(_.htmlEntitiesRegex,(function(s,i){return String.fromCharCode(i)}))).replace(_.htmlCtrlEntityRegex,"").replace(_.ctrlCharactersRegex,"").trim()).match(_.ctrlCharactersRegex)||w.match(_.htmlEntitiesRegex)||w.match(_.htmlCtrlEntityRegex)}while(i&&i.length>0);var x=w;if(!x)return _.BLANK_URL;if(function isRelativeUrlWithoutProtocol(s){return _.relativeFirstCharacters.indexOf(s[0])>-1}(x))return x;var j=x.match(_.urlSchemeRegex);if(!j)return x;var B=j[0];return _.invalidProtocolRegex.test(B)?_.BLANK_URL:x}},67526:(s,i)=>{"use strict";i.byteLength=function byteLength(s){var i=getLens(s),u=i[0],_=i[1];return 3*(u+_)/4-_},i.toByteArray=function toByteArray(s){var i,u,x=getLens(s),j=x[0],B=x[1],L=new w(function _byteLength(s,i,u){return 3*(i+u)/4-u}(0,j,B)),$=0,U=B>0?j-4:j;for(u=0;u>16&255,L[$++]=i>>8&255,L[$++]=255&i;2===B&&(i=_[s.charCodeAt(u)]<<2|_[s.charCodeAt(u+1)]>>4,L[$++]=255&i);1===B&&(i=_[s.charCodeAt(u)]<<10|_[s.charCodeAt(u+1)]<<4|_[s.charCodeAt(u+2)]>>2,L[$++]=i>>8&255,L[$++]=255&i);return L},i.fromByteArray=function fromByteArray(s){for(var i,_=s.length,w=_%3,x=[],j=16383,B=0,L=_-w;BL?L:B+j));1===w?(i=s[_-1],x.push(u[i>>2]+u[i<<4&63]+"==")):2===w&&(i=(s[_-2]<<8)+s[_-1],x.push(u[i>>10]+u[i>>4&63]+u[i<<2&63]+"="));return x.join("")};for(var u=[],_=[],w="undefined"!=typeof Uint8Array?Uint8Array:Array,x="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",j=0;j<64;++j)u[j]=x[j],_[x.charCodeAt(j)]=j;function getLens(s){var i=s.length;if(i%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var u=s.indexOf("=");return-1===u&&(u=i),[u,u===i?0:4-u%4]}function encodeChunk(s,i,_){for(var w,x,j=[],B=i;B<_;B+=3)w=(s[B]<<16&16711680)+(s[B+1]<<8&65280)+(255&s[B+2]),j.push(u[(x=w)>>18&63]+u[x>>12&63]+u[x>>6&63]+u[63&x]);return j.join("")}_["-".charCodeAt(0)]=62,_["_".charCodeAt(0)]=63},48287:(s,i,u)=>{"use strict";const _=u(67526),w=u(251),x="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;i.Buffer=Buffer,i.SlowBuffer=function SlowBuffer(s){+s!=s&&(s=0);return Buffer.alloc(+s)},i.INSPECT_MAX_BYTES=50;const j=2147483647;function createBuffer(s){if(s>j)throw new RangeError('The value "'+s+'" is invalid for option "size"');const i=new Uint8Array(s);return Object.setPrototypeOf(i,Buffer.prototype),i}function Buffer(s,i,u){if("number"==typeof s){if("string"==typeof i)throw new TypeError('The "string" argument must be of type string. Received type number');return allocUnsafe(s)}return from(s,i,u)}function from(s,i,u){if("string"==typeof s)return function fromString(s,i){"string"==typeof i&&""!==i||(i="utf8");if(!Buffer.isEncoding(i))throw new TypeError("Unknown encoding: "+i);const u=0|byteLength(s,i);let _=createBuffer(u);const w=_.write(s,i);w!==u&&(_=_.slice(0,w));return _}(s,i);if(ArrayBuffer.isView(s))return function fromArrayView(s){if(isInstance(s,Uint8Array)){const i=new Uint8Array(s);return fromArrayBuffer(i.buffer,i.byteOffset,i.byteLength)}return fromArrayLike(s)}(s);if(null==s)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s);if(isInstance(s,ArrayBuffer)||s&&isInstance(s.buffer,ArrayBuffer))return fromArrayBuffer(s,i,u);if("undefined"!=typeof SharedArrayBuffer&&(isInstance(s,SharedArrayBuffer)||s&&isInstance(s.buffer,SharedArrayBuffer)))return fromArrayBuffer(s,i,u);if("number"==typeof s)throw new TypeError('The "value" argument must not be of type number. Received type number');const _=s.valueOf&&s.valueOf();if(null!=_&&_!==s)return Buffer.from(_,i,u);const w=function fromObject(s){if(Buffer.isBuffer(s)){const i=0|checked(s.length),u=createBuffer(i);return 0===u.length||s.copy(u,0,0,i),u}if(void 0!==s.length)return"number"!=typeof s.length||numberIsNaN(s.length)?createBuffer(0):fromArrayLike(s);if("Buffer"===s.type&&Array.isArray(s.data))return fromArrayLike(s.data)}(s);if(w)return w;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof s[Symbol.toPrimitive])return Buffer.from(s[Symbol.toPrimitive]("string"),i,u);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s)}function assertSize(s){if("number"!=typeof s)throw new TypeError('"size" argument must be of type number');if(s<0)throw new RangeError('The value "'+s+'" is invalid for option "size"')}function allocUnsafe(s){return assertSize(s),createBuffer(s<0?0:0|checked(s))}function fromArrayLike(s){const i=s.length<0?0:0|checked(s.length),u=createBuffer(i);for(let _=0;_=j)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+j.toString(16)+" bytes");return 0|s}function byteLength(s,i){if(Buffer.isBuffer(s))return s.length;if(ArrayBuffer.isView(s)||isInstance(s,ArrayBuffer))return s.byteLength;if("string"!=typeof s)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof s);const u=s.length,_=arguments.length>2&&!0===arguments[2];if(!_&&0===u)return 0;let w=!1;for(;;)switch(i){case"ascii":case"latin1":case"binary":return u;case"utf8":case"utf-8":return utf8ToBytes(s).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*u;case"hex":return u>>>1;case"base64":return base64ToBytes(s).length;default:if(w)return _?-1:utf8ToBytes(s).length;i=(""+i).toLowerCase(),w=!0}}function slowToString(s,i,u){let _=!1;if((void 0===i||i<0)&&(i=0),i>this.length)return"";if((void 0===u||u>this.length)&&(u=this.length),u<=0)return"";if((u>>>=0)<=(i>>>=0))return"";for(s||(s="utf8");;)switch(s){case"hex":return hexSlice(this,i,u);case"utf8":case"utf-8":return utf8Slice(this,i,u);case"ascii":return asciiSlice(this,i,u);case"latin1":case"binary":return latin1Slice(this,i,u);case"base64":return base64Slice(this,i,u);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return utf16leSlice(this,i,u);default:if(_)throw new TypeError("Unknown encoding: "+s);s=(s+"").toLowerCase(),_=!0}}function swap(s,i,u){const _=s[i];s[i]=s[u],s[u]=_}function bidirectionalIndexOf(s,i,u,_,w){if(0===s.length)return-1;if("string"==typeof u?(_=u,u=0):u>2147483647?u=2147483647:u<-2147483648&&(u=-2147483648),numberIsNaN(u=+u)&&(u=w?0:s.length-1),u<0&&(u=s.length+u),u>=s.length){if(w)return-1;u=s.length-1}else if(u<0){if(!w)return-1;u=0}if("string"==typeof i&&(i=Buffer.from(i,_)),Buffer.isBuffer(i))return 0===i.length?-1:arrayIndexOf(s,i,u,_,w);if("number"==typeof i)return i&=255,"function"==typeof Uint8Array.prototype.indexOf?w?Uint8Array.prototype.indexOf.call(s,i,u):Uint8Array.prototype.lastIndexOf.call(s,i,u):arrayIndexOf(s,[i],u,_,w);throw new TypeError("val must be string, number or Buffer")}function arrayIndexOf(s,i,u,_,w){let x,j=1,B=s.length,L=i.length;if(void 0!==_&&("ucs2"===(_=String(_).toLowerCase())||"ucs-2"===_||"utf16le"===_||"utf-16le"===_)){if(s.length<2||i.length<2)return-1;j=2,B/=2,L/=2,u/=2}function read(s,i){return 1===j?s[i]:s.readUInt16BE(i*j)}if(w){let _=-1;for(x=u;xB&&(u=B-L),x=u;x>=0;x--){let u=!0;for(let _=0;_w&&(_=w):_=w;const x=i.length;let j;for(_>x/2&&(_=x/2),j=0;j<_;++j){const _=parseInt(i.substr(2*j,2),16);if(numberIsNaN(_))return j;s[u+j]=_}return j}function utf8Write(s,i,u,_){return blitBuffer(utf8ToBytes(i,s.length-u),s,u,_)}function asciiWrite(s,i,u,_){return blitBuffer(function asciiToBytes(s){const i=[];for(let u=0;u>8,w=u%256,x.push(w),x.push(_);return x}(i,s.length-u),s,u,_)}function base64Slice(s,i,u){return 0===i&&u===s.length?_.fromByteArray(s):_.fromByteArray(s.slice(i,u))}function utf8Slice(s,i,u){u=Math.min(s.length,u);const _=[];let w=i;for(;w239?4:i>223?3:i>191?2:1;if(w+j<=u){let u,_,B,L;switch(j){case 1:i<128&&(x=i);break;case 2:u=s[w+1],128==(192&u)&&(L=(31&i)<<6|63&u,L>127&&(x=L));break;case 3:u=s[w+1],_=s[w+2],128==(192&u)&&128==(192&_)&&(L=(15&i)<<12|(63&u)<<6|63&_,L>2047&&(L<55296||L>57343)&&(x=L));break;case 4:u=s[w+1],_=s[w+2],B=s[w+3],128==(192&u)&&128==(192&_)&&128==(192&B)&&(L=(15&i)<<18|(63&u)<<12|(63&_)<<6|63&B,L>65535&&L<1114112&&(x=L))}}null===x?(x=65533,j=1):x>65535&&(x-=65536,_.push(x>>>10&1023|55296),x=56320|1023&x),_.push(x),w+=j}return function decodeCodePointsArray(s){const i=s.length;if(i<=B)return String.fromCharCode.apply(String,s);let u="",_=0;for(;__.length?(Buffer.isBuffer(i)||(i=Buffer.from(i)),i.copy(_,w)):Uint8Array.prototype.set.call(_,i,w);else{if(!Buffer.isBuffer(i))throw new TypeError('"list" argument must be an Array of Buffers');i.copy(_,w)}w+=i.length}return _},Buffer.byteLength=byteLength,Buffer.prototype._isBuffer=!0,Buffer.prototype.swap16=function swap16(){const s=this.length;if(s%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let i=0;iu&&(s+=" ... "),""},x&&(Buffer.prototype[x]=Buffer.prototype.inspect),Buffer.prototype.compare=function compare(s,i,u,_,w){if(isInstance(s,Uint8Array)&&(s=Buffer.from(s,s.offset,s.byteLength)),!Buffer.isBuffer(s))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof s);if(void 0===i&&(i=0),void 0===u&&(u=s?s.length:0),void 0===_&&(_=0),void 0===w&&(w=this.length),i<0||u>s.length||_<0||w>this.length)throw new RangeError("out of range index");if(_>=w&&i>=u)return 0;if(_>=w)return-1;if(i>=u)return 1;if(this===s)return 0;let x=(w>>>=0)-(_>>>=0),j=(u>>>=0)-(i>>>=0);const B=Math.min(x,j),L=this.slice(_,w),$=s.slice(i,u);for(let s=0;s>>=0,isFinite(u)?(u>>>=0,void 0===_&&(_="utf8")):(_=u,u=void 0)}const w=this.length-i;if((void 0===u||u>w)&&(u=w),s.length>0&&(u<0||i<0)||i>this.length)throw new RangeError("Attempt to write outside buffer bounds");_||(_="utf8");let x=!1;for(;;)switch(_){case"hex":return hexWrite(this,s,i,u);case"utf8":case"utf-8":return utf8Write(this,s,i,u);case"ascii":case"latin1":case"binary":return asciiWrite(this,s,i,u);case"base64":return base64Write(this,s,i,u);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return ucs2Write(this,s,i,u);default:if(x)throw new TypeError("Unknown encoding: "+_);_=(""+_).toLowerCase(),x=!0}},Buffer.prototype.toJSON=function toJSON(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};const B=4096;function asciiSlice(s,i,u){let _="";u=Math.min(s.length,u);for(let w=i;w_)&&(u=_);let w="";for(let _=i;_u)throw new RangeError("Trying to access beyond buffer length")}function checkInt(s,i,u,_,w,x){if(!Buffer.isBuffer(s))throw new TypeError('"buffer" argument must be a Buffer instance');if(i>w||is.length)throw new RangeError("Index out of range")}function wrtBigUInt64LE(s,i,u,_,w){checkIntBI(i,_,w,s,u,7);let x=Number(i&BigInt(4294967295));s[u++]=x,x>>=8,s[u++]=x,x>>=8,s[u++]=x,x>>=8,s[u++]=x;let j=Number(i>>BigInt(32)&BigInt(4294967295));return s[u++]=j,j>>=8,s[u++]=j,j>>=8,s[u++]=j,j>>=8,s[u++]=j,u}function wrtBigUInt64BE(s,i,u,_,w){checkIntBI(i,_,w,s,u,7);let x=Number(i&BigInt(4294967295));s[u+7]=x,x>>=8,s[u+6]=x,x>>=8,s[u+5]=x,x>>=8,s[u+4]=x;let j=Number(i>>BigInt(32)&BigInt(4294967295));return s[u+3]=j,j>>=8,s[u+2]=j,j>>=8,s[u+1]=j,j>>=8,s[u]=j,u+8}function checkIEEE754(s,i,u,_,w,x){if(u+_>s.length)throw new RangeError("Index out of range");if(u<0)throw new RangeError("Index out of range")}function writeFloat(s,i,u,_,x){return i=+i,u>>>=0,x||checkIEEE754(s,0,u,4),w.write(s,i,u,_,23,4),u+4}function writeDouble(s,i,u,_,x){return i=+i,u>>>=0,x||checkIEEE754(s,0,u,8),w.write(s,i,u,_,52,8),u+8}Buffer.prototype.slice=function slice(s,i){const u=this.length;(s=~~s)<0?(s+=u)<0&&(s=0):s>u&&(s=u),(i=void 0===i?u:~~i)<0?(i+=u)<0&&(i=0):i>u&&(i=u),i>>=0,i>>>=0,u||checkOffset(s,i,this.length);let _=this[s],w=1,x=0;for(;++x>>=0,i>>>=0,u||checkOffset(s,i,this.length);let _=this[s+--i],w=1;for(;i>0&&(w*=256);)_+=this[s+--i]*w;return _},Buffer.prototype.readUint8=Buffer.prototype.readUInt8=function readUInt8(s,i){return s>>>=0,i||checkOffset(s,1,this.length),this[s]},Buffer.prototype.readUint16LE=Buffer.prototype.readUInt16LE=function readUInt16LE(s,i){return s>>>=0,i||checkOffset(s,2,this.length),this[s]|this[s+1]<<8},Buffer.prototype.readUint16BE=Buffer.prototype.readUInt16BE=function readUInt16BE(s,i){return s>>>=0,i||checkOffset(s,2,this.length),this[s]<<8|this[s+1]},Buffer.prototype.readUint32LE=Buffer.prototype.readUInt32LE=function readUInt32LE(s,i){return s>>>=0,i||checkOffset(s,4,this.length),(this[s]|this[s+1]<<8|this[s+2]<<16)+16777216*this[s+3]},Buffer.prototype.readUint32BE=Buffer.prototype.readUInt32BE=function readUInt32BE(s,i){return s>>>=0,i||checkOffset(s,4,this.length),16777216*this[s]+(this[s+1]<<16|this[s+2]<<8|this[s+3])},Buffer.prototype.readBigUInt64LE=defineBigIntMethod((function readBigUInt64LE(s){validateNumber(s>>>=0,"offset");const i=this[s],u=this[s+7];void 0!==i&&void 0!==u||boundsError(s,this.length-8);const _=i+256*this[++s]+65536*this[++s]+this[++s]*2**24,w=this[++s]+256*this[++s]+65536*this[++s]+u*2**24;return BigInt(_)+(BigInt(w)<>>=0,"offset");const i=this[s],u=this[s+7];void 0!==i&&void 0!==u||boundsError(s,this.length-8);const _=i*2**24+65536*this[++s]+256*this[++s]+this[++s],w=this[++s]*2**24+65536*this[++s]+256*this[++s]+u;return(BigInt(_)<>>=0,i>>>=0,u||checkOffset(s,i,this.length);let _=this[s],w=1,x=0;for(;++x=w&&(_-=Math.pow(2,8*i)),_},Buffer.prototype.readIntBE=function readIntBE(s,i,u){s>>>=0,i>>>=0,u||checkOffset(s,i,this.length);let _=i,w=1,x=this[s+--_];for(;_>0&&(w*=256);)x+=this[s+--_]*w;return w*=128,x>=w&&(x-=Math.pow(2,8*i)),x},Buffer.prototype.readInt8=function readInt8(s,i){return s>>>=0,i||checkOffset(s,1,this.length),128&this[s]?-1*(255-this[s]+1):this[s]},Buffer.prototype.readInt16LE=function readInt16LE(s,i){s>>>=0,i||checkOffset(s,2,this.length);const u=this[s]|this[s+1]<<8;return 32768&u?4294901760|u:u},Buffer.prototype.readInt16BE=function readInt16BE(s,i){s>>>=0,i||checkOffset(s,2,this.length);const u=this[s+1]|this[s]<<8;return 32768&u?4294901760|u:u},Buffer.prototype.readInt32LE=function readInt32LE(s,i){return s>>>=0,i||checkOffset(s,4,this.length),this[s]|this[s+1]<<8|this[s+2]<<16|this[s+3]<<24},Buffer.prototype.readInt32BE=function readInt32BE(s,i){return s>>>=0,i||checkOffset(s,4,this.length),this[s]<<24|this[s+1]<<16|this[s+2]<<8|this[s+3]},Buffer.prototype.readBigInt64LE=defineBigIntMethod((function readBigInt64LE(s){validateNumber(s>>>=0,"offset");const i=this[s],u=this[s+7];void 0!==i&&void 0!==u||boundsError(s,this.length-8);const _=this[s+4]+256*this[s+5]+65536*this[s+6]+(u<<24);return(BigInt(_)<>>=0,"offset");const i=this[s],u=this[s+7];void 0!==i&&void 0!==u||boundsError(s,this.length-8);const _=(i<<24)+65536*this[++s]+256*this[++s]+this[++s];return(BigInt(_)<>>=0,i||checkOffset(s,4,this.length),w.read(this,s,!0,23,4)},Buffer.prototype.readFloatBE=function readFloatBE(s,i){return s>>>=0,i||checkOffset(s,4,this.length),w.read(this,s,!1,23,4)},Buffer.prototype.readDoubleLE=function readDoubleLE(s,i){return s>>>=0,i||checkOffset(s,8,this.length),w.read(this,s,!0,52,8)},Buffer.prototype.readDoubleBE=function readDoubleBE(s,i){return s>>>=0,i||checkOffset(s,8,this.length),w.read(this,s,!1,52,8)},Buffer.prototype.writeUintLE=Buffer.prototype.writeUIntLE=function writeUIntLE(s,i,u,_){if(s=+s,i>>>=0,u>>>=0,!_){checkInt(this,s,i,u,Math.pow(2,8*u)-1,0)}let w=1,x=0;for(this[i]=255&s;++x>>=0,u>>>=0,!_){checkInt(this,s,i,u,Math.pow(2,8*u)-1,0)}let w=u-1,x=1;for(this[i+w]=255&s;--w>=0&&(x*=256);)this[i+w]=s/x&255;return i+u},Buffer.prototype.writeUint8=Buffer.prototype.writeUInt8=function writeUInt8(s,i,u){return s=+s,i>>>=0,u||checkInt(this,s,i,1,255,0),this[i]=255&s,i+1},Buffer.prototype.writeUint16LE=Buffer.prototype.writeUInt16LE=function writeUInt16LE(s,i,u){return s=+s,i>>>=0,u||checkInt(this,s,i,2,65535,0),this[i]=255&s,this[i+1]=s>>>8,i+2},Buffer.prototype.writeUint16BE=Buffer.prototype.writeUInt16BE=function writeUInt16BE(s,i,u){return s=+s,i>>>=0,u||checkInt(this,s,i,2,65535,0),this[i]=s>>>8,this[i+1]=255&s,i+2},Buffer.prototype.writeUint32LE=Buffer.prototype.writeUInt32LE=function writeUInt32LE(s,i,u){return s=+s,i>>>=0,u||checkInt(this,s,i,4,4294967295,0),this[i+3]=s>>>24,this[i+2]=s>>>16,this[i+1]=s>>>8,this[i]=255&s,i+4},Buffer.prototype.writeUint32BE=Buffer.prototype.writeUInt32BE=function writeUInt32BE(s,i,u){return s=+s,i>>>=0,u||checkInt(this,s,i,4,4294967295,0),this[i]=s>>>24,this[i+1]=s>>>16,this[i+2]=s>>>8,this[i+3]=255&s,i+4},Buffer.prototype.writeBigUInt64LE=defineBigIntMethod((function writeBigUInt64LE(s,i=0){return wrtBigUInt64LE(this,s,i,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeBigUInt64BE=defineBigIntMethod((function writeBigUInt64BE(s,i=0){return wrtBigUInt64BE(this,s,i,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeIntLE=function writeIntLE(s,i,u,_){if(s=+s,i>>>=0,!_){const _=Math.pow(2,8*u-1);checkInt(this,s,i,u,_-1,-_)}let w=0,x=1,j=0;for(this[i]=255&s;++w>0)-j&255;return i+u},Buffer.prototype.writeIntBE=function writeIntBE(s,i,u,_){if(s=+s,i>>>=0,!_){const _=Math.pow(2,8*u-1);checkInt(this,s,i,u,_-1,-_)}let w=u-1,x=1,j=0;for(this[i+w]=255&s;--w>=0&&(x*=256);)s<0&&0===j&&0!==this[i+w+1]&&(j=1),this[i+w]=(s/x>>0)-j&255;return i+u},Buffer.prototype.writeInt8=function writeInt8(s,i,u){return s=+s,i>>>=0,u||checkInt(this,s,i,1,127,-128),s<0&&(s=255+s+1),this[i]=255&s,i+1},Buffer.prototype.writeInt16LE=function writeInt16LE(s,i,u){return s=+s,i>>>=0,u||checkInt(this,s,i,2,32767,-32768),this[i]=255&s,this[i+1]=s>>>8,i+2},Buffer.prototype.writeInt16BE=function writeInt16BE(s,i,u){return s=+s,i>>>=0,u||checkInt(this,s,i,2,32767,-32768),this[i]=s>>>8,this[i+1]=255&s,i+2},Buffer.prototype.writeInt32LE=function writeInt32LE(s,i,u){return s=+s,i>>>=0,u||checkInt(this,s,i,4,2147483647,-2147483648),this[i]=255&s,this[i+1]=s>>>8,this[i+2]=s>>>16,this[i+3]=s>>>24,i+4},Buffer.prototype.writeInt32BE=function writeInt32BE(s,i,u){return s=+s,i>>>=0,u||checkInt(this,s,i,4,2147483647,-2147483648),s<0&&(s=4294967295+s+1),this[i]=s>>>24,this[i+1]=s>>>16,this[i+2]=s>>>8,this[i+3]=255&s,i+4},Buffer.prototype.writeBigInt64LE=defineBigIntMethod((function writeBigInt64LE(s,i=0){return wrtBigUInt64LE(this,s,i,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeBigInt64BE=defineBigIntMethod((function writeBigInt64BE(s,i=0){return wrtBigUInt64BE(this,s,i,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeFloatLE=function writeFloatLE(s,i,u){return writeFloat(this,s,i,!0,u)},Buffer.prototype.writeFloatBE=function writeFloatBE(s,i,u){return writeFloat(this,s,i,!1,u)},Buffer.prototype.writeDoubleLE=function writeDoubleLE(s,i,u){return writeDouble(this,s,i,!0,u)},Buffer.prototype.writeDoubleBE=function writeDoubleBE(s,i,u){return writeDouble(this,s,i,!1,u)},Buffer.prototype.copy=function copy(s,i,u,_){if(!Buffer.isBuffer(s))throw new TypeError("argument should be a Buffer");if(u||(u=0),_||0===_||(_=this.length),i>=s.length&&(i=s.length),i||(i=0),_>0&&_=this.length)throw new RangeError("Index out of range");if(_<0)throw new RangeError("sourceEnd out of bounds");_>this.length&&(_=this.length),s.length-i<_-u&&(_=s.length-i+u);const w=_-u;return this===s&&"function"==typeof Uint8Array.prototype.copyWithin?this.copyWithin(i,u,_):Uint8Array.prototype.set.call(s,this.subarray(u,_),i),w},Buffer.prototype.fill=function fill(s,i,u,_){if("string"==typeof s){if("string"==typeof i?(_=i,i=0,u=this.length):"string"==typeof u&&(_=u,u=this.length),void 0!==_&&"string"!=typeof _)throw new TypeError("encoding must be a string");if("string"==typeof _&&!Buffer.isEncoding(_))throw new TypeError("Unknown encoding: "+_);if(1===s.length){const i=s.charCodeAt(0);("utf8"===_&&i<128||"latin1"===_)&&(s=i)}}else"number"==typeof s?s&=255:"boolean"==typeof s&&(s=Number(s));if(i<0||this.length>>=0,u=void 0===u?this.length:u>>>0,s||(s=0),"number"==typeof s)for(w=i;w=_+4;u-=3)i=`_${s.slice(u-3,u)}${i}`;return`${s.slice(0,u)}${i}`}function checkIntBI(s,i,u,_,w,x){if(s>u||s3?0===i||i===BigInt(0)?`>= 0${_} and < 2${_} ** ${8*(x+1)}${_}`:`>= -(2${_} ** ${8*(x+1)-1}${_}) and < 2 ** ${8*(x+1)-1}${_}`:`>= ${i}${_} and <= ${u}${_}`,new L.ERR_OUT_OF_RANGE("value",w,s)}!function checkBounds(s,i,u){validateNumber(i,"offset"),void 0!==s[i]&&void 0!==s[i+u]||boundsError(i,s.length-(u+1))}(_,w,x)}function validateNumber(s,i){if("number"!=typeof s)throw new L.ERR_INVALID_ARG_TYPE(i,"number",s)}function boundsError(s,i,u){if(Math.floor(s)!==s)throw validateNumber(s,u),new L.ERR_OUT_OF_RANGE(u||"offset","an integer",s);if(i<0)throw new L.ERR_BUFFER_OUT_OF_BOUNDS;throw new L.ERR_OUT_OF_RANGE(u||"offset",`>= ${u?1:0} and <= ${i}`,s)}E("ERR_BUFFER_OUT_OF_BOUNDS",(function(s){return s?`${s} is outside of buffer bounds`:"Attempt to access memory outside buffer bounds"}),RangeError),E("ERR_INVALID_ARG_TYPE",(function(s,i){return`The "${s}" argument must be of type number. Received type ${typeof i}`}),TypeError),E("ERR_OUT_OF_RANGE",(function(s,i,u){let _=`The value of "${s}" is out of range.`,w=u;return Number.isInteger(u)&&Math.abs(u)>2**32?w=addNumericalSeparator(String(u)):"bigint"==typeof u&&(w=String(u),(u>BigInt(2)**BigInt(32)||u<-(BigInt(2)**BigInt(32)))&&(w=addNumericalSeparator(w)),w+="n"),_+=` It must be ${i}. Received ${w}`,_}),RangeError);const $=/[^+/0-9A-Za-z-_]/g;function utf8ToBytes(s,i){let u;i=i||1/0;const _=s.length;let w=null;const x=[];for(let j=0;j<_;++j){if(u=s.charCodeAt(j),u>55295&&u<57344){if(!w){if(u>56319){(i-=3)>-1&&x.push(239,191,189);continue}if(j+1===_){(i-=3)>-1&&x.push(239,191,189);continue}w=u;continue}if(u<56320){(i-=3)>-1&&x.push(239,191,189),w=u;continue}u=65536+(w-55296<<10|u-56320)}else w&&(i-=3)>-1&&x.push(239,191,189);if(w=null,u<128){if((i-=1)<0)break;x.push(u)}else if(u<2048){if((i-=2)<0)break;x.push(u>>6|192,63&u|128)}else if(u<65536){if((i-=3)<0)break;x.push(u>>12|224,u>>6&63|128,63&u|128)}else{if(!(u<1114112))throw new Error("Invalid code point");if((i-=4)<0)break;x.push(u>>18|240,u>>12&63|128,u>>6&63|128,63&u|128)}}return x}function base64ToBytes(s){return _.toByteArray(function base64clean(s){if((s=(s=s.split("=")[0]).trim().replace($,"")).length<2)return"";for(;s.length%4!=0;)s+="=";return s}(s))}function blitBuffer(s,i,u,_){let w;for(w=0;w<_&&!(w+u>=i.length||w>=s.length);++w)i[w+u]=s[w];return w}function isInstance(s,i){return s instanceof i||null!=s&&null!=s.constructor&&null!=s.constructor.name&&s.constructor.name===i.name}function numberIsNaN(s){return s!=s}const U=function(){const s="0123456789abcdef",i=new Array(256);for(let u=0;u<16;++u){const _=16*u;for(let w=0;w<16;++w)i[_+w]=s[u]+s[w]}return i}();function defineBigIntMethod(s){return"undefined"==typeof BigInt?BufferBigIntNotDefined:s}function BufferBigIntNotDefined(){throw new Error("BigInt not supported")}},38075:(s,i,u)=>{"use strict";var _=u(70453),w=u(10487),x=w(_("String.prototype.indexOf"));s.exports=function callBoundIntrinsic(s,i){var u=_(s,!!i);return"function"==typeof u&&x(s,".prototype.")>-1?w(u):u}},10487:(s,i,u)=>{"use strict";var _=u(66743),w=u(70453),x=u(96897),j=u(69675),B=w("%Function.prototype.apply%"),L=w("%Function.prototype.call%"),$=w("%Reflect.apply%",!0)||_.call(L,B),U=u(30655),Y=w("%Math.max%");s.exports=function callBind(s){if("function"!=typeof s)throw new j("a function is required");var i=$(_,L,arguments);return x(i,1+Y(0,s.length-(arguments.length-1)),!0)};var Z=function applyBind(){return $(_,B,arguments)};U?U(s.exports,"apply",{value:Z}):s.exports.apply=Z},57427:(s,i)=>{"use strict";i.parse=function parse(s,i){if("string"!=typeof s)throw new TypeError("argument str must be a string");var u={},_=(i||{}).decode||decode,w=0;for(;w{"use strict";var _=u(16426),w={"text/plain":"Text","text/html":"Url",default:"Text"};s.exports=function copy(s,i){var u,x,j,B,L,$,U=!1;i||(i={}),u=i.debug||!1;try{if(j=_(),B=document.createRange(),L=document.getSelection(),($=document.createElement("span")).textContent=s,$.ariaHidden="true",$.style.all="unset",$.style.position="fixed",$.style.top=0,$.style.clip="rect(0, 0, 0, 0)",$.style.whiteSpace="pre",$.style.webkitUserSelect="text",$.style.MozUserSelect="text",$.style.msUserSelect="text",$.style.userSelect="text",$.addEventListener("copy",(function(_){if(_.stopPropagation(),i.format)if(_.preventDefault(),void 0===_.clipboardData){u&&console.warn("unable to use e.clipboardData"),u&&console.warn("trying IE specific stuff"),window.clipboardData.clearData();var x=w[i.format]||w.default;window.clipboardData.setData(x,s)}else _.clipboardData.clearData(),_.clipboardData.setData(i.format,s);i.onCopy&&(_.preventDefault(),i.onCopy(_.clipboardData))})),document.body.appendChild($),B.selectNodeContents($),L.addRange(B),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");U=!0}catch(_){u&&console.error("unable to copy using execCommand: ",_),u&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(i.format||"text",s),i.onCopy&&i.onCopy(window.clipboardData),U=!0}catch(_){u&&console.error("unable to copy using clipboardData: ",_),u&&console.error("falling back to prompt"),x=function format(s){var i=(/mac os x/i.test(navigator.userAgent)?"⌘":"Ctrl")+"+C";return s.replace(/#{\s*key\s*}/g,i)}("message"in i?i.message:"Copy to clipboard: #{key}, Enter"),window.prompt(x,s)}}finally{L&&("function"==typeof L.removeRange?L.removeRange(B):L.removeAllRanges()),$&&document.body.removeChild($),j()}return U}},2205:function(s,i,u){var _;_=void 0!==u.g?u.g:this,s.exports=function(s){if(s.CSS&&s.CSS.escape)return s.CSS.escape;var cssEscape=function(s){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var i,u=String(s),_=u.length,w=-1,x="",j=u.charCodeAt(0);++w<_;)0!=(i=u.charCodeAt(w))?x+=i>=1&&i<=31||127==i||0==w&&i>=48&&i<=57||1==w&&i>=48&&i<=57&&45==j?"\\"+i.toString(16)+" ":0==w&&1==_&&45==i||!(i>=128||45==i||95==i||i>=48&&i<=57||i>=65&&i<=90||i>=97&&i<=122)?"\\"+u.charAt(w):u.charAt(w):x+="�";return x};return s.CSS||(s.CSS={}),s.CSS.escape=cssEscape,cssEscape}(_)},81919:(s,i,u)=>{"use strict";var _=u(48287).Buffer;function isSpecificValue(s){return s instanceof _||s instanceof Date||s instanceof RegExp}function cloneSpecificValue(s){if(s instanceof _){var i=_.alloc?_.alloc(s.length):new _(s.length);return s.copy(i),i}if(s instanceof Date)return new Date(s.getTime());if(s instanceof RegExp)return new RegExp(s);throw new Error("Unexpected situation")}function deepCloneArray(s){var i=[];return s.forEach((function(s,u){"object"==typeof s&&null!==s?Array.isArray(s)?i[u]=deepCloneArray(s):isSpecificValue(s)?i[u]=cloneSpecificValue(s):i[u]=w({},s):i[u]=s})),i}function safeGetProperty(s,i){return"__proto__"===i?void 0:s[i]}var w=s.exports=function(){if(arguments.length<1||"object"!=typeof arguments[0])return!1;if(arguments.length<2)return arguments[0];var s,i,u=arguments[0];return Array.prototype.slice.call(arguments,1).forEach((function(_){"object"!=typeof _||null===_||Array.isArray(_)||Object.keys(_).forEach((function(x){return i=safeGetProperty(u,x),(s=safeGetProperty(_,x))===u?void 0:"object"!=typeof s||null===s?void(u[x]=s):Array.isArray(s)?void(u[x]=deepCloneArray(s)):isSpecificValue(s)?void(u[x]=cloneSpecificValue(s)):"object"!=typeof i||null===i||Array.isArray(i)?void(u[x]=w({},s)):void(u[x]=w(i,s))}))})),u}},14744:s=>{"use strict";var i=function isMergeableObject(s){return function isNonNullObject(s){return!!s&&"object"==typeof s}(s)&&!function isSpecial(s){var i=Object.prototype.toString.call(s);return"[object RegExp]"===i||"[object Date]"===i||function isReactElement(s){return s.$$typeof===u}(s)}(s)};var u="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function cloneUnlessOtherwiseSpecified(s,i){return!1!==i.clone&&i.isMergeableObject(s)?deepmerge(function emptyTarget(s){return Array.isArray(s)?[]:{}}(s),s,i):s}function defaultArrayMerge(s,i,u){return s.concat(i).map((function(s){return cloneUnlessOtherwiseSpecified(s,u)}))}function getKeys(s){return Object.keys(s).concat(function getEnumerableOwnPropertySymbols(s){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(s).filter((function(i){return Object.propertyIsEnumerable.call(s,i)})):[]}(s))}function propertyIsOnObject(s,i){try{return i in s}catch(s){return!1}}function mergeObject(s,i,u){var _={};return u.isMergeableObject(s)&&getKeys(s).forEach((function(i){_[i]=cloneUnlessOtherwiseSpecified(s[i],u)})),getKeys(i).forEach((function(w){(function propertyIsUnsafe(s,i){return propertyIsOnObject(s,i)&&!(Object.hasOwnProperty.call(s,i)&&Object.propertyIsEnumerable.call(s,i))})(s,w)||(propertyIsOnObject(s,w)&&u.isMergeableObject(i[w])?_[w]=function getMergeFunction(s,i){if(!i.customMerge)return deepmerge;var u=i.customMerge(s);return"function"==typeof u?u:deepmerge}(w,u)(s[w],i[w],u):_[w]=cloneUnlessOtherwiseSpecified(i[w],u))})),_}function deepmerge(s,u,_){(_=_||{}).arrayMerge=_.arrayMerge||defaultArrayMerge,_.isMergeableObject=_.isMergeableObject||i,_.cloneUnlessOtherwiseSpecified=cloneUnlessOtherwiseSpecified;var w=Array.isArray(u);return w===Array.isArray(s)?w?_.arrayMerge(s,u,_):mergeObject(s,u,_):cloneUnlessOtherwiseSpecified(u,_)}deepmerge.all=function deepmergeAll(s,i){if(!Array.isArray(s))throw new Error("first argument should be an array");return s.reduce((function(s,u){return deepmerge(s,u,i)}),{})};var _=deepmerge;s.exports=_},30041:(s,i,u)=>{"use strict";var _=u(30655),w=u(58068),x=u(69675),j=u(75795);s.exports=function defineDataProperty(s,i,u){if(!s||"object"!=typeof s&&"function"!=typeof s)throw new x("`obj` must be an object or a function`");if("string"!=typeof i&&"symbol"!=typeof i)throw new x("`property` must be a string or a symbol`");if(arguments.length>3&&"boolean"!=typeof arguments[3]&&null!==arguments[3])throw new x("`nonEnumerable`, if provided, must be a boolean or null");if(arguments.length>4&&"boolean"!=typeof arguments[4]&&null!==arguments[4])throw new x("`nonWritable`, if provided, must be a boolean or null");if(arguments.length>5&&"boolean"!=typeof arguments[5]&&null!==arguments[5])throw new x("`nonConfigurable`, if provided, must be a boolean or null");if(arguments.length>6&&"boolean"!=typeof arguments[6])throw new x("`loose`, if provided, must be a boolean");var B=arguments.length>3?arguments[3]:null,L=arguments.length>4?arguments[4]:null,$=arguments.length>5?arguments[5]:null,U=arguments.length>6&&arguments[6],Y=!!j&&j(s,i);if(_)_(s,i,{configurable:null===$&&Y?Y.configurable:!$,enumerable:null===B&&Y?Y.enumerable:!B,value:u,writable:null===L&&Y?Y.writable:!L});else{if(!U&&(B||L||$))throw new w("This environment does not support defining a property as non-configurable, non-writable, or non-enumerable.");s[i]=u}}},42838:function(s){s.exports=function(){"use strict";const{entries:s,setPrototypeOf:i,isFrozen:u,getPrototypeOf:_,getOwnPropertyDescriptor:w}=Object;let{freeze:x,seal:j,create:B}=Object,{apply:L,construct:$}="undefined"!=typeof Reflect&&Reflect;x||(x=function freeze(s){return s}),j||(j=function seal(s){return s}),L||(L=function apply(s,i,u){return s.apply(i,u)}),$||($=function construct(s,i){return new s(...i)});const U=unapply(Array.prototype.forEach),Y=unapply(Array.prototype.pop),Z=unapply(Array.prototype.push),ee=unapply(String.prototype.toLowerCase),ie=unapply(String.prototype.toString),ae=unapply(String.prototype.match),le=unapply(String.prototype.replace),ce=unapply(String.prototype.indexOf),pe=unapply(String.prototype.trim),de=unapply(Object.prototype.hasOwnProperty),fe=unapply(RegExp.prototype.test),ye=unconstruct(TypeError);function unapply(s){return function(i){for(var u=arguments.length,_=new Array(u>1?u-1:0),w=1;w2&&void 0!==arguments[2]?arguments[2]:ee;i&&i(s,null);let x=_.length;for(;x--;){let i=_[x];if("string"==typeof i){const s=w(i);s!==i&&(u(_)||(_[x]=s),i=s)}s[i]=!0}return s}function cleanArray(s){for(let i=0;i/gm),Xe=j(/\${[\w\W]*}/gm),Ye=j(/^data-[\-\w.\u00B7-\uFFFF]/),Qe=j(/^aria-[\-\w]+$/),et=j(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),tt=j(/^(?:\w+script|data):/i),rt=j(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),nt=j(/^html$/i),ot=j(/^[a-z][.\w]*(-[.\w]+)+$/i);var st=Object.freeze({__proto__:null,MUSTACHE_EXPR:We,ERB_EXPR:He,TMPLIT_EXPR:Xe,DATA_ATTR:Ye,ARIA_ATTR:Qe,IS_ALLOWED_URI:et,IS_SCRIPT_OR_DATA:tt,ATTR_WHITESPACE:rt,DOCTYPE_NAME:nt,CUSTOM_ELEMENT:ot});const it=function getGlobal(){return"undefined"==typeof window?null:window},at=function _createTrustedTypesPolicy(s,i){if("object"!=typeof s||"function"!=typeof s.createPolicy)return null;let u=null;const _="data-tt-policy-suffix";i&&i.hasAttribute(_)&&(u=i.getAttribute(_));const w="dompurify"+(u?"#"+u:"");try{return s.createPolicy(w,{createHTML:s=>s,createScriptURL:s=>s})}catch(s){return console.warn("TrustedTypes policy "+w+" could not be created."),null}};function createDOMPurify(){let i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:it();const DOMPurify=s=>createDOMPurify(s);if(DOMPurify.version="3.1.1",DOMPurify.removed=[],!i||!i.document||9!==i.document.nodeType)return DOMPurify.isSupported=!1,DOMPurify;let{document:u}=i;const _=u,w=_.currentScript,{DocumentFragment:j,HTMLTemplateElement:L,Node:$,Element:We,NodeFilter:He,NamedNodeMap:Xe=i.NamedNodeMap||i.MozNamedAttrMap,HTMLFormElement:Ye,DOMParser:Qe,trustedTypes:tt}=i,rt=We.prototype,ot=lookupGetter(rt,"cloneNode"),lt=lookupGetter(rt,"nextSibling"),ct=lookupGetter(rt,"childNodes"),ut=lookupGetter(rt,"parentNode");if("function"==typeof L){const s=u.createElement("template");s.content&&s.content.ownerDocument&&(u=s.content.ownerDocument)}let pt,ht="";const{implementation:dt,createNodeIterator:mt,createDocumentFragment:gt,getElementsByTagName:yt}=u,{importNode:vt}=_;let bt={};DOMPurify.isSupported="function"==typeof s&&"function"==typeof ut&&dt&&void 0!==dt.createHTMLDocument;const{MUSTACHE_EXPR:_t,ERB_EXPR:Et,TMPLIT_EXPR:wt,DATA_ATTR:St,ARIA_ATTR:xt,IS_SCRIPT_OR_DATA:kt,ATTR_WHITESPACE:Ot,CUSTOM_ELEMENT:Ct}=st;let{IS_ALLOWED_URI:At}=st,jt=null;const Pt=addToSet({},[...be,..._e,...we,...xe,...Te]);let It=null;const Nt=addToSet({},[...Re,...qe,...$e,...ze]);let Mt=Object.seal(B(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Tt=null,Rt=null,Dt=!0,Bt=!0,Lt=!1,Ft=!0,qt=!1,$t=!0,Ut=!1,zt=!1,Vt=!1,Wt=!1,Kt=!1,Ht=!1,Jt=!0,Gt=!1;const Xt="user-content-";let Yt=!0,Qt=!1,Zt={},er=null;const tr=addToSet({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let rr=null;const nr=addToSet({},["audio","video","img","source","image","track"]);let sr=null;const ir=addToSet({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),ar="http://www.w3.org/1998/Math/MathML",lr="http://www.w3.org/2000/svg",cr="http://www.w3.org/1999/xhtml";let ur=cr,pr=!1,dr=null;const fr=addToSet({},[ar,lr,cr],ie);let mr=null;const gr=["application/xhtml+xml","text/html"],yr="text/html";let vr=null,br=null;const _r=255,Er=u.createElement("form"),wr=function isRegexOrFunction(s){return s instanceof RegExp||s instanceof Function},Sr=function _parseConfig(){let s=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!br||br!==s){if(s&&"object"==typeof s||(s={}),s=clone(s),mr=-1===gr.indexOf(s.PARSER_MEDIA_TYPE)?yr:s.PARSER_MEDIA_TYPE,vr="application/xhtml+xml"===mr?ie:ee,jt=de(s,"ALLOWED_TAGS")?addToSet({},s.ALLOWED_TAGS,vr):Pt,It=de(s,"ALLOWED_ATTR")?addToSet({},s.ALLOWED_ATTR,vr):Nt,dr=de(s,"ALLOWED_NAMESPACES")?addToSet({},s.ALLOWED_NAMESPACES,ie):fr,sr=de(s,"ADD_URI_SAFE_ATTR")?addToSet(clone(ir),s.ADD_URI_SAFE_ATTR,vr):ir,rr=de(s,"ADD_DATA_URI_TAGS")?addToSet(clone(nr),s.ADD_DATA_URI_TAGS,vr):nr,er=de(s,"FORBID_CONTENTS")?addToSet({},s.FORBID_CONTENTS,vr):tr,Tt=de(s,"FORBID_TAGS")?addToSet({},s.FORBID_TAGS,vr):{},Rt=de(s,"FORBID_ATTR")?addToSet({},s.FORBID_ATTR,vr):{},Zt=!!de(s,"USE_PROFILES")&&s.USE_PROFILES,Dt=!1!==s.ALLOW_ARIA_ATTR,Bt=!1!==s.ALLOW_DATA_ATTR,Lt=s.ALLOW_UNKNOWN_PROTOCOLS||!1,Ft=!1!==s.ALLOW_SELF_CLOSE_IN_ATTR,qt=s.SAFE_FOR_TEMPLATES||!1,$t=!1!==s.SAFE_FOR_XML,Ut=s.WHOLE_DOCUMENT||!1,Wt=s.RETURN_DOM||!1,Kt=s.RETURN_DOM_FRAGMENT||!1,Ht=s.RETURN_TRUSTED_TYPE||!1,Vt=s.FORCE_BODY||!1,Jt=!1!==s.SANITIZE_DOM,Gt=s.SANITIZE_NAMED_PROPS||!1,Yt=!1!==s.KEEP_CONTENT,Qt=s.IN_PLACE||!1,At=s.ALLOWED_URI_REGEXP||et,ur=s.NAMESPACE||cr,Mt=s.CUSTOM_ELEMENT_HANDLING||{},s.CUSTOM_ELEMENT_HANDLING&&wr(s.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Mt.tagNameCheck=s.CUSTOM_ELEMENT_HANDLING.tagNameCheck),s.CUSTOM_ELEMENT_HANDLING&&wr(s.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(Mt.attributeNameCheck=s.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),s.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof s.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(Mt.allowCustomizedBuiltInElements=s.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),qt&&(Bt=!1),Kt&&(Wt=!0),Zt&&(jt=addToSet({},Te),It=[],!0===Zt.html&&(addToSet(jt,be),addToSet(It,Re)),!0===Zt.svg&&(addToSet(jt,_e),addToSet(It,qe),addToSet(It,ze)),!0===Zt.svgFilters&&(addToSet(jt,we),addToSet(It,qe),addToSet(It,ze)),!0===Zt.mathMl&&(addToSet(jt,xe),addToSet(It,$e),addToSet(It,ze))),s.ADD_TAGS&&(jt===Pt&&(jt=clone(jt)),addToSet(jt,s.ADD_TAGS,vr)),s.ADD_ATTR&&(It===Nt&&(It=clone(It)),addToSet(It,s.ADD_ATTR,vr)),s.ADD_URI_SAFE_ATTR&&addToSet(sr,s.ADD_URI_SAFE_ATTR,vr),s.FORBID_CONTENTS&&(er===tr&&(er=clone(er)),addToSet(er,s.FORBID_CONTENTS,vr)),Yt&&(jt["#text"]=!0),Ut&&addToSet(jt,["html","head","body"]),jt.table&&(addToSet(jt,["tbody"]),delete Tt.tbody),s.TRUSTED_TYPES_POLICY){if("function"!=typeof s.TRUSTED_TYPES_POLICY.createHTML)throw ye('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof s.TRUSTED_TYPES_POLICY.createScriptURL)throw ye('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');pt=s.TRUSTED_TYPES_POLICY,ht=pt.createHTML("")}else void 0===pt&&(pt=at(tt,w)),null!==pt&&"string"==typeof ht&&(ht=pt.createHTML(""));x&&x(s),br=s}},xr=addToSet({},["mi","mo","mn","ms","mtext"]),kr=addToSet({},["foreignobject","desc","title","annotation-xml"]),Or=addToSet({},["title","style","font","a","script"]),Cr=addToSet({},[..._e,...we,...Se]),Ar=addToSet({},[...xe,...Pe]),jr=function _checkValidNamespace(s){let i=ut(s);i&&i.tagName||(i={namespaceURI:ur,tagName:"template"});const u=ee(s.tagName),_=ee(i.tagName);return!!dr[s.namespaceURI]&&(s.namespaceURI===lr?i.namespaceURI===cr?"svg"===u:i.namespaceURI===ar?"svg"===u&&("annotation-xml"===_||xr[_]):Boolean(Cr[u]):s.namespaceURI===ar?i.namespaceURI===cr?"math"===u:i.namespaceURI===lr?"math"===u&&kr[_]:Boolean(Ar[u]):s.namespaceURI===cr?!(i.namespaceURI===lr&&!kr[_])&&!(i.namespaceURI===ar&&!xr[_])&&!Ar[u]&&(Or[u]||!Cr[u]):!("application/xhtml+xml"!==mr||!dr[s.namespaceURI]))},Pr=function _forceRemove(s){Z(DOMPurify.removed,{element:s});try{s.parentNode.removeChild(s)}catch(i){s.remove()}},Ir=function _removeAttribute(s,i){try{Z(DOMPurify.removed,{attribute:i.getAttributeNode(s),from:i})}catch(s){Z(DOMPurify.removed,{attribute:null,from:i})}if(i.removeAttribute(s),"is"===s&&!It[s])if(Wt||Kt)try{Pr(i)}catch(s){}else try{i.setAttribute(s,"")}catch(s){}},Nr=function _initDocument(s){let i=null,_=null;if(Vt)s=""+s;else{const i=ae(s,/^[\r\n\t ]+/);_=i&&i[0]}"application/xhtml+xml"===mr&&ur===cr&&(s=''+s+"");const w=pt?pt.createHTML(s):s;if(ur===cr)try{i=(new Qe).parseFromString(w,mr)}catch(s){}if(!i||!i.documentElement){i=dt.createDocument(ur,"template",null);try{i.documentElement.innerHTML=pr?ht:w}catch(s){}}const x=i.body||i.documentElement;return s&&_&&x.insertBefore(u.createTextNode(_),x.childNodes[0]||null),ur===cr?yt.call(i,Ut?"html":"body")[0]:Ut?i.documentElement:x},Mr=function _createNodeIterator(s){return mt.call(s.ownerDocument||s,s,He.SHOW_ELEMENT|He.SHOW_COMMENT|He.SHOW_TEXT|He.SHOW_PROCESSING_INSTRUCTION|He.SHOW_CDATA_SECTION,null)},Tr=function _isClobbered(s){return s instanceof Ye&&(void 0!==s.__depth&&"number"!=typeof s.__depth||void 0!==s.__removalCount&&"number"!=typeof s.__removalCount||"string"!=typeof s.nodeName||"string"!=typeof s.textContent||"function"!=typeof s.removeChild||!(s.attributes instanceof Xe)||"function"!=typeof s.removeAttribute||"function"!=typeof s.setAttribute||"string"!=typeof s.namespaceURI||"function"!=typeof s.insertBefore||"function"!=typeof s.hasChildNodes)},Rr=function _isNode(s){return"function"==typeof $&&s instanceof $},Dr=function _executeHook(s,i,u){bt[s]&&U(bt[s],(s=>{s.call(DOMPurify,i,u,br)}))},Br=function _sanitizeElements(s){let i=null;if(Dr("beforeSanitizeElements",s,null),Tr(s))return Pr(s),!0;const u=vr(s.nodeName);if(Dr("uponSanitizeElement",s,{tagName:u,allowedTags:jt}),s.hasChildNodes()&&!Rr(s.firstElementChild)&&fe(/<[/\w]/g,s.innerHTML)&&fe(/<[/\w]/g,s.textContent))return Pr(s),!0;if(7===s.nodeType)return Pr(s),!0;if($t&&8===s.nodeType&&fe(/<[/\w]/g,s.data))return Pr(s),!0;if(!jt[u]||Tt[u]){if(!Tt[u]&&Fr(u)){if(Mt.tagNameCheck instanceof RegExp&&fe(Mt.tagNameCheck,u))return!1;if(Mt.tagNameCheck instanceof Function&&Mt.tagNameCheck(u))return!1}if(Yt&&!er[u]){const i=ut(s)||s.parentNode,u=ct(s)||s.childNodes;if(u&&i)for(let _=u.length-1;_>=0;--_){const w=ot(u[_],!0);w.__removalCount=(s.__removalCount||0)+1,i.insertBefore(w,lt(s))}}return Pr(s),!0}return s instanceof We&&!jr(s)?(Pr(s),!0):"noscript"!==u&&"noembed"!==u&&"noframes"!==u||!fe(/<\/no(script|embed|frames)/i,s.innerHTML)?(qt&&3===s.nodeType&&(i=s.textContent,U([_t,Et,wt],(s=>{i=le(i,s," ")})),s.textContent!==i&&(Z(DOMPurify.removed,{element:s.cloneNode()}),s.textContent=i)),Dr("afterSanitizeElements",s,null),!1):(Pr(s),!0)},Lr=function _isValidAttribute(s,i,_){if(Jt&&("id"===i||"name"===i)&&(_ in u||_ in Er))return!1;if(Bt&&!Rt[i]&&fe(St,i));else if(Dt&&fe(xt,i));else if(!It[i]||Rt[i]){if(!(Fr(s)&&(Mt.tagNameCheck instanceof RegExp&&fe(Mt.tagNameCheck,s)||Mt.tagNameCheck instanceof Function&&Mt.tagNameCheck(s))&&(Mt.attributeNameCheck instanceof RegExp&&fe(Mt.attributeNameCheck,i)||Mt.attributeNameCheck instanceof Function&&Mt.attributeNameCheck(i))||"is"===i&&Mt.allowCustomizedBuiltInElements&&(Mt.tagNameCheck instanceof RegExp&&fe(Mt.tagNameCheck,_)||Mt.tagNameCheck instanceof Function&&Mt.tagNameCheck(_))))return!1}else if(sr[i]);else if(fe(At,le(_,Ot,"")));else if("src"!==i&&"xlink:href"!==i&&"href"!==i||"script"===s||0!==ce(_,"data:")||!rr[s])if(Lt&&!fe(kt,le(_,Ot,"")));else if(_)return!1;return!0},Fr=function _isBasicCustomElement(s){return"annotation-xml"!==s&&ae(s,Ct)},qr=function _sanitizeAttributes(s){Dr("beforeSanitizeAttributes",s,null);const{attributes:i}=s;if(!i)return;const u={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:It};let _=i.length;for(;_--;){const w=i[_],{name:x,namespaceURI:j,value:B}=w,L=vr(x);let $="value"===x?B:pe(B);if(u.attrName=L,u.attrValue=$,u.keepAttr=!0,u.forceKeepAttr=void 0,Dr("uponSanitizeAttribute",s,u),$=u.attrValue,u.forceKeepAttr)continue;if(Ir(x,s),!u.keepAttr)continue;if(!Ft&&fe(/\/>/i,$)){Ir(x,s);continue}qt&&U([_t,Et,wt],(s=>{$=le($,s," ")}));const Z=vr(s.nodeName);if(Lr(Z,L,$)){if(!Gt||"id"!==L&&"name"!==L||(Ir(x,s),$=Xt+$),pt&&"object"==typeof tt&&"function"==typeof tt.getAttributeType)if(j);else switch(tt.getAttributeType(Z,L)){case"TrustedHTML":$=pt.createHTML($);break;case"TrustedScriptURL":$=pt.createScriptURL($)}try{j?s.setAttributeNS(j,x,$):s.setAttribute(x,$),Y(DOMPurify.removed)}catch(s){}}}Dr("afterSanitizeAttributes",s,null)},$r=function _sanitizeShadowDOM(s){let i=null;const u=Mr(s);for(Dr("beforeSanitizeShadowDOM",s,null);i=u.nextNode();)Dr("uponSanitizeShadowNode",i,null),Br(i)||(1===i.nodeType&&(i.parentNode&&i.parentNode.__depth?i.__depth=(i.__removalCount||0)+i.parentNode.__depth+1:i.__depth=1),i.__depth>=_r&&Pr(i),i.content instanceof j&&(i.content.__depth=i.__depth,_sanitizeShadowDOM(i.content)),qr(i));Dr("afterSanitizeShadowDOM",s,null)};return DOMPurify.sanitize=function(s){let i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},u=null,w=null,x=null,B=null;if(pr=!s,pr&&(s="\x3c!--\x3e"),"string"!=typeof s&&!Rr(s)){if("function"!=typeof s.toString)throw ye("toString is not a function");if("string"!=typeof(s=s.toString()))throw ye("dirty is not a string, aborting")}if(!DOMPurify.isSupported)return s;if(zt||Sr(i),DOMPurify.removed=[],"string"==typeof s&&(Qt=!1),Qt){if(s.nodeName){const i=vr(s.nodeName);if(!jt[i]||Tt[i])throw ye("root node is forbidden and cannot be sanitized in-place")}}else if(s instanceof $)u=Nr("\x3c!----\x3e"),w=u.ownerDocument.importNode(s,!0),1===w.nodeType&&"BODY"===w.nodeName||"HTML"===w.nodeName?u=w:u.appendChild(w);else{if(!Wt&&!qt&&!Ut&&-1===s.indexOf("<"))return pt&&Ht?pt.createHTML(s):s;if(u=Nr(s),!u)return Wt?null:Ht?ht:""}u&&Vt&&Pr(u.firstChild);const L=Mr(Qt?s:u);for(;x=L.nextNode();)Br(x)||(1===x.nodeType&&(x.parentNode&&x.parentNode.__depth?x.__depth=(x.__removalCount||0)+x.parentNode.__depth+1:x.__depth=1),x.__depth>=_r&&Pr(x),x.content instanceof j&&(x.content.__depth=x.__depth,$r(x.content)),qr(x));if(Qt)return s;if(Wt){if(Kt)for(B=gt.call(u.ownerDocument);u.firstChild;)B.appendChild(u.firstChild);else B=u;return(It.shadowroot||It.shadowrootmode)&&(B=vt.call(_,B,!0)),B}let Y=Ut?u.outerHTML:u.innerHTML;return Ut&&jt["!doctype"]&&u.ownerDocument&&u.ownerDocument.doctype&&u.ownerDocument.doctype.name&&fe(nt,u.ownerDocument.doctype.name)&&(Y="\n"+Y),qt&&U([_t,Et,wt],(s=>{Y=le(Y,s," ")})),pt&&Ht?pt.createHTML(Y):Y},DOMPurify.setConfig=function(){Sr(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),zt=!0},DOMPurify.clearConfig=function(){br=null,zt=!1},DOMPurify.isValidAttribute=function(s,i,u){br||Sr({});const _=vr(s),w=vr(i);return Lr(_,w,u)},DOMPurify.addHook=function(s,i){"function"==typeof i&&(bt[s]=bt[s]||[],Z(bt[s],i))},DOMPurify.removeHook=function(s){if(bt[s])return Y(bt[s])},DOMPurify.removeHooks=function(s){bt[s]&&(bt[s]=[])},DOMPurify.removeAllHooks=function(){bt={}},DOMPurify}return createDOMPurify()}()},78004:s=>{"use strict";class SubRange{constructor(s,i){this.low=s,this.high=i,this.length=1+i-s}overlaps(s){return!(this.highs.high)}touches(s){return!(this.high+1s.high)}add(s){return new SubRange(Math.min(this.low,s.low),Math.max(this.high,s.high))}subtract(s){return s.low<=this.low&&s.high>=this.high?[]:s.low>this.low&&s.highs+i.length),0)}add(s,i){var _add=s=>{for(var i=0;i{for(var i=0;i{for(var i=0;i{for(var u=i.low;u<=i.high;)s.push(u),u++;return s}),[])}subranges(){return this.ranges.map((s=>({low:s.low,high:s.high,length:1+s.high-s.low})))}}s.exports=DRange},30655:(s,i,u)=>{"use strict";var _=u(70453)("%Object.defineProperty%",!0)||!1;if(_)try{_({},"a",{value:1})}catch(s){_=!1}s.exports=_},41237:s=>{"use strict";s.exports=EvalError},69383:s=>{"use strict";s.exports=Error},79290:s=>{"use strict";s.exports=RangeError},79538:s=>{"use strict";s.exports=ReferenceError},58068:s=>{"use strict";s.exports=SyntaxError},69675:s=>{"use strict";s.exports=TypeError},35345:s=>{"use strict";s.exports=URIError},37007:s=>{"use strict";var i,u="object"==typeof Reflect?Reflect:null,_=u&&"function"==typeof u.apply?u.apply:function ReflectApply(s,i,u){return Function.prototype.apply.call(s,i,u)};i=u&&"function"==typeof u.ownKeys?u.ownKeys:Object.getOwnPropertySymbols?function ReflectOwnKeys(s){return Object.getOwnPropertyNames(s).concat(Object.getOwnPropertySymbols(s))}:function ReflectOwnKeys(s){return Object.getOwnPropertyNames(s)};var w=Number.isNaN||function NumberIsNaN(s){return s!=s};function EventEmitter(){EventEmitter.init.call(this)}s.exports=EventEmitter,s.exports.once=function once(s,i){return new Promise((function(u,_){function errorListener(u){s.removeListener(i,resolver),_(u)}function resolver(){"function"==typeof s.removeListener&&s.removeListener("error",errorListener),u([].slice.call(arguments))}eventTargetAgnosticAddListener(s,i,resolver,{once:!0}),"error"!==i&&function addErrorHandlerIfEventEmitter(s,i,u){"function"==typeof s.on&&eventTargetAgnosticAddListener(s,"error",i,u)}(s,errorListener,{once:!0})}))},EventEmitter.EventEmitter=EventEmitter,EventEmitter.prototype._events=void 0,EventEmitter.prototype._eventsCount=0,EventEmitter.prototype._maxListeners=void 0;var x=10;function checkListener(s){if("function"!=typeof s)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof s)}function _getMaxListeners(s){return void 0===s._maxListeners?EventEmitter.defaultMaxListeners:s._maxListeners}function _addListener(s,i,u,_){var w,x,j;if(checkListener(u),void 0===(x=s._events)?(x=s._events=Object.create(null),s._eventsCount=0):(void 0!==x.newListener&&(s.emit("newListener",i,u.listener?u.listener:u),x=s._events),j=x[i]),void 0===j)j=x[i]=u,++s._eventsCount;else if("function"==typeof j?j=x[i]=_?[u,j]:[j,u]:_?j.unshift(u):j.push(u),(w=_getMaxListeners(s))>0&&j.length>w&&!j.warned){j.warned=!0;var B=new Error("Possible EventEmitter memory leak detected. "+j.length+" "+String(i)+" listeners added. Use emitter.setMaxListeners() to increase limit");B.name="MaxListenersExceededWarning",B.emitter=s,B.type=i,B.count=j.length,function ProcessEmitWarning(s){console&&console.warn&&console.warn(s)}(B)}return s}function onceWrapper(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function _onceWrap(s,i,u){var _={fired:!1,wrapFn:void 0,target:s,type:i,listener:u},w=onceWrapper.bind(_);return w.listener=u,_.wrapFn=w,w}function _listeners(s,i,u){var _=s._events;if(void 0===_)return[];var w=_[i];return void 0===w?[]:"function"==typeof w?u?[w.listener||w]:[w]:u?function unwrapListeners(s){for(var i=new Array(s.length),u=0;u0&&(j=i[0]),j instanceof Error)throw j;var B=new Error("Unhandled error."+(j?" ("+j.message+")":""));throw B.context=j,B}var L=x[s];if(void 0===L)return!1;if("function"==typeof L)_(L,this,i);else{var $=L.length,U=arrayClone(L,$);for(u=0;u<$;++u)_(U[u],this,i)}return!0},EventEmitter.prototype.addListener=function addListener(s,i){return _addListener(this,s,i,!1)},EventEmitter.prototype.on=EventEmitter.prototype.addListener,EventEmitter.prototype.prependListener=function prependListener(s,i){return _addListener(this,s,i,!0)},EventEmitter.prototype.once=function once(s,i){return checkListener(i),this.on(s,_onceWrap(this,s,i)),this},EventEmitter.prototype.prependOnceListener=function prependOnceListener(s,i){return checkListener(i),this.prependListener(s,_onceWrap(this,s,i)),this},EventEmitter.prototype.removeListener=function removeListener(s,i){var u,_,w,x,j;if(checkListener(i),void 0===(_=this._events))return this;if(void 0===(u=_[s]))return this;if(u===i||u.listener===i)0==--this._eventsCount?this._events=Object.create(null):(delete _[s],_.removeListener&&this.emit("removeListener",s,u.listener||i));else if("function"!=typeof u){for(w=-1,x=u.length-1;x>=0;x--)if(u[x]===i||u[x].listener===i){j=u[x].listener,w=x;break}if(w<0)return this;0===w?u.shift():function spliceOne(s,i){for(;i+1=0;_--)this.removeListener(s,i[_]);return this},EventEmitter.prototype.listeners=function listeners(s){return _listeners(this,s,!0)},EventEmitter.prototype.rawListeners=function rawListeners(s){return _listeners(this,s,!1)},EventEmitter.listenerCount=function(s,i){return"function"==typeof s.listenerCount?s.listenerCount(i):listenerCount.call(s,i)},EventEmitter.prototype.listenerCount=listenerCount,EventEmitter.prototype.eventNames=function eventNames(){return this._eventsCount>0?i(this._events):[]}},85587:(s,i,u)=>{"use strict";var _=u(26311),w=create(Error);function create(s){return FormattedError.displayName=s.displayName||s.name,FormattedError;function FormattedError(i){return i&&(i=_.apply(null,arguments)),new s(i)}}s.exports=w,w.eval=create(EvalError),w.range=create(RangeError),w.reference=create(ReferenceError),w.syntax=create(SyntaxError),w.type=create(TypeError),w.uri=create(URIError),w.create=create},26311:s=>{!function(){var i;function format(s){for(var i,u,_,w,x=1,j=[].slice.call(arguments),B=0,L=s.length,$="",U=!1,Y=!1,nextArg=function(){return j[x++]},slurpNumber=function(){for(var u="";/\d/.test(s[B]);)u+=s[B++],i=s[B];return u.length>0?parseInt(u):null};B{"use strict";var i=Object.prototype.toString,u=Math.max,_=function concatty(s,i){for(var u=[],_=0;_{"use strict";var _=u(89353);s.exports=Function.prototype.bind||_},70453:(s,i,u)=>{"use strict";var _,w=u(69383),x=u(41237),j=u(79290),B=u(79538),L=u(58068),$=u(69675),U=u(35345),Y=Function,getEvalledConstructor=function(s){try{return Y('"use strict"; return ('+s+").constructor;")()}catch(s){}},Z=Object.getOwnPropertyDescriptor;if(Z)try{Z({},"")}catch(s){Z=null}var throwTypeError=function(){throw new $},ee=Z?function(){try{return throwTypeError}catch(s){try{return Z(arguments,"callee").get}catch(s){return throwTypeError}}}():throwTypeError,ie=u(64039)(),ae=u(80024)(),le=Object.getPrototypeOf||(ae?function(s){return s.__proto__}:null),ce={},pe="undefined"!=typeof Uint8Array&&le?le(Uint8Array):_,de={__proto__:null,"%AggregateError%":"undefined"==typeof AggregateError?_:AggregateError,"%Array%":Array,"%ArrayBuffer%":"undefined"==typeof ArrayBuffer?_:ArrayBuffer,"%ArrayIteratorPrototype%":ie&&le?le([][Symbol.iterator]()):_,"%AsyncFromSyncIteratorPrototype%":_,"%AsyncFunction%":ce,"%AsyncGenerator%":ce,"%AsyncGeneratorFunction%":ce,"%AsyncIteratorPrototype%":ce,"%Atomics%":"undefined"==typeof Atomics?_:Atomics,"%BigInt%":"undefined"==typeof BigInt?_:BigInt,"%BigInt64Array%":"undefined"==typeof BigInt64Array?_:BigInt64Array,"%BigUint64Array%":"undefined"==typeof BigUint64Array?_:BigUint64Array,"%Boolean%":Boolean,"%DataView%":"undefined"==typeof DataView?_:DataView,"%Date%":Date,"%decodeURI%":decodeURI,"%decodeURIComponent%":decodeURIComponent,"%encodeURI%":encodeURI,"%encodeURIComponent%":encodeURIComponent,"%Error%":w,"%eval%":eval,"%EvalError%":x,"%Float32Array%":"undefined"==typeof Float32Array?_:Float32Array,"%Float64Array%":"undefined"==typeof Float64Array?_:Float64Array,"%FinalizationRegistry%":"undefined"==typeof FinalizationRegistry?_:FinalizationRegistry,"%Function%":Y,"%GeneratorFunction%":ce,"%Int8Array%":"undefined"==typeof Int8Array?_:Int8Array,"%Int16Array%":"undefined"==typeof Int16Array?_:Int16Array,"%Int32Array%":"undefined"==typeof Int32Array?_:Int32Array,"%isFinite%":isFinite,"%isNaN%":isNaN,"%IteratorPrototype%":ie&&le?le(le([][Symbol.iterator]())):_,"%JSON%":"object"==typeof JSON?JSON:_,"%Map%":"undefined"==typeof Map?_:Map,"%MapIteratorPrototype%":"undefined"!=typeof Map&&ie&&le?le((new Map)[Symbol.iterator]()):_,"%Math%":Math,"%Number%":Number,"%Object%":Object,"%parseFloat%":parseFloat,"%parseInt%":parseInt,"%Promise%":"undefined"==typeof Promise?_:Promise,"%Proxy%":"undefined"==typeof Proxy?_:Proxy,"%RangeError%":j,"%ReferenceError%":B,"%Reflect%":"undefined"==typeof Reflect?_:Reflect,"%RegExp%":RegExp,"%Set%":"undefined"==typeof Set?_:Set,"%SetIteratorPrototype%":"undefined"!=typeof Set&&ie&&le?le((new Set)[Symbol.iterator]()):_,"%SharedArrayBuffer%":"undefined"==typeof SharedArrayBuffer?_:SharedArrayBuffer,"%String%":String,"%StringIteratorPrototype%":ie&&le?le(""[Symbol.iterator]()):_,"%Symbol%":ie?Symbol:_,"%SyntaxError%":L,"%ThrowTypeError%":ee,"%TypedArray%":pe,"%TypeError%":$,"%Uint8Array%":"undefined"==typeof Uint8Array?_:Uint8Array,"%Uint8ClampedArray%":"undefined"==typeof Uint8ClampedArray?_:Uint8ClampedArray,"%Uint16Array%":"undefined"==typeof Uint16Array?_:Uint16Array,"%Uint32Array%":"undefined"==typeof Uint32Array?_:Uint32Array,"%URIError%":U,"%WeakMap%":"undefined"==typeof WeakMap?_:WeakMap,"%WeakRef%":"undefined"==typeof WeakRef?_:WeakRef,"%WeakSet%":"undefined"==typeof WeakSet?_:WeakSet};if(le)try{null.error}catch(s){var fe=le(le(s));de["%Error.prototype%"]=fe}var ye=function doEval(s){var i;if("%AsyncFunction%"===s)i=getEvalledConstructor("async function () {}");else if("%GeneratorFunction%"===s)i=getEvalledConstructor("function* () {}");else if("%AsyncGeneratorFunction%"===s)i=getEvalledConstructor("async function* () {}");else if("%AsyncGenerator%"===s){var u=doEval("%AsyncGeneratorFunction%");u&&(i=u.prototype)}else if("%AsyncIteratorPrototype%"===s){var _=doEval("%AsyncGenerator%");_&&le&&(i=le(_.prototype))}return de[s]=i,i},be={__proto__:null,"%ArrayBufferPrototype%":["ArrayBuffer","prototype"],"%ArrayPrototype%":["Array","prototype"],"%ArrayProto_entries%":["Array","prototype","entries"],"%ArrayProto_forEach%":["Array","prototype","forEach"],"%ArrayProto_keys%":["Array","prototype","keys"],"%ArrayProto_values%":["Array","prototype","values"],"%AsyncFunctionPrototype%":["AsyncFunction","prototype"],"%AsyncGenerator%":["AsyncGeneratorFunction","prototype"],"%AsyncGeneratorPrototype%":["AsyncGeneratorFunction","prototype","prototype"],"%BooleanPrototype%":["Boolean","prototype"],"%DataViewPrototype%":["DataView","prototype"],"%DatePrototype%":["Date","prototype"],"%ErrorPrototype%":["Error","prototype"],"%EvalErrorPrototype%":["EvalError","prototype"],"%Float32ArrayPrototype%":["Float32Array","prototype"],"%Float64ArrayPrototype%":["Float64Array","prototype"],"%FunctionPrototype%":["Function","prototype"],"%Generator%":["GeneratorFunction","prototype"],"%GeneratorPrototype%":["GeneratorFunction","prototype","prototype"],"%Int8ArrayPrototype%":["Int8Array","prototype"],"%Int16ArrayPrototype%":["Int16Array","prototype"],"%Int32ArrayPrototype%":["Int32Array","prototype"],"%JSONParse%":["JSON","parse"],"%JSONStringify%":["JSON","stringify"],"%MapPrototype%":["Map","prototype"],"%NumberPrototype%":["Number","prototype"],"%ObjectPrototype%":["Object","prototype"],"%ObjProto_toString%":["Object","prototype","toString"],"%ObjProto_valueOf%":["Object","prototype","valueOf"],"%PromisePrototype%":["Promise","prototype"],"%PromiseProto_then%":["Promise","prototype","then"],"%Promise_all%":["Promise","all"],"%Promise_reject%":["Promise","reject"],"%Promise_resolve%":["Promise","resolve"],"%RangeErrorPrototype%":["RangeError","prototype"],"%ReferenceErrorPrototype%":["ReferenceError","prototype"],"%RegExpPrototype%":["RegExp","prototype"],"%SetPrototype%":["Set","prototype"],"%SharedArrayBufferPrototype%":["SharedArrayBuffer","prototype"],"%StringPrototype%":["String","prototype"],"%SymbolPrototype%":["Symbol","prototype"],"%SyntaxErrorPrototype%":["SyntaxError","prototype"],"%TypedArrayPrototype%":["TypedArray","prototype"],"%TypeErrorPrototype%":["TypeError","prototype"],"%Uint8ArrayPrototype%":["Uint8Array","prototype"],"%Uint8ClampedArrayPrototype%":["Uint8ClampedArray","prototype"],"%Uint16ArrayPrototype%":["Uint16Array","prototype"],"%Uint32ArrayPrototype%":["Uint32Array","prototype"],"%URIErrorPrototype%":["URIError","prototype"],"%WeakMapPrototype%":["WeakMap","prototype"],"%WeakSetPrototype%":["WeakSet","prototype"]},_e=u(66743),we=u(9957),Se=_e.call(Function.call,Array.prototype.concat),xe=_e.call(Function.apply,Array.prototype.splice),Pe=_e.call(Function.call,String.prototype.replace),Te=_e.call(Function.call,String.prototype.slice),Re=_e.call(Function.call,RegExp.prototype.exec),qe=/[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g,$e=/\\(\\)?/g,ze=function getBaseIntrinsic(s,i){var u,_=s;if(we(be,_)&&(_="%"+(u=be[_])[0]+"%"),we(de,_)){var w=de[_];if(w===ce&&(w=ye(_)),void 0===w&&!i)throw new $("intrinsic "+s+" exists, but is not available. Please file an issue!");return{alias:u,name:_,value:w}}throw new L("intrinsic "+s+" does not exist!")};s.exports=function GetIntrinsic(s,i){if("string"!=typeof s||0===s.length)throw new $("intrinsic name must be a non-empty string");if(arguments.length>1&&"boolean"!=typeof i)throw new $('"allowMissing" argument must be a boolean');if(null===Re(/^%?[^%]*%?$/,s))throw new L("`%` may not be present anywhere but at the beginning and end of the intrinsic name");var u=function stringToPath(s){var i=Te(s,0,1),u=Te(s,-1);if("%"===i&&"%"!==u)throw new L("invalid intrinsic syntax, expected closing `%`");if("%"===u&&"%"!==i)throw new L("invalid intrinsic syntax, expected opening `%`");var _=[];return Pe(s,qe,(function(s,i,u,w){_[_.length]=u?Pe(w,$e,"$1"):i||s})),_}(s),_=u.length>0?u[0]:"",w=ze("%"+_+"%",i),x=w.name,j=w.value,B=!1,U=w.alias;U&&(_=U[0],xe(u,Se([0,1],U)));for(var Y=1,ee=!0;Y=u.length){var ce=Z(j,ie);j=(ee=!!ce)&&"get"in ce&&!("originalValue"in ce.get)?ce.get:j[ie]}else ee=we(j,ie),j=j[ie];ee&&!B&&(de[x]=j)}}return j}},75795:(s,i,u)=>{"use strict";var _=u(70453)("%Object.getOwnPropertyDescriptor%",!0);if(_)try{_([],"length")}catch(s){_=null}s.exports=_},30592:(s,i,u)=>{"use strict";var _=u(30655),w=function hasPropertyDescriptors(){return!!_};w.hasArrayLengthDefineBug=function hasArrayLengthDefineBug(){if(!_)return null;try{return 1!==_([],"length",{value:1}).length}catch(s){return!0}},s.exports=w},80024:s=>{"use strict";var i={__proto__:null,foo:{}},u=Object;s.exports=function hasProto(){return{__proto__:i}.foo===i.foo&&!(i instanceof u)}},64039:(s,i,u)=>{"use strict";var _="undefined"!=typeof Symbol&&Symbol,w=u(41333);s.exports=function hasNativeSymbols(){return"function"==typeof _&&("function"==typeof Symbol&&("symbol"==typeof _("foo")&&("symbol"==typeof Symbol("bar")&&w())))}},41333:s=>{"use strict";s.exports=function hasSymbols(){if("function"!=typeof Symbol||"function"!=typeof Object.getOwnPropertySymbols)return!1;if("symbol"==typeof Symbol.iterator)return!0;var s={},i=Symbol("test"),u=Object(i);if("string"==typeof i)return!1;if("[object Symbol]"!==Object.prototype.toString.call(i))return!1;if("[object Symbol]"!==Object.prototype.toString.call(u))return!1;for(i in s[i]=42,s)return!1;if("function"==typeof Object.keys&&0!==Object.keys(s).length)return!1;if("function"==typeof Object.getOwnPropertyNames&&0!==Object.getOwnPropertyNames(s).length)return!1;var _=Object.getOwnPropertySymbols(s);if(1!==_.length||_[0]!==i)return!1;if(!Object.prototype.propertyIsEnumerable.call(s,i))return!1;if("function"==typeof Object.getOwnPropertyDescriptor){var w=Object.getOwnPropertyDescriptor(s,i);if(42!==w.value||!0!==w.enumerable)return!1}return!0}},9957:(s,i,u)=>{"use strict";var _=Function.prototype.call,w=Object.prototype.hasOwnProperty,x=u(66743);s.exports=x.call(_,w)},45981:s=>{function deepFreeze(s){return s instanceof Map?s.clear=s.delete=s.set=function(){throw new Error("map is read-only")}:s instanceof Set&&(s.add=s.clear=s.delete=function(){throw new Error("set is read-only")}),Object.freeze(s),Object.getOwnPropertyNames(s).forEach((function(i){var u=s[i];"object"!=typeof u||Object.isFrozen(u)||deepFreeze(u)})),s}var i=deepFreeze,u=deepFreeze;i.default=u;class Response{constructor(s){void 0===s.data&&(s.data={}),this.data=s.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function escapeHTML(s){return s.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function inherit(s,...i){const u=Object.create(null);for(const i in s)u[i]=s[i];return i.forEach((function(s){for(const i in s)u[i]=s[i]})),u}const emitsWrappingTags=s=>!!s.kind;class HTMLRenderer{constructor(s,i){this.buffer="",this.classPrefix=i.classPrefix,s.walk(this)}addText(s){this.buffer+=escapeHTML(s)}openNode(s){if(!emitsWrappingTags(s))return;let i=s.kind;s.sublanguage||(i=`${this.classPrefix}${i}`),this.span(i)}closeNode(s){emitsWrappingTags(s)&&(this.buffer+="")}value(){return this.buffer}span(s){this.buffer+=``}}class TokenTree{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(s){this.top.children.push(s)}openNode(s){const i={kind:s,children:[]};this.add(i),this.stack.push(i)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(s){return this.constructor._walk(s,this.rootNode)}static _walk(s,i){return"string"==typeof i?s.addText(i):i.children&&(s.openNode(i),i.children.forEach((i=>this._walk(s,i))),s.closeNode(i)),s}static _collapse(s){"string"!=typeof s&&s.children&&(s.children.every((s=>"string"==typeof s))?s.children=[s.children.join("")]:s.children.forEach((s=>{TokenTree._collapse(s)})))}}class TokenTreeEmitter extends TokenTree{constructor(s){super(),this.options=s}addKeyword(s,i){""!==s&&(this.openNode(i),this.addText(s),this.closeNode())}addText(s){""!==s&&this.add(s)}addSublanguage(s,i){const u=s.root;u.kind=i,u.sublanguage=!0,this.add(u)}toHTML(){return new HTMLRenderer(this,this.options).value()}finalize(){return!0}}function source(s){return s?"string"==typeof s?s:s.source:null}const _=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;const w="[a-zA-Z]\\w*",x="[a-zA-Z_]\\w*",j="\\b\\d+(\\.\\d+)?",B="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",L="\\b(0b[01]+)",$={begin:"\\\\[\\s\\S]",relevance:0},U={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[$]},Y={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[$]},Z={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},COMMENT=function(s,i,u={}){const _=inherit({className:"comment",begin:s,end:i,contains:[]},u);return _.contains.push(Z),_.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),_},ee=COMMENT("//","$"),ie=COMMENT("/\\*","\\*/"),ae=COMMENT("#","$"),le={className:"number",begin:j,relevance:0},ce={className:"number",begin:B,relevance:0},pe={className:"number",begin:L,relevance:0},de={className:"number",begin:j+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},fe={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[$,{begin:/\[/,end:/\]/,relevance:0,contains:[$]}]}]},ye={className:"title",begin:w,relevance:0},be={className:"title",begin:x,relevance:0},_e={begin:"\\.\\s*"+x,relevance:0};var we=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:w,UNDERSCORE_IDENT_RE:x,NUMBER_RE:j,C_NUMBER_RE:B,BINARY_NUMBER_RE:L,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(s={})=>{const i=/^#![ ]*\//;return s.binary&&(s.begin=function concat(...s){return s.map((s=>source(s))).join("")}(i,/.*\b/,s.binary,/\b.*/)),inherit({className:"meta",begin:i,end:/$/,relevance:0,"on:begin":(s,i)=>{0!==s.index&&i.ignoreMatch()}},s)},BACKSLASH_ESCAPE:$,APOS_STRING_MODE:U,QUOTE_STRING_MODE:Y,PHRASAL_WORDS_MODE:Z,COMMENT,C_LINE_COMMENT_MODE:ee,C_BLOCK_COMMENT_MODE:ie,HASH_COMMENT_MODE:ae,NUMBER_MODE:le,C_NUMBER_MODE:ce,BINARY_NUMBER_MODE:pe,CSS_NUMBER_MODE:de,REGEXP_MODE:fe,TITLE_MODE:ye,UNDERSCORE_TITLE_MODE:be,METHOD_GUARD:_e,END_SAME_AS_BEGIN:function(s){return Object.assign(s,{"on:begin":(s,i)=>{i.data._beginMatch=s[1]},"on:end":(s,i)=>{i.data._beginMatch!==s[1]&&i.ignoreMatch()}})}});function skipIfhasPrecedingDot(s,i){"."===s.input[s.index-1]&&i.ignoreMatch()}function beginKeywords(s,i){i&&s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",s.__beforeBegin=skipIfhasPrecedingDot,s.keywords=s.keywords||s.beginKeywords,delete s.beginKeywords,void 0===s.relevance&&(s.relevance=0))}function compileIllegal(s,i){Array.isArray(s.illegal)&&(s.illegal=function either(...s){return"("+s.map((s=>source(s))).join("|")+")"}(...s.illegal))}function compileMatch(s,i){if(s.match){if(s.begin||s.end)throw new Error("begin & end are not supported with match");s.begin=s.match,delete s.match}}function compileRelevance(s,i){void 0===s.relevance&&(s.relevance=1)}const Se=["of","and","for","in","not","or","if","then","parent","list","value"],xe="keyword";function compileKeywords(s,i,u=xe){const _={};return"string"==typeof s?compileList(u,s.split(" ")):Array.isArray(s)?compileList(u,s):Object.keys(s).forEach((function(u){Object.assign(_,compileKeywords(s[u],i,u))})),_;function compileList(s,u){i&&(u=u.map((s=>s.toLowerCase()))),u.forEach((function(i){const u=i.split("|");_[u[0]]=[s,scoreForKeyword(u[0],u[1])]}))}}function scoreForKeyword(s,i){return i?Number(i):function commonKeyword(s){return Se.includes(s.toLowerCase())}(s)?0:1}function compileLanguage(s,{plugins:i}){function langRe(i,u){return new RegExp(source(i),"m"+(s.case_insensitive?"i":"")+(u?"g":""))}class MultiRegex{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(s,i){i.position=this.position++,this.matchIndexes[this.matchAt]=i,this.regexes.push([i,s]),this.matchAt+=function countMatchGroups(s){return new RegExp(s.toString()+"|").exec("").length-1}(s)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const s=this.regexes.map((s=>s[1]));this.matcherRe=langRe(function join(s,i="|"){let u=0;return s.map((s=>{u+=1;const i=u;let w=source(s),x="";for(;w.length>0;){const s=_.exec(w);if(!s){x+=w;break}x+=w.substring(0,s.index),w=w.substring(s.index+s[0].length),"\\"===s[0][0]&&s[1]?x+="\\"+String(Number(s[1])+i):(x+=s[0],"("===s[0]&&u++)}return x})).map((s=>`(${s})`)).join(i)}(s),!0),this.lastIndex=0}exec(s){this.matcherRe.lastIndex=this.lastIndex;const i=this.matcherRe.exec(s);if(!i)return null;const u=i.findIndex(((s,i)=>i>0&&void 0!==s)),_=this.matchIndexes[u];return i.splice(0,u),Object.assign(i,_)}}class ResumableMultiRegex{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(s){if(this.multiRegexes[s])return this.multiRegexes[s];const i=new MultiRegex;return this.rules.slice(s).forEach((([s,u])=>i.addRule(s,u))),i.compile(),this.multiRegexes[s]=i,i}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(s,i){this.rules.push([s,i]),"begin"===i.type&&this.count++}exec(s){const i=this.getMatcher(this.regexIndex);i.lastIndex=this.lastIndex;let u=i.exec(s);if(this.resumingScanAtSamePosition())if(u&&u.index===this.lastIndex);else{const i=this.getMatcher(0);i.lastIndex=this.lastIndex+1,u=i.exec(s)}return u&&(this.regexIndex+=u.position+1,this.regexIndex===this.count&&this.considerAll()),u}}if(s.compilerExtensions||(s.compilerExtensions=[]),s.contains&&s.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return s.classNameAliases=inherit(s.classNameAliases||{}),function compileMode(i,u){const _=i;if(i.isCompiled)return _;[compileMatch].forEach((s=>s(i,u))),s.compilerExtensions.forEach((s=>s(i,u))),i.__beforeBegin=null,[beginKeywords,compileIllegal,compileRelevance].forEach((s=>s(i,u))),i.isCompiled=!0;let w=null;if("object"==typeof i.keywords&&(w=i.keywords.$pattern,delete i.keywords.$pattern),i.keywords&&(i.keywords=compileKeywords(i.keywords,s.case_insensitive)),i.lexemes&&w)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return w=w||i.lexemes||/\w+/,_.keywordPatternRe=langRe(w,!0),u&&(i.begin||(i.begin=/\B|\b/),_.beginRe=langRe(i.begin),i.endSameAsBegin&&(i.end=i.begin),i.end||i.endsWithParent||(i.end=/\B|\b/),i.end&&(_.endRe=langRe(i.end)),_.terminatorEnd=source(i.end)||"",i.endsWithParent&&u.terminatorEnd&&(_.terminatorEnd+=(i.end?"|":"")+u.terminatorEnd)),i.illegal&&(_.illegalRe=langRe(i.illegal)),i.contains||(i.contains=[]),i.contains=[].concat(...i.contains.map((function(s){return function expandOrCloneMode(s){s.variants&&!s.cachedVariants&&(s.cachedVariants=s.variants.map((function(i){return inherit(s,{variants:null},i)})));if(s.cachedVariants)return s.cachedVariants;if(dependencyOnParent(s))return inherit(s,{starts:s.starts?inherit(s.starts):null});if(Object.isFrozen(s))return inherit(s);return s}("self"===s?i:s)}))),i.contains.forEach((function(s){compileMode(s,_)})),i.starts&&compileMode(i.starts,u),_.matcher=function buildModeRegex(s){const i=new ResumableMultiRegex;return s.contains.forEach((s=>i.addRule(s.begin,{rule:s,type:"begin"}))),s.terminatorEnd&&i.addRule(s.terminatorEnd,{type:"end"}),s.illegal&&i.addRule(s.illegal,{type:"illegal"}),i}(_),_}(s)}function dependencyOnParent(s){return!!s&&(s.endsWithParent||dependencyOnParent(s.starts))}function BuildVuePlugin(s){const i={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!s.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,escapeHTML(this.code);let i={};return this.autoDetect?(i=s.highlightAuto(this.code),this.detectedLanguage=i.language):(i=s.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),i.value},autoDetect(){return!this.language||function hasValueOrEmptyAttribute(s){return Boolean(s||""===s)}(this.autodetect)},ignoreIllegals:()=>!0},render(s){return s("pre",{},[s("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:i,VuePlugin:{install(s){s.component("highlightjs",i)}}}}const Pe={"after:highlightElement":({el:s,result:i,text:u})=>{const _=nodeStream(s);if(!_.length)return;const w=document.createElement("div");w.innerHTML=i.value,i.value=function mergeStreams(s,i,u){let _=0,w="";const x=[];function selectStream(){return s.length&&i.length?s[0].offset!==i[0].offset?s[0].offset"}function close(s){w+=""+tag(s)+">"}function render(s){("start"===s.event?open:close)(s.node)}for(;s.length||i.length;){let i=selectStream();if(w+=escapeHTML(u.substring(_,i[0].offset)),_=i[0].offset,i===s){x.reverse().forEach(close);do{render(i.splice(0,1)[0]),i=selectStream()}while(i===s&&i.length&&i[0].offset===_);x.reverse().forEach(open)}else"start"===i[0].event?x.push(i[0].node):x.pop(),render(i.splice(0,1)[0])}return w+escapeHTML(u.substr(_))}(_,nodeStream(w),u)}};function tag(s){return s.nodeName.toLowerCase()}function nodeStream(s){const i=[];return function _nodeStream(s,u){for(let _=s.firstChild;_;_=_.nextSibling)3===_.nodeType?u+=_.nodeValue.length:1===_.nodeType&&(i.push({event:"start",offset:u,node:_}),u=_nodeStream(_,u),tag(_).match(/br|hr|img|input/)||i.push({event:"stop",offset:u,node:_}));return u}(s,0),i}const Te={},error=s=>{console.error(s)},warn=(s,...i)=>{console.log(`WARN: ${s}`,...i)},deprecated=(s,i)=>{Te[`${s}/${i}`]||(console.log(`Deprecated as of ${s}. ${i}`),Te[`${s}/${i}`]=!0)},Re=escapeHTML,qe=inherit,$e=Symbol("nomatch");var ze=function(s){const u=Object.create(null),_=Object.create(null),w=[];let x=!0;const j=/(^(<[^>]+>|\t|)+|\n)/gm,B="Could not find the language '{}', did you forget to load/include a language module?",L={disableAutodetect:!0,name:"Plain text",contains:[]};let $={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:TokenTreeEmitter};function shouldNotHighlight(s){return $.noHighlightRe.test(s)}function highlight(s,i,u,_){let w="",x="";"object"==typeof i?(w=s,u=i.ignoreIllegals,x=i.language,_=void 0):(deprecated("10.7.0","highlight(lang, code, ...args) has been deprecated."),deprecated("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),x=s,w=i);const j={code:w,language:x};fire("before:highlight",j);const B=j.result?j.result:_highlight(j.language,j.code,u,_);return B.code=j.code,fire("after:highlight",B),B}function _highlight(s,i,_,j){function keywordData(s,i){const u=U.case_insensitive?i[0].toLowerCase():i[0];return Object.prototype.hasOwnProperty.call(s.keywords,u)&&s.keywords[u]}function processBuffer(){null!=ee.subLanguage?function processSubLanguage(){if(""===le)return;let s=null;if("string"==typeof ee.subLanguage){if(!u[ee.subLanguage])return void ae.addText(le);s=_highlight(ee.subLanguage,le,!0,ie[ee.subLanguage]),ie[ee.subLanguage]=s.top}else s=highlightAuto(le,ee.subLanguage.length?ee.subLanguage:null);ee.relevance>0&&(ce+=s.relevance),ae.addSublanguage(s.emitter,s.language)}():function processKeywords(){if(!ee.keywords)return void ae.addText(le);let s=0;ee.keywordPatternRe.lastIndex=0;let i=ee.keywordPatternRe.exec(le),u="";for(;i;){u+=le.substring(s,i.index);const _=keywordData(ee,i);if(_){const[s,w]=_;if(ae.addText(u),u="",ce+=w,s.startsWith("_"))u+=i[0];else{const u=U.classNameAliases[s]||s;ae.addKeyword(i[0],u)}}else u+=i[0];s=ee.keywordPatternRe.lastIndex,i=ee.keywordPatternRe.exec(le)}u+=le.substr(s),ae.addText(u)}(),le=""}function startNewMode(s){return s.className&&ae.openNode(U.classNameAliases[s.className]||s.className),ee=Object.create(s,{parent:{value:ee}}),ee}function endOfMode(s,i,u){let _=function startsWith(s,i){const u=s&&s.exec(i);return u&&0===u.index}(s.endRe,u);if(_){if(s["on:end"]){const u=new Response(s);s["on:end"](i,u),u.isMatchIgnored&&(_=!1)}if(_){for(;s.endsParent&&s.parent;)s=s.parent;return s}}if(s.endsWithParent)return endOfMode(s.parent,i,u)}function doIgnore(s){return 0===ee.matcher.regexIndex?(le+=s[0],1):(fe=!0,0)}function doBeginMatch(s){const i=s[0],u=s.rule,_=new Response(u),w=[u.__beforeBegin,u["on:begin"]];for(const u of w)if(u&&(u(s,_),_.isMatchIgnored))return doIgnore(i);return u&&u.endSameAsBegin&&(u.endRe=function escape(s){return new RegExp(s.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")}(i)),u.skip?le+=i:(u.excludeBegin&&(le+=i),processBuffer(),u.returnBegin||u.excludeBegin||(le=i)),startNewMode(u),u.returnBegin?0:i.length}function doEndMatch(s){const u=s[0],_=i.substr(s.index),w=endOfMode(ee,s,_);if(!w)return $e;const x=ee;x.skip?le+=u:(x.returnEnd||x.excludeEnd||(le+=u),processBuffer(),x.excludeEnd&&(le=u));do{ee.className&&ae.closeNode(),ee.skip||ee.subLanguage||(ce+=ee.relevance),ee=ee.parent}while(ee!==w.parent);return w.starts&&(w.endSameAsBegin&&(w.starts.endRe=w.endRe),startNewMode(w.starts)),x.returnEnd?0:u.length}let L={};function processLexeme(u,w){const j=w&&w[0];if(le+=u,null==j)return processBuffer(),0;if("begin"===L.type&&"end"===w.type&&L.index===w.index&&""===j){if(le+=i.slice(w.index,w.index+1),!x){const i=new Error("0 width match regex");throw i.languageName=s,i.badRule=L.rule,i}return 1}if(L=w,"begin"===w.type)return doBeginMatch(w);if("illegal"===w.type&&!_){const s=new Error('Illegal lexeme "'+j+'" for mode "'+(ee.className||"")+'"');throw s.mode=ee,s}if("end"===w.type){const s=doEndMatch(w);if(s!==$e)return s}if("illegal"===w.type&&""===j)return 1;if(de>1e5&&de>3*w.index){throw new Error("potential infinite loop, way more iterations than matches")}return le+=j,j.length}const U=getLanguage(s);if(!U)throw error(B.replace("{}",s)),new Error('Unknown language: "'+s+'"');const Y=compileLanguage(U,{plugins:w});let Z="",ee=j||Y;const ie={},ae=new $.__emitter($);!function processContinuations(){const s=[];for(let i=ee;i!==U;i=i.parent)i.className&&s.unshift(i.className);s.forEach((s=>ae.openNode(s)))}();let le="",ce=0,pe=0,de=0,fe=!1;try{for(ee.matcher.considerAll();;){de++,fe?fe=!1:ee.matcher.considerAll(),ee.matcher.lastIndex=pe;const s=ee.matcher.exec(i);if(!s)break;const u=processLexeme(i.substring(pe,s.index),s);pe=s.index+u}return processLexeme(i.substr(pe)),ae.closeAllNodes(),ae.finalize(),Z=ae.toHTML(),{relevance:Math.floor(ce),value:Z,language:s,illegal:!1,emitter:ae,top:ee}}catch(u){if(u.message&&u.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:u.message,context:i.slice(pe-100,pe+100),mode:u.mode},sofar:Z,relevance:0,value:Re(i),emitter:ae};if(x)return{illegal:!1,relevance:0,value:Re(i),emitter:ae,language:s,top:ee,errorRaised:u};throw u}}function highlightAuto(s,i){i=i||$.languages||Object.keys(u);const _=function justTextHighlightResult(s){const i={relevance:0,emitter:new $.__emitter($),value:Re(s),illegal:!1,top:L};return i.emitter.addText(s),i}(s),w=i.filter(getLanguage).filter(autoDetection).map((i=>_highlight(i,s,!1)));w.unshift(_);const x=w.sort(((s,i)=>{if(s.relevance!==i.relevance)return i.relevance-s.relevance;if(s.language&&i.language){if(getLanguage(s.language).supersetOf===i.language)return 1;if(getLanguage(i.language).supersetOf===s.language)return-1}return 0})),[j,B]=x,U=j;return U.second_best=B,U}const U={"before:highlightElement":({el:s})=>{$.useBR&&(s.innerHTML=s.innerHTML.replace(/\n/g,"").replace(/
/g,"\n"))},"after:highlightElement":({result:s})=>{$.useBR&&(s.value=s.value.replace(/\n/g,"
"))}},Y=/^(<[^>]+>|\t)+/gm,Z={"after:highlightElement":({result:s})=>{$.tabReplace&&(s.value=s.value.replace(Y,(s=>s.replace(/\t/g,$.tabReplace))))}};function highlightElement(s){let i=null;const u=function blockLanguage(s){let i=s.className+" ";i+=s.parentNode?s.parentNode.className:"";const u=$.languageDetectRe.exec(i);if(u){const i=getLanguage(u[1]);return i||(warn(B.replace("{}",u[1])),warn("Falling back to no-highlight mode for this block.",s)),i?u[1]:"no-highlight"}return i.split(/\s+/).find((s=>shouldNotHighlight(s)||getLanguage(s)))}(s);if(shouldNotHighlight(u))return;fire("before:highlightElement",{el:s,language:u}),i=s;const w=i.textContent,x=u?highlight(w,{language:u,ignoreIllegals:!0}):highlightAuto(w);fire("after:highlightElement",{el:s,result:x,text:w}),s.innerHTML=x.value,function updateClassName(s,i,u){const w=i?_[i]:u;s.classList.add("hljs"),w&&s.classList.add(w)}(s,u,x.language),s.result={language:x.language,re:x.relevance,relavance:x.relevance},x.second_best&&(s.second_best={language:x.second_best.language,re:x.second_best.relevance,relavance:x.second_best.relevance})}const initHighlighting=()=>{if(initHighlighting.called)return;initHighlighting.called=!0,deprecated("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead.");document.querySelectorAll("pre code").forEach(highlightElement)};let ee=!1;function highlightAll(){if("loading"===document.readyState)return void(ee=!0);document.querySelectorAll("pre code").forEach(highlightElement)}function getLanguage(s){return s=(s||"").toLowerCase(),u[s]||u[_[s]]}function registerAliases(s,{languageName:i}){"string"==typeof s&&(s=[s]),s.forEach((s=>{_[s.toLowerCase()]=i}))}function autoDetection(s){const i=getLanguage(s);return i&&!i.disableAutodetect}function fire(s,i){const u=s;w.forEach((function(s){s[u]&&s[u](i)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(function boot(){ee&&highlightAll()}),!1),Object.assign(s,{highlight,highlightAuto,highlightAll,fixMarkup:function deprecateFixMarkup(s){return deprecated("10.2.0","fixMarkup will be removed entirely in v11.0"),deprecated("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),function fixMarkup(s){return $.tabReplace||$.useBR?s.replace(j,(s=>"\n"===s?$.useBR?"
":s:$.tabReplace?s.replace(/\t/g,$.tabReplace):s)):s}(s)},highlightElement,highlightBlock:function deprecateHighlightBlock(s){return deprecated("10.7.0","highlightBlock will be removed entirely in v12.0"),deprecated("10.7.0","Please use highlightElement now."),highlightElement(s)},configure:function configure(s){s.useBR&&(deprecated("10.3.0","'useBR' will be removed entirely in v11.0"),deprecated("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),$=qe($,s)},initHighlighting,initHighlightingOnLoad:function initHighlightingOnLoad(){deprecated("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),ee=!0},registerLanguage:function registerLanguage(i,_){let w=null;try{w=_(s)}catch(s){if(error("Language definition for '{}' could not be registered.".replace("{}",i)),!x)throw s;error(s),w=L}w.name||(w.name=i),u[i]=w,w.rawDefinition=_.bind(null,s),w.aliases&®isterAliases(w.aliases,{languageName:i})},unregisterLanguage:function unregisterLanguage(s){delete u[s];for(const i of Object.keys(_))_[i]===s&&delete _[i]},listLanguages:function listLanguages(){return Object.keys(u)},getLanguage,registerAliases,requireLanguage:function requireLanguage(s){deprecated("10.4.0","requireLanguage will be removed entirely in v11."),deprecated("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");const i=getLanguage(s);if(i)return i;throw new Error("The '{}' language is required, but not loaded.".replace("{}",s))},autoDetection,inherit:qe,addPlugin:function addPlugin(s){!function upgradePluginAPI(s){s["before:highlightBlock"]&&!s["before:highlightElement"]&&(s["before:highlightElement"]=i=>{s["before:highlightBlock"](Object.assign({block:i.el},i))}),s["after:highlightBlock"]&&!s["after:highlightElement"]&&(s["after:highlightElement"]=i=>{s["after:highlightBlock"](Object.assign({block:i.el},i))})}(s),w.push(s)},vuePlugin:BuildVuePlugin(s).VuePlugin}),s.debugMode=function(){x=!1},s.safeMode=function(){x=!0},s.versionString="10.7.3";for(const s in we)"object"==typeof we[s]&&i(we[s]);return Object.assign(s,we),s.addPlugin(U),s.addPlugin(Pe),s.addPlugin(Z),s}({});s.exports=ze},35344:s=>{function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function bash(s){const i={},u={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[i]}]};Object.assign(i,{className:"variable",variants:[{begin:concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},u]});const _={className:"subst",begin:/\$\(/,end:/\)/,contains:[s.BACKSLASH_ESCAPE]},w={begin:/<<-?\s*(?=\w+)/,starts:{contains:[s.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},x={className:"string",begin:/"/,end:/"/,contains:[s.BACKSLASH_ESCAPE,i,_]};_.contains.push(x);const j={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},s.NUMBER_MODE,i]},B=s.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),L={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[s.inherit(s.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[B,s.SHEBANG(),L,j,s.HASH_COMMENT_MODE,w,x,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},i]}}},73402:s=>{function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function http(s){const i="HTTP/(2|1\\.[01])",u={className:"attribute",begin:concat("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},_=[u,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+i+" \\d{3})",end:/$/,contains:[{className:"meta",begin:i},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:_}},{begin:"(?=^[A-Z]+ (.*?) "+i+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:i},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:_}},s.inherit(u,{relevance:0})]}}},95089:s=>{const i="[A-Za-z$_][0-9A-Za-z$_]*",u=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],_=["true","false","null","undefined","NaN","Infinity"],w=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function lookahead(s){return concat("(?=",s,")")}function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function javascript(s){const x=i,j="<>",B=">",L={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(s,i)=>{const u=s[0].length+s.index,_=s.input[u];"<"!==_?">"===_&&(((s,{after:i})=>{const u=""+s[0].slice(1);return-1!==s.input.indexOf(u,i)})(s,{after:u})||i.ignoreMatch()):i.ignoreMatch()}},$={$pattern:i,keyword:u,literal:_,built_in:w},U="[0-9](_?[0-9])*",Y=`\\.(${U})`,Z="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",ee={className:"number",variants:[{begin:`(\\b(${Z})((${Y})|\\.)?|(${Y}))[eE][+-]?(${U})\\b`},{begin:`\\b(${Z})\\b((${Y})\\b|\\.)?|(${Y})\\b`},{begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{begin:"\\b0[0-7]+n?\\b"}],relevance:0},ie={className:"subst",begin:"\\$\\{",end:"\\}",keywords:$,contains:[]},ae={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[s.BACKSLASH_ESCAPE,ie],subLanguage:"xml"}},le={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[s.BACKSLASH_ESCAPE,ie],subLanguage:"css"}},ce={className:"string",begin:"`",end:"`",contains:[s.BACKSLASH_ESCAPE,ie]},pe={className:"comment",variants:[s.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:x+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),s.C_BLOCK_COMMENT_MODE,s.C_LINE_COMMENT_MODE]},de=[s.APOS_STRING_MODE,s.QUOTE_STRING_MODE,ae,le,ce,ee,s.REGEXP_MODE];ie.contains=de.concat({begin:/\{/,end:/\}/,keywords:$,contains:["self"].concat(de)});const fe=[].concat(pe,ie.contains),ye=fe.concat([{begin:/\(/,end:/\)/,keywords:$,contains:["self"].concat(fe)}]),be={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:$,contains:ye};return{name:"Javascript",aliases:["js","jsx","mjs","cjs"],keywords:$,exports:{PARAMS_CONTAINS:ye},illegal:/#(?![$_A-z])/,contains:[s.SHEBANG({label:"shebang",binary:"node",relevance:5}),{label:"use_strict",className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},s.APOS_STRING_MODE,s.QUOTE_STRING_MODE,ae,le,ce,pe,ee,{begin:concat(/[{,\n]\s*/,lookahead(concat(/(((\/\/.*$)|(\/\*(\*[^/]|[^*])*\*\/))\s*)*/,x+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:x+lookahead("\\s*:"),relevance:0}]},{begin:"("+s.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[pe,s.REGEXP_MODE,{className:"function",begin:"(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+s.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:s.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:$,contains:ye}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:j,end:B},{begin:L.begin,"on:begin":L.isTrulyOpeningTag,end:L.end}],subLanguage:"xml",contains:[{begin:L.begin,end:L.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:$,contains:["self",s.inherit(s.TITLE_MODE,{begin:x}),be],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:s.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[be,s.inherit(s.TITLE_MODE,{begin:x})]},{variants:[{begin:"\\."+x},{begin:"\\$"+x}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},s.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[s.inherit(s.TITLE_MODE,{begin:x}),"self",be]},{begin:"(get|set)\\s+(?="+x+"\\()",end:/\{/,keywords:"get set",contains:[s.inherit(s.TITLE_MODE,{begin:x}),{begin:/\(\)/},be]},{begin:/\$[(.]/}]}}},65772:s=>{s.exports=function json(s){const i={literal:"true false null"},u=[s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE],_=[s.QUOTE_STRING_MODE,s.C_NUMBER_MODE],w={end:",",endsWithParent:!0,excludeEnd:!0,contains:_,keywords:i},x={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[s.BACKSLASH_ESCAPE],illegal:"\\n"},s.inherit(w,{begin:/:/})].concat(u),illegal:"\\S"},j={begin:"\\[",end:"\\]",contains:[s.inherit(w)],illegal:"\\S"};return _.push(x,j),u.forEach((function(s){_.push(s)})),{name:"JSON",contains:_,keywords:i,illegal:"\\S"}}},26571:s=>{s.exports=function powershell(s){const i={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},u={begin:"`[\\s\\S]",relevance:0},_={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},w={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[u,_,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},x={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},j=s.inherit(s.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),B={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},L={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[s.TITLE_MODE]},$={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[_]}]},U={begin:/using\s/,end:/$/,returnBegin:!0,contains:[w,x,{className:"keyword",begin:/(using|assembly|command|module|namespace|type)/}]},Y={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},Z={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(i.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},s.inherit(s.TITLE_MODE,{endsParent:!0})]},ee=[Z,j,u,s.NUMBER_MODE,w,x,B,_,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}],ie={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",ee,{begin:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};return Z.contains.unshift(ie),{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:i,contains:ee.concat(L,$,U,Y,ie)}}},17285:s=>{function source(s){return s?"string"==typeof s?s:s.source:null}function lookahead(s){return concat("(?=",s,")")}function concat(...s){return s.map((s=>source(s))).join("")}function either(...s){return"("+s.map((s=>source(s))).join("|")+")"}s.exports=function xml(s){const i=concat(/[A-Z_]/,function optional(s){return concat("(",s,")?")}(/[A-Z0-9_.-]*:/),/[A-Z0-9_.-]*/),u={className:"symbol",begin:/&[a-z]+;|[0-9]+;|[a-f0-9]+;/},_={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},w=s.inherit(_,{begin:/\(/,end:/\)/}),x=s.inherit(s.APOS_STRING_MODE,{className:"meta-string"}),j=s.inherit(s.QUOTE_STRING_MODE,{className:"meta-string"}),B={endsWithParent:!0,illegal:/,relevance:0,contains:[{className:"attr",begin:/[A-Za-z0-9._:-]+/,relevance:0},{begin:/=\s*/,relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/,end:/"/,contains:[u]},{begin:/'/,end:/'/,contains:[u]},{begin:/[^\s"'=<>`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[_,j,x,w,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[_,w,j,x]}]}]},s.COMMENT(//,{relevance:10}),{begin://,relevance:10},u,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/