From e29f6857dbb2cbaf2c6612190e5ebf1e29f4901d Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Wed, 22 Mar 2023 19:02:54 +0100 Subject: [PATCH] EventManager: add IDP login trigger and check account action Signed-off-by: Nicola Murino --- docs/eventmanager.md | 3 + docs/oidc.md | 3 + go.mod | 64 +++--- go.sum | 126 ++++++------ internal/acme/acme.go | 5 +- internal/common/actions_test.go | 2 +- internal/common/common_test.go | 2 +- internal/common/connection_test.go | 4 +- internal/common/eventmanager.go | 297 ++++++++++++++++++++++++--- internal/common/eventmanager_test.go | 231 ++++++++++++++++++--- internal/common/protocol_test.go | 280 +++++++++++++++++++++++++ internal/dataprovider/bolt.go | 60 +++--- internal/dataprovider/eventrule.go | 182 +++++++++++++--- internal/dataprovider/memory.go | 62 +++--- internal/dataprovider/sqlcommon.go | 2 +- internal/dataprovider/sqlite.go | 2 +- internal/dataprovider/sqlqueries.go | 4 +- internal/ftpd/handler.go | 16 +- internal/ftpd/internal_test.go | 10 +- internal/httpd/api_maintenance.go | 4 +- internal/httpd/auth_utils.go | 1 + internal/httpd/flash.go | 1 + internal/httpd/handler.go | 6 +- internal/httpd/httpd_test.go | 118 ++++++++++- internal/httpd/internal_test.go | 4 +- internal/httpd/oidc.go | 49 +++-- internal/httpd/oidc_test.go | 182 +++++++++++++++- internal/httpd/webadmin.go | 21 ++ internal/httpdtest/httpdtest.go | 19 ++ internal/kms/builtin.go | 2 +- internal/kms/local.go | 2 +- internal/logger/hclog.go | 4 +- internal/logger/request_logger.go | 2 +- internal/plugin/plugin.go | 2 +- internal/service/service.go | 2 +- internal/sftpd/internal_test.go | 4 +- internal/sftpd/transfer.go | 2 +- internal/smtp/smtp.go | 3 + internal/util/util.go | 15 +- internal/vfs/azblobfs.go | 18 +- internal/vfs/cryptfs.go | 2 +- internal/vfs/gcsfs.go | 18 +- internal/vfs/httpfs.go | 12 +- internal/vfs/osfs.go | 4 +- internal/vfs/s3fs.go | 16 +- internal/vfs/sftpfs.go | 2 +- internal/vfs/sys_unix.go | 2 +- internal/webdavd/file.go | 4 +- internal/webdavd/handler.go | 10 +- internal/webdavd/internal_test.go | 4 +- openapi/openapi.yaml | 37 ++++ pkgs/build.sh | 2 +- templates/webadmin/eventaction.html | 38 ++++ templates/webadmin/eventrule.html | 18 +- tests/eventsearcher/go.mod | 10 +- tests/eventsearcher/go.sum | 20 +- tests/ipfilter/go.mod | 10 +- tests/ipfilter/go.sum | 20 +- 58 files changed, 1660 insertions(+), 385 deletions(-) diff --git a/docs/eventmanager.md b/docs/eventmanager.md index be397393..4900ab7e 100644 --- a/docs/eventmanager.md +++ b/docs/eventmanager.md @@ -15,6 +15,7 @@ The following actions are supported: - `Metadata check`. A metadata check requires a metadata plugin such as [this one](https://github.com/sftpgo/sftpgo-plugin-metadata) and removes the metadata associated to missing items (for example objects deleted outside SFTPGo). A metadata check does nothing is no metadata plugin is installed or external metadata are not supported for a filesystem. - `Password expiration check`. You can send an email notification to users whose password is about to expire. - `User expiration check`. You can receive notifications with expired users. +- `Identity Provider account check`. You can create/update accounts for users/admins logging in using an Identity Provider. - `Filesystem`. For these actions, the required permissions are automatically granted. This is the same as executing the actions from an SFTP client and the same restrictions applies. Supported actions: - `Rename`. You can rename one or more files or directories. - `Delete`. You can delete one or more files and directories. @@ -47,6 +48,7 @@ The following placeholders are supported: - `{{Timestamp}}`. Event timestamp as nanoseconds since epoch. - `{{ObjectData}}`. Provider object data serialized as JSON with sensitive fields removed. - `{{RetentionReports}}`. Data retention reports as zip compressed CSV files. Supported as email attachment, file path for multipart HTTP request and as single parameter for HTTP requests body. Data retention reports contain details on the number of files deleted and the total size deleted for each folder. +- `{{IDPField}}`. Identity Provider custom fields containing a string. Event rules are based on the premise that an event occours. To each rule you can associate one or more actions. The following trigger events are supported: @@ -57,6 +59,7 @@ The following trigger events are supported: - `IP Blocked`, this event can be generated if you enable the [defender](./defender.md). - `Certificate`, this event is generated when a certificate is renewed using the built-in ACME protocol. Both successful and failed renewals are notified. - `On demand`, this trigger is generated manually using the WebAdmin or the REST API. +- `Identity Provider login`, this trigger is generated when a user/admin logs in using an external Identity Provider. You can further restrict a rule by specifying additional conditions that must be met before the rule’s actions are taken. For example you can react to uploads only if they are performed by a particular user or using a specified protocol. diff --git a/docs/oidc.md b/docs/oidc.md index 3e9d7350..ae363fbd 100644 --- a/docs/oidc.md +++ b/docs/oidc.md @@ -121,6 +121,7 @@ And the following is an example ID token which allows the SFTPGo user `user1` to ``` SFTPGo users (not admins) can be created/updated after successful OpenID authentication by defining a [pre-login hook](./dynamic-user-mod.md). +Users and admins can also be created/updated after successful OpenID authentication using the [EventManager](./eventmanager.md). You can use `scopes` configuration to request additional information (claims) about authenticated users (See your provider's own documentation for more information). By default the scopes `"openid", "profile", "email"` are retrieved. The `custom_fields` configuration parameter can be used to define claim field names to pass to the pre-login hook, @@ -165,3 +166,5 @@ The pre-login hook will receive a JSON serialized user with the following field: }, ... ``` + +In EventManager actions you can use the placeholder `{{IDPFieldsftpgo_home_dir}}` for string-based custom fields. diff --git a/go.mod b/go.mod index 9d54a90c..49723622 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,22 @@ module github.com/drakkan/sftpgo/v2 go 1.20 require ( - cloud.google.com/go/storage v1.29.0 + cloud.google.com/go/storage v1.30.1 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 - github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 + github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736 - github.com/aws/aws-sdk-go-v2 v1.17.6 - github.com/aws/aws-sdk-go-v2/config v1.18.17 - github.com/aws/aws-sdk-go-v2/credentials v1.13.17 - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.57 - github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.6 - github.com/aws/aws-sdk-go-v2/service/s3 v1.30.6 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 + github.com/aws/aws-sdk-go-v2 v1.17.7 + github.com/aws/aws-sdk-go-v2/config v1.18.19 + github.com/aws/aws-sdk-go-v2/credentials v1.13.18 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.59 + github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.7 + github.com/aws/aws-sdk-go-v2/service/s3 v1.31.0 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 github.com/bmatcuk/doublestar/v4 v4.6.0 - github.com/cockroachdb/cockroach-go/v2 v2.3.2 + github.com/cockroachdb/cockroach-go/v2 v2.3.3 github.com/coreos/go-oidc/v3 v3.5.0 github.com/drakkan/webdav v0.0.0-20230227175313-32996838bcd8 github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 @@ -32,13 +32,13 @@ require ( github.com/golang/mock v1.6.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.3.0 - github.com/hashicorp/go-hclog v1.4.0 + github.com/hashicorp/go-hclog v1.5.0 github.com/hashicorp/go-plugin v1.4.10-0.20230306173702-d78f3fc2891d github.com/hashicorp/go-retryablehttp v0.7.2 github.com/jackc/pgx/v5 v5.3.2-0.20230311213408-9ae852eb583d github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126 github.com/klauspost/compress v1.16.3 - github.com/lestrrat-go/jwx/v2 v2.0.8 + github.com/lestrrat-go/jwx/v2 v2.0.9 github.com/lithammer/shortuuid/v3 v3.0.7 github.com/mattn/go-sqlite3 v1.14.16 github.com/mhale/smtpd v0.8.0 @@ -62,10 +62,10 @@ require ( github.com/subosito/gotenv v1.4.2 github.com/unrolled/secure v1.13.0 github.com/wagslane/go-password-validator v0.3.0 - github.com/wneessen/go-mail v0.3.8 + github.com/wneessen/go-mail v0.3.9 github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a go.etcd.io/bbolt v1.3.7 - go.uber.org/automaxprocs v1.5.1 + go.uber.org/automaxprocs v1.5.2 gocloud.dev v0.29.0 golang.org/x/crypto v0.7.0 golang.org/x/net v0.8.0 @@ -73,7 +73,7 @@ require ( golang.org/x/sys v0.6.0 golang.org/x/term v0.6.0 golang.org/x/time v0.3.0 - google.golang.org/api v0.112.0 + google.golang.org/api v0.114.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) @@ -81,20 +81,20 @@ require ( cloud.google.com/go v0.110.0 // indirect cloud.google.com/go/compute v1.18.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.12.0 // indirect + cloud.google.com/go/iam v0.13.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0 // indirect github.com/ajg/form v1.5.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.23 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.25 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.24 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.26 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1 // indirect @@ -108,7 +108,7 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/goccy/go-json v0.10.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect @@ -131,7 +131,7 @@ require ( github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de // 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.17 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.52 // indirect github.com/minio/sha256-simd v1.0.0 // indirect @@ -157,9 +157,9 @@ require ( golang.org/x/tools v0.7.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect - google.golang.org/grpc v1.53.0 // indirect - google.golang.org/protobuf v1.29.1 // indirect + google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9192d337..8cc8e483 100644 --- a/go.sum +++ b/go.sum @@ -217,8 +217,8 @@ cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHD cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= cloud.google.com/go/iam v0.10.0/go.mod h1:nXAECrMt2qHpF6RZUZseteD6QyanL68reN4OXPw0UWM= -cloud.google.com/go/iam v0.12.0 h1:DRtTY29b75ciH6Ov1PHb4/iat2CLCvrOm40Q0a6DFpE= -cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= @@ -368,8 +368,9 @@ cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= -cloud.google.com/go/storage v1.29.0 h1:6weCgzRvMg7lzuUurI4697AqIRPU1SvzHhynwpW31jI= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= +cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= @@ -478,8 +479,8 @@ github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1/go.mod h1:4qFo github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw= -github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo= +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/cloudsql-proxy v1.33.2/go.mod h1:uqoR4sJc63p7ugW8a/vsEspOsNuehbi7ptS2CHCyOnY= github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= @@ -556,67 +557,67 @@ github.com/aws/aws-sdk-go v1.44.187/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8 github.com/aws/aws-sdk-go v1.44.200/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2 v1.17.6 h1:Y773UK7OBqhzi5VDXMi1zVGsoj+CVHs2eaC2bDsLwi0= -github.com/aws/aws-sdk-go-v2 v1.17.6/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg= +github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno= github.com/aws/aws-sdk-go-v2/config v1.18.12/go.mod h1:J36fOhj1LQBr+O4hJCiT8FwVvieeoSGOtPuvhKlsNu8= -github.com/aws/aws-sdk-go-v2/config v1.18.17 h1:jwTkhULSrbr/SQA8tfdYqZxpG8YsRycmIXxJcbrqY5E= -github.com/aws/aws-sdk-go-v2/config v1.18.17/go.mod h1:Lj3E7XcxJnxMa+AYo89YiL68s1cFJRGduChynYU67VA= +github.com/aws/aws-sdk-go-v2/config v1.18.19 h1:AqFK6zFNtq4i1EYu+eC7lcKHYnZagMn6SW171la0bGw= +github.com/aws/aws-sdk-go-v2/config v1.18.19/go.mod h1:XvTmGMY8d52ougvakOv1RpiTLPz9dlG/OQHsKU/cMmY= github.com/aws/aws-sdk-go-v2/credentials v1.13.12/go.mod h1:37HG2MBroXK3jXfxVGtbM2J48ra2+Ltu+tmwr/jO0KA= -github.com/aws/aws-sdk-go-v2/credentials v1.13.17 h1:IubQO/RNeIVKF5Jy77w/LfUvmmCxTnk2TP1UZZIMiF4= -github.com/aws/aws-sdk-go-v2/credentials v1.13.17/go.mod h1:K9xeFo1g/YPMguMUD69YpwB4Nyi6W/5wn706xIInJFg= +github.com/aws/aws-sdk-go-v2/credentials v1.13.18 h1:EQMdtHwz0ILTW1hoP+EwuWhwCG1hD6l3+RWFQABET4c= +github.com/aws/aws-sdk-go-v2/credentials v1.13.18/go.mod h1:vnwlwjIe+3XJPBYKu1et30ZPABG3VaXJYr8ryohpIyM= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22/go.mod h1:YGSIJyQ6D6FjKMQh16hVFSIUD54L4F7zTGePqYMYYJU= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0 h1:/2Cb3SK3xVOQA7Xfr5nCWCo5H3UiNINtsVvVdk8sQqA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0/go.mod h1:neYVaeKr5eT7BzwULuG2YbLhzWZ22lpjKdCybR7AXrQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 h1:gt57MN3liKiyGopcqgNzJb2+d9MJaKT/q1OksHNXVE4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1/go.mod h1:lfUx8puBRdM5lVVMQlwt2v+ofiG/X6Ms+dy0UkG/kXw= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.51/go.mod h1:7Grl2gV+dx9SWrUIgwwlUvU40t7+lOSbx34XwfmsTkY= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.57 h1:ubKS0iZH5veiqb44qeHzaoKNPvCZQeBVFw4JDhfeWjk= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.57/go.mod h1:dRBjXtcjmYglxVHpdoGGVWvZumDC27I2GLDGI0Uw4RQ= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.59 h1:E3Y+OfzOK1+rmRo/K2G0ml8Vs+Xqk0kOnf4nS0kUtBc= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.59/go.mod h1:1M4PLSBUVfBI0aP+C9XI7SM6kZPCGYyI6izWz0TGprE= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28/go.mod h1:3lwChorpIM/BhImY/hy+Z6jekmN92cXGPI1QJasVPYY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 h1:y+8n9AGDjikyXoMBTRaHHHSaFEB8267ykmvyPodJfys= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30/go.mod h1:LUBAO3zNXQjoONBKn/kR1y0Q4cj/D02Ts0uHYjcCQLM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 h1:sJLYcS+eZn5EeNINGHSCRAwUJMFVqklwkH36Vbyai7M= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31/go.mod h1:QT0BqUvX1Bh2ABdTGnjqEjvjzrCfIniM9Sc8zn9Yndo= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22/go.mod h1:EqK7gVrIGAHyZItrD1D8B0ilgwMD1GiWAmbU4u/JHNk= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 h1:r+Kv+SEJquhAZXaJ7G4u44cIwXV3f8K+N482NNAzJZA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24/go.mod h1:gAuCezX/gob6BSMbItsSlMb6WZGV7K2+fWOvk8xBSto= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 h1:1mnRASEKnkqsntcxHaysxwgVoUUp5dkiB+l3llKnqyg= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25/go.mod h1:zBHOPwhBc3FlQjQJE/D3IfPWiWaQmT06Vq9aNukDo0k= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29/go.mod h1:TwuqRBGzxjQJIwH16/fOZodwXt2Zxa9/cwJC5ke4j7s= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 h1:hf+Vhp5WtTdcSdE+yEcUz8L73sAzN0R+0jQv+Z51/mI= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31/go.mod h1:5zUjguZfG5qjhG9/wqmuyHRyUftl2B5Cp6NNxNC6kRA= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 h1:p5luUImdIqywn6JpQsW3tq5GNOxKmOnEpybzPx+d1lk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32/go.mod h1:XGhIBZDEgfqmFIugclZ6FU7v75nHhBDtzuB4xB/tEi4= github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.19/go.mod h1:8W88sW3PjamQpKFUQvHWWKay6ARsNvZnzU7+a4apubw= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.22 h1:lTqBRUuy8oLhBsnnVZf14uRbIHPHCrGqg4Plc8gU/1U= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.22/go.mod h1:YsOa3tFriwWNvBPYHXM5ARiU2yqBNWPWeUiq+4i7Na0= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.23 h1:DWYZIsyqagnWL00f8M/SOr9fN063OEQWn9LLTbdYXsk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.23/go.mod h1:uIiFgURZbACBEQJfqTZPb/jxO7R+9LeoHUFudtIdeQI= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.23/go.mod h1:1jcUfF+FAOEwtIcNiHPaV4TSoZqkUIPzrohmD7fb95c= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.25 h1:B/hO3jfWRm7hP00UeieNlI5O2xP5WJ27tyJG5lzc7AM= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.25/go.mod h1:54K1zgxK/lai3a4HosE4IKBwZsP/5YAJ6dzJfwsjJ0U= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.26 h1:CeuSeq/8FnYpPtnuIeLQEEvDv9zUjneuYi8EghMBdwQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.26/go.mod h1:2UqAAwMUXKeRkAHIlDJqvMVgOWkUi/AUXPk/YIe+Dg4= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22/go.mod h1:xt0Au8yPIwYXf/GYPy/vl4K3CgwhfQMYbrH7DlUUIws= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24 h1:c5qGfdbCHav6viBwiyDns3OXqhqAbGjfIB4uVu2ayhk= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24/go.mod h1:HMA4FZG6fyib+NDo5bpIxX1EhYjrAOveZJY2YR0xrNE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 h1:5LHn8JQ0qvjD9L9JhMtylnkcw7j05GDZqM9Oin6hpr0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25/go.mod h1:/95IA+0lMnzW6XzqYJRpjjsAbKEORVeO0anQqjd2CNU= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.22/go.mod h1:QFVbqK54XArazLvn2wvWMRBi/jGrWii46qbr5DyPGjc= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.24 h1:i4RH8DLv/BHY0fCrXYQDr+DGnWzaxB3Ee/esxUaSavk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.24/go.mod h1:N8X45/o2cngvjCYi2ZnvI0P4mU4ZRJfEYC3maCSsPyw= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.0 h1:e2ooMhpYGhDnBfSvIyusvAwX7KexuZaHbQY2Dyei7VU= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.0/go.mod h1:bh2E0CXKZsQN+faiKVqC40vfNMAWheoULBCnEgO9K+8= github.com/aws/aws-sdk-go-v2/service/kms v1.20.2/go.mod h1:vdqtUOdVuf5ooy+hJ2GnzqNo94xiAA9s1xbZ1hQgRE0= -github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.6 h1:3yAJmDgUzVGGp5PkHA/HGFquEJRK0uEaep22XZj4UJg= -github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.6/go.mod h1:kFXyTQKLc5KyBUhJ0kUckwncHElnSEbXbBeGpNJUMEY= +github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.7 h1:QA+w3BlShNMgOTRN1kBG2qOIHuTzVTxZ0l3ImKkz+ic= +github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.7/go.mod h1:8Ma9cuRQADU+FwLPYmoSdvUt4tItX2kleWi/++H0BF0= github.com/aws/aws-sdk-go-v2/service/s3 v1.30.2/go.mod h1:SXDHd6fI2RhqB7vmAzyYQCTQnpZrIprVJvYxpzW3JAM= -github.com/aws/aws-sdk-go-v2/service/s3 v1.30.6 h1:zzTm99krKsFcF4N7pu2z17yCcAZpQYZ7jnJZPIgEMXE= -github.com/aws/aws-sdk-go-v2/service/s3 v1.30.6/go.mod h1:PudwVKUTApfm0nYaPutOXaKdPKTlZYClGBQpVIRdcbs= +github.com/aws/aws-sdk-go-v2/service/s3 v1.31.0 h1:B1G2pSPvbAtQjilPq+Y7jLIzCOwKzuVEl+aBBaNG0AQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.31.0/go.mod h1:ncltU6n4Nof5uJttDtcNQ537uNuwYqsZZQcpkd2/GUQ= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.3/go.mod h1:hqPcyOuLU6yWIbLy3qMnQnmidgKuIEwqIlW6+chYnog= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.0 h1:B4LvuBxrxh2WXakqwJL22EPAWgqGGK9/E4YQV/IIkYo= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.0/go.mod h1:XF4Gbmcn6V9xIIm6lhwtyX1NXConNJ8x6yizt2Ejx/0= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.1 h1:+rANS0SbrDUqF3VJeil1HJHhNK8vdUu1VGqnkr4o6kw= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.1/go.mod h1:SUiYnlcBDUvSLD6iUmwSwXni2i6iGa9WHc+eM5061W4= github.com/aws/aws-sdk-go-v2/service/sns v1.20.2/go.mod h1:VN2n9SOMS1lNbh5YD7o+ho0/rgfifSrK//YYNiVVF5E= github.com/aws/aws-sdk-go-v2/service/sqs v1.20.2/go.mod h1:1ttxGjUHZliCQMpPss1sU5+Ph/5NvdMFRzr96bv8gm0= github.com/aws/aws-sdk-go-v2/service/ssm v1.35.2/go.mod h1:VLSz2SHUKYFSOlXB/GlXoLU6KPYQJAbw7I20TDJdyws= github.com/aws/aws-sdk-go-v2/service/sso v1.12.1/go.mod h1:IgV8l3sj22nQDd5qcAGY0WenwCzCphqdbFOpfktZPrI= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 h1:bdKIX6SVF3nc3xJFw6Nf0igzS6Ff/louGq8Z6VP/3Hs= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.5/go.mod h1:vuWiaDB30M/QTC+lI3Wj6S/zb7tpUK2MSYgy3Guh2L0= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 h1:5V7DWLBd7wTELVz5bPpwzYy/sikk0gsgZfj40X+l5OI= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.6/go.mod h1:Y1VOmit/Fn6Tz1uFAeCO6Q7M2fmfXSCLeL5INVYsLuY= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1/go.mod h1:O1YSOg3aekZibh2SngvCRRG+cRHKKlYgxf/JBF/Kr/k= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 h1:xLPZMyuZ4GuqRCIec/zWuIhRFPXh2UOJdLXBSi64ZWQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5/go.mod h1:QjxpHmCwAg0ESGtPQnLIVp7SedTOBMYy+Slr3IfMKeI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 h1:B8cauxOH1W1v7rd8RdI/MWnoR4Ze0wIHWrb90qczxj4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6/go.mod h1:Lh/bc9XUf8CfOY6Jp5aIkQtN+j1mc+nExc+KXj9jx2s= github.com/aws/aws-sdk-go-v2/service/sts v1.18.3/go.mod h1:b+psTJn33Q4qGoDaM7ZiOVVG8uVjGI6HaZ8WBHdgDgU= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 h1:rIFn5J3yDoeuKCE9sESXqM5POTAhOP1du3bv/qTL+tE= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.6/go.mod h1:48WJ9l3dwP0GSHWGc5sFGGlCkuA82Mc2xnw+T6Q8aDw= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 h1:bWNgNdRko2x6gqa0blfATqAZKZokPIeM1vfmQt2pnvM= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.7/go.mod h1:JuTnSoeePXmMVe9G8NcjjwgOKEfZ4cOjMuT2IBT/2eI= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= @@ -693,8 +694,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/cockroach-go/v2 v2.3.2 h1:r+HgkUAPoRtB5UUxnSvmh3irEVeUETHSAjCSxX5Tfto= -github.com/cockroachdb/cockroach-go/v2 v2.3.2/go.mod h1:1wNJ45eSXW9AnOc3skntW9ZUZz6gxrQK3cOj3rK+BC8= +github.com/cockroachdb/cockroach-go/v2 v2.3.3 h1:fNmtG6XhoA1DhdDCIu66YyGSsNb1szj4CaAsbDxRmy4= +github.com/cockroachdb/cockroach-go/v2 v2.3.3/go.mod h1:1wNJ45eSXW9AnOc3skntW9ZUZz6gxrQK3cOj3rK+BC8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= @@ -1062,9 +1063,8 @@ github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY9 github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.1 h1:lEs5Ob+oOG/Ze199njvzHbhn6p9T+h64F5hRj69iTTo= -github.com/goccy/go-json v0.10.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +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-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= @@ -1278,8 +1278,8 @@ github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39 github.com/hashicorp/go-hclog v0.12.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I= -github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -1475,8 +1475,8 @@ github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJG github.com/lestrrat-go/httprc v1.0.4/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.8 h1:jCFT8oc0hEDVjgUgsBy1F9cbjsjAVZSXNi7JaU9HR/Q= -github.com/lestrrat-go/jwx/v2 v2.0.8/go.mod h1:zLxnyv9rTlEvOUHbc48FAfIL8iYu2hHvIRaTFGc8mT0= +github.com/lestrrat-go/jwx/v2 v2.0.9 h1:TRX4Q630UXxPVLvP5vGaqVJO7S+0PE6msRZUsFSBoC8= +github.com/lestrrat-go/jwx/v2 v2.0.9/go.mod h1:K68euYaR95FnL0hIQB8VvzL70vB7pSifbJUydCTPmgM= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 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= @@ -1529,8 +1529,8 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= @@ -1963,8 +1963,8 @@ github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSV github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= -github.com/wneessen/go-mail v0.3.8 h1:ja5D/o/RVwrtRIYFlrO7GmtcjDNeMakGQuwQRZYv0JM= -github.com/wneessen/go-mail v0.3.8/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E= +github.com/wneessen/go-mail v0.3.9 h1:Q4DbCk3htT5DtDWKeMgNXCiHc4bBY/vv/XQPT6XDXzc= +github.com/wneessen/go-mail v0.3.9/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= @@ -2080,8 +2080,9 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk= go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU= +go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME= +go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= @@ -2655,8 +2656,8 @@ google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= -google.golang.org/api v0.112.0 h1:iDmzvZ4C086R3+en4nSyIf07HlQKMOX1Xx2dmia/+KQ= -google.golang.org/api v0.112.0/go.mod h1:737UfWHNsOq4F3REUTmb+GN9pugkgNLCayLTfoIKpPc= +google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2801,8 +2802,8 @@ google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 h1:khxVcsk/FhnzxMKOyD+TDGwjbEOpcPuIpmafPGFmhMA= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -2846,8 +2847,9 @@ google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCD google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/grpc v1.52.1/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -2864,8 +2866,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM= -google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/acme/acme.go b/internal/acme/acme.go index dfd3181e..fd1694e5 100644 --- a/internal/acme/acme.go +++ b/internal/acme/acme.go @@ -287,10 +287,7 @@ func (c *Configuration) validateChallenges() error { if err := c.HTTP01Challenge.validate(); err != nil { return err } - if err := c.TLSALPN01Challenge.validate(); err != nil { - return err - } - return nil + return c.TLSALPN01Challenge.validate() } func (c *Configuration) checkDomains() { diff --git a/internal/common/actions_test.go b/internal/common/actions_test.go index 7a3a404d..983f170f 100644 --- a/internal/common/actions_test.go +++ b/internal/common/actions_test.go @@ -321,7 +321,7 @@ type actionHandlerStub struct { called bool } -func (h *actionHandlerStub) Handle(event *notifier.FsEvent) (int, error) { +func (h *actionHandlerStub) Handle(_ *notifier.FsEvent) (int, error) { h.called = true return 1, nil diff --git a/internal/common/common_test.go b/internal/common/common_test.go index 8b59b1d4..885d13d1 100644 --- a/internal/common/common_test.go +++ b/internal/common/common_test.go @@ -1235,7 +1235,7 @@ func TestParseAllowedIPAndRanges(t *testing.T) { assert.False(t, allow[1](net.ParseIP("172.16.1.1"))) } -func TestHideConfidentialData(t *testing.T) { +func TestHideConfidentialData(_ *testing.T) { for _, provider := range []sdk.FilesystemProvider{sdk.LocalFilesystemProvider, sdk.CryptedFilesystemProvider, sdk.S3FilesystemProvider, sdk.GCSFilesystemProvider, sdk.AzureBlobFilesystemProvider, sdk.SFTPFilesystemProvider, diff --git a/internal/common/connection_test.go b/internal/common/connection_test.go index 68e711cb..c6cdbefb 100644 --- a/internal/common/connection_test.go +++ b/internal/common/connection_test.go @@ -63,7 +63,7 @@ func (fs *MockOsFs) IsUploadResumeSupported() bool { return !fs.hasVirtualFolders } -func (fs *MockOsFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error { +func (fs *MockOsFs) Chtimes(_ string, _, _ time.Time, _ bool) error { return vfs.ErrVfsUnsupported } @@ -75,7 +75,7 @@ func (fs *MockOsFs) Lstat(name string) (os.FileInfo, error) { } // Walk returns a duplicate path for testing -func (fs *MockOsFs) Walk(root string, walkFn filepath.WalkFunc) error { +func (fs *MockOsFs) Walk(_ string, walkFn filepath.WalkFunc) error { if fs.err == errWalkDir { walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now(), false), nil) //nolint:errcheck return walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now(), false), nil) //nolint:errcheck diff --git a/internal/common/eventmanager.go b/internal/common/eventmanager.go index 7a640d4b..81304a22 100644 --- a/internal/common/eventmanager.go +++ b/internal/common/eventmanager.go @@ -18,6 +18,7 @@ import ( "bytes" "context" "encoding/csv" + "encoding/json" "errors" "fmt" "io" @@ -56,6 +57,12 @@ const ( maxAttachmentsSize = int64(10 * 1024 * 1024) ) +// Supported IDP login events +const ( + IDPLoginUser = "IDP login user" + IDPLoginAdmin = "IDP login admin" +) + var ( // eventManager handle the supported event rules actions eventManager eventRulesContainer @@ -90,6 +97,11 @@ func HandleCertificateEvent(params EventParams) { eventManager.handleCertificateEvent(params) } +// HandleIDPLoginEvent executes actions defined for a successful login from an Identity Provider +func HandleIDPLoginEvent(params EventParams, customFields *map[string]any) (*dataprovider.User, *dataprovider.Admin, error) { + return eventManager.handleIDPLoginEvent(params, customFields) +} + // eventRulesContainer stores event rules by trigger type eventRulesContainer struct { sync.RWMutex @@ -99,6 +111,7 @@ type eventRulesContainer struct { Schedules []dataprovider.EventRule IPBlockedEvents []dataprovider.EventRule CertificateEvents []dataprovider.EventRule + IPDLoginEvents []dataprovider.EventRule schedulesMapping map[string][]cron.EntryID concurrencyGuard chan struct{} } @@ -168,6 +181,15 @@ func (r *eventRulesContainer) removeRuleInternal(name string) { return } } + for idx := range r.IPDLoginEvents { + if r.IPDLoginEvents[idx].Name == name { + lastIdx := len(r.IPDLoginEvents) - 1 + r.IPDLoginEvents[idx] = r.IPDLoginEvents[lastIdx] + r.IPDLoginEvents = r.IPDLoginEvents[:lastIdx] + eventManagerLog(logger.LevelDebug, "removed rule %q from IDP login events", name) + return + } + } for idx := range r.Schedules { if r.Schedules[idx].Name == name { if schedules, ok := r.schedulesMapping[name]; ok { @@ -213,6 +235,9 @@ func (r *eventRulesContainer) addUpdateRuleInternal(rule dataprovider.EventRule) case dataprovider.EventTriggerCertificate: r.CertificateEvents = append(r.CertificateEvents, rule) eventManagerLog(logger.LevelDebug, "added rule %q to certificate events", rule.Name) + case dataprovider.EventTriggerIDPLogin: + r.IPDLoginEvents = append(r.IPDLoginEvents, rule) + eventManagerLog(logger.LevelDebug, "added rule %q to IDP login events", rule.Name) case dataprovider.EventTriggerSchedule: for _, schedule := range rule.Conditions.Schedules { cronSpec := schedule.GetCronSpec() @@ -253,13 +278,27 @@ func (r *eventRulesContainer) loadRules() { r.addUpdateRuleInternal(rule) } } - eventManagerLog(logger.LevelDebug, "event rules updated, fs events: %d, provider events: %d, schedules: %d, ip blocked events: %d, certificate events: %d", - len(r.FsEvents), len(r.ProviderEvents), len(r.Schedules), len(r.IPBlockedEvents), len(r.CertificateEvents)) + eventManagerLog(logger.LevelDebug, "event rules updated, fs events: %d, provider events: %d, schedules: %d, ip blocked events: %d, certificate events: %d, IDP login events: %d", + len(r.FsEvents), len(r.ProviderEvents), len(r.Schedules), len(r.IPBlockedEvents), len(r.CertificateEvents), len(r.IPDLoginEvents)) r.setLastLoadTime(modTime) } -func (r *eventRulesContainer) checkProviderEventMatch(conditions dataprovider.EventConditions, params EventParams) bool { +func (*eventRulesContainer) checkIPDLoginEventMatch(conditions *dataprovider.EventConditions, params *EventParams) bool { + switch conditions.IDPLoginEvent { + case dataprovider.IDPLoginUser: + if params.Event != IDPLoginUser { + return false + } + case dataprovider.IDPLoginAdmin: + if params.Event != IDPLoginAdmin { + return false + } + } + return checkEventConditionPatterns(params.Name, conditions.Options.Names) +} + +func (*eventRulesContainer) checkProviderEventMatch(conditions *dataprovider.EventConditions, params *EventParams) bool { if !util.Contains(conditions.ProviderEvents, params.Event) { return false } @@ -275,7 +314,7 @@ func (r *eventRulesContainer) checkProviderEventMatch(conditions dataprovider.Ev return true } -func (r *eventRulesContainer) checkFsEventMatch(conditions dataprovider.EventConditions, params EventParams) bool { +func (*eventRulesContainer) checkFsEventMatch(conditions *dataprovider.EventConditions, params *EventParams) bool { if !util.Contains(conditions.FsEvents, params.Event) { return false } @@ -327,7 +366,7 @@ func (r *eventRulesContainer) handleFsEvent(params EventParams) (bool, error) { var rulesWithSyncActions, rulesAsync []dataprovider.EventRule for _, rule := range r.FsEvents { - if r.checkFsEventMatch(rule.Conditions, params) { + if r.checkFsEventMatch(&rule.Conditions, ¶ms) { if err := rule.CheckActionsConsistency(""); err != nil { eventManagerLog(logger.LevelWarn, "rule %q skipped: %v, event %q", rule.Name, err, params.Event) @@ -361,6 +400,59 @@ func (r *eventRulesContainer) handleFsEvent(params EventParams) (bool, error) { return false, nil } +func (r *eventRulesContainer) handleIDPLoginEvent(params EventParams, customFields *map[string]any) (*dataprovider.User, + *dataprovider.Admin, error, +) { + r.RLock() + + var rulesWithSyncActions, rulesAsync []dataprovider.EventRule + for _, rule := range r.IPDLoginEvents { + if r.checkIPDLoginEventMatch(&rule.Conditions, ¶ms) { + if err := rule.CheckActionsConsistency(""); err != nil { + eventManagerLog(logger.LevelWarn, "rule %q skipped: %v, event %q", + rule.Name, err, params.Event) + continue + } + hasSyncActions := false + for _, action := range rule.Actions { + if action.Options.ExecuteSync { + hasSyncActions = true + break + } + } + if hasSyncActions { + rulesWithSyncActions = append(rulesWithSyncActions, rule) + } else { + rulesAsync = append(rulesAsync, rule) + } + } + } + + r.RUnlock() + + if len(rulesAsync) == 0 && len(rulesWithSyncActions) == 0 { + return nil, nil, nil + } + + params.addIDPCustomFields(customFields) + if len(rulesWithSyncActions) > 1 { + var ruleNames []string + for _, r := range rulesWithSyncActions { + ruleNames = append(ruleNames, r.Name) + } + return nil, nil, fmt.Errorf("more than one account check action rules matches: %q", strings.Join(ruleNames, ",")) + } + + if len(rulesAsync) > 0 { + go executeAsyncRulesActions(rulesAsync, params) + } + + if len(rulesWithSyncActions) > 0 { + return executeIDPAccountCheckRule(rulesWithSyncActions[0], params) + } + return nil, nil, nil +} + // username is populated for user objects func (r *eventRulesContainer) handleProviderEvent(params EventParams) { r.RLock() @@ -368,7 +460,7 @@ func (r *eventRulesContainer) handleProviderEvent(params EventParams) { var rules []dataprovider.EventRule for _, rule := range r.ProviderEvents { - if r.checkProviderEventMatch(rule.Conditions, params) { + if r.checkProviderEventMatch(&rule.Conditions, ¶ms) { if err := rule.CheckActionsConsistency(params.ObjectType); err == nil { rules = append(rules, rule) } else { @@ -452,6 +544,7 @@ type EventParams struct { IP string Role string Timestamp int64 + IDPCustomFields *map[string]string Object plugin.Renderer sender string updateStatusFromError bool @@ -474,10 +567,32 @@ func (p *EventParams) getACopy() *EventParams { retentionChecks = append(retentionChecks, executedCheck) } params.retentionChecks = retentionChecks + if p.IDPCustomFields != nil { + fields := make(map[string]string) + for k, v := range *p.IDPCustomFields { + fields[k] = v + } + params.IDPCustomFields = &fields + } return ¶ms } +func (p *EventParams) addIDPCustomFields(customFields *map[string]any) { + if customFields == nil { + return + } + + fields := make(map[string]string) + for k, v := range *customFields { + switch val := v.(type) { + case string: + fields[k] = val + } + } + p.IDPCustomFields = &fields +} + // AddError adds a new error to the event params and update the status if needed func (p *EventParams) AddError(err error) { if err == nil { @@ -622,34 +737,41 @@ func (p *EventParams) getRetentionReportsAsMailAttachment() (*mail.File, error) }, nil } -func (p *EventParams) getStringReplacements(addObjectData bool) []string { +func (*EventParams) getStringReplacement(val string, jsonEscaped bool) string { + if jsonEscaped { + return util.JSONEscape(val) + } + return val +} + +func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []string { replacements := []string{ - "{{Name}}", p.Name, + "{{Name}}", p.getStringReplacement(p.Name, jsonEscaped), "{{Event}}", p.Event, "{{Status}}", fmt.Sprintf("%d", p.Status), - "{{VirtualPath}}", p.VirtualPath, - "{{FsPath}}", p.FsPath, - "{{VirtualTargetPath}}", p.VirtualTargetPath, - "{{FsTargetPath}}", p.FsTargetPath, - "{{ObjectName}}", p.ObjectName, + "{{VirtualPath}}", p.getStringReplacement(p.VirtualPath, jsonEscaped), + "{{FsPath}}", p.getStringReplacement(p.FsPath, jsonEscaped), + "{{VirtualTargetPath}}", p.getStringReplacement(p.VirtualTargetPath, jsonEscaped), + "{{FsTargetPath}}", p.getStringReplacement(p.FsTargetPath, jsonEscaped), + "{{ObjectName}}", p.getStringReplacement(p.ObjectName, jsonEscaped), "{{ObjectType}}", p.ObjectType, "{{FileSize}}", fmt.Sprintf("%d", p.FileSize), "{{Elapsed}}", fmt.Sprintf("%d", p.Elapsed), "{{Protocol}}", p.Protocol, "{{IP}}", p.IP, - "{{Role}}", p.Role, + "{{Role}}", p.getStringReplacement(p.Role, jsonEscaped), "{{Timestamp}}", fmt.Sprintf("%d", p.Timestamp), "{{StatusString}}", p.getStatusString(), } if p.VirtualPath != "" { - replacements = append(replacements, "{{VirtualDirPath}}", path.Dir(p.VirtualPath)) + replacements = append(replacements, "{{VirtualDirPath}}", p.getStringReplacement(path.Dir(p.VirtualPath), jsonEscaped)) } if p.VirtualTargetPath != "" { - replacements = append(replacements, "{{VirtualTargetDirPath}}", path.Dir(p.VirtualTargetPath)) - replacements = append(replacements, "{{TargetName}}", path.Base(p.VirtualTargetPath)) + replacements = append(replacements, "{{VirtualTargetDirPath}}", p.getStringReplacement(path.Dir(p.VirtualTargetPath), jsonEscaped)) + replacements = append(replacements, "{{TargetName}}", p.getStringReplacement(path.Base(p.VirtualTargetPath), jsonEscaped)) } if len(p.errors) > 0 { - replacements = append(replacements, "{{ErrorString}}", strings.Join(p.errors, ", ")) + replacements = append(replacements, "{{ErrorString}}", p.getStringReplacement(strings.Join(p.errors, ", "), jsonEscaped)) } else { replacements = append(replacements, "{{ErrorString}}", "") } @@ -660,6 +782,11 @@ func (p *EventParams) getStringReplacements(addObjectData bool) []string { replacements[len(replacements)-1] = string(data) } } + if p.IDPCustomFields != nil { + for k, v := range *p.IDPCustomFields { + replacements = append(replacements, fmt.Sprintf("{{IDPField%s}}", k), p.getStringReplacement(v, jsonEscaped)) + } + } return replacements } @@ -1060,7 +1187,7 @@ func getHTTPRuleActionEndpoint(c dataprovider.EventActionHTTPConfig, replacer *s } func writeHTTPPart(m *multipart.Writer, part dataprovider.HTTPPart, h textproto.MIMEHeader, - conn *BaseConnection, replacer *strings.Replacer, params *EventParams, + conn *BaseConnection, replacer *strings.Replacer, params *EventParams, addObjectData bool, ) error { partWriter, err := m.CreatePart(h) if err != nil { @@ -1068,7 +1195,14 @@ func writeHTTPPart(m *multipart.Writer, part dataprovider.HTTPPart, h textproto. return err } if part.Body != "" { - _, err = partWriter.Write([]byte(replaceWithReplacer(part.Body, replacer))) + cType := h.Get("Content-Type") + if strings.Contains(strings.ToLower(cType), "application/json") { + replacements := params.getStringReplacements(addObjectData, true) + jsonReplacer := strings.NewReplacer(replacements...) + _, err = partWriter.Write([]byte(replaceWithReplacer(part.Body, jsonReplacer))) + } else { + _, err = partWriter.Write([]byte(replaceWithReplacer(part.Body, replacer))) + } if err != nil { eventManagerLog(logger.LevelError, "unable to write part %q, err: %v", part.Name, err) return err @@ -1095,8 +1229,8 @@ func writeHTTPPart(m *multipart.Writer, part dataprovider.HTTPPart, h textproto. return nil } -func getHTTPRuleActionBody(c dataprovider.EventActionHTTPConfig, replacer *strings.Replacer, - cancel context.CancelFunc, user dataprovider.User, params *EventParams, +func getHTTPRuleActionBody(c *dataprovider.EventActionHTTPConfig, replacer *strings.Replacer, + cancel context.CancelFunc, user dataprovider.User, params *EventParams, addObjectData bool, ) (io.ReadCloser, string, error) { var body io.ReadCloser if c.Method == http.MethodGet { @@ -1110,6 +1244,11 @@ func getHTTPRuleActionBody(c dataprovider.EventActionHTTPConfig, replacer *strin } return io.NopCloser(bytes.NewBuffer(data)), "", nil } + if c.HasJSONBody() { + replacements := params.getStringReplacements(addObjectData, true) + jsonReplacer := strings.NewReplacer(replacements...) + return io.NopCloser(bytes.NewBufferString(replaceWithReplacer(c.Body, jsonReplacer))), "", nil + } return io.NopCloser(bytes.NewBufferString(replaceWithReplacer(c.Body, replacer))), "", nil } if len(c.Parts) > 0 { @@ -1154,7 +1293,7 @@ func getHTTPRuleActionBody(c dataprovider.EventActionHTTPConfig, replacer *strin for _, keyVal := range part.Headers { h.Set(keyVal.Key, replaceWithReplacer(keyVal.Value, replacer)) } - if err := writeHTTPPart(m, part, h, conn, replacer, params); err != nil { + if err := writeHTTPPart(m, part, h, conn, replacer, params, addObjectData); err != nil { cancel() return } @@ -1176,7 +1315,7 @@ func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventPa addObjectData = c.HasObjectData() } - replacements := params.getStringReplacements(addObjectData) + replacements := params.getStringReplacements(addObjectData, false) replacer := strings.NewReplacer(replacements...) endpoint, err := getHTTPRuleActionEndpoint(c, replacer) if err != nil { @@ -1193,7 +1332,7 @@ func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventPa return err } } - body, contentType, err := getHTTPRuleActionBody(c, replacer, cancel, user, params) + body, contentType, err := getHTTPRuleActionBody(&c, replacer, cancel, user, params, addObjectData) if err != nil { return err } @@ -1244,7 +1383,7 @@ func executeCommandRuleAction(c dataprovider.EventActionCommandConfig, params *E } } } - replacements := params.getStringReplacements(addObjectData) + replacements := params.getStringReplacements(addObjectData, false) replacer := strings.NewReplacer(replacements...) args := make([]string, 0, len(c.Args)) @@ -1277,7 +1416,7 @@ func executeEmailRuleAction(c dataprovider.EventActionEmailConfig, params *Event addObjectData = true } } - replacements := params.getStringReplacements(addObjectData) + replacements := params.getStringReplacements(addObjectData, false) replacer := strings.NewReplacer(replacements...) body := replaceWithReplacer(c.Body, replacer) subject := replaceWithReplacer(c.Subject, replacer) @@ -1825,7 +1964,7 @@ func executeFsRuleAction(c dataprovider.EventActionFilesystemConfig, conditions params *EventParams, ) error { addObjectData := false - replacements := params.getStringReplacements(addObjectData) + replacements := params.getStringReplacements(addObjectData, false) replacer := strings.NewReplacer(replacements...) switch c.Type { case dataprovider.FilesystemActionRename: @@ -2207,6 +2346,71 @@ func executePwdExpirationCheckRuleAction(config dataprovider.EventActionPassword return nil } +func executeAdminCheckAction(c *dataprovider.EventActionIDPAccountCheck, params *EventParams) (*dataprovider.Admin, error) { + admin, err := dataprovider.AdminExists(params.Name) + exists := err == nil + if exists && c.Mode == 1 { + return &admin, nil + } + if err != nil && !errors.Is(err, util.ErrNotFound) { + return nil, err + } + + replacements := params.getStringReplacements(false, true) + replacer := strings.NewReplacer(replacements...) + data := replaceWithReplacer(c.TemplateAdmin, replacer) + + var newAdmin dataprovider.Admin + err = json.Unmarshal([]byte(data), &newAdmin) + 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) + err = dataprovider.UpdateAdmin(&newAdmin, dataprovider.ActionExecutorSystem, "", "") + } else { + eventManagerLog(logger.LevelDebug, "creating admin %q after IDP login", params.Name) + err = dataprovider.AddAdmin(&newAdmin, dataprovider.ActionExecutorSystem, "", "") + } + return &newAdmin, err +} + +func executeUserCheckAction(c *dataprovider.EventActionIDPAccountCheck, params *EventParams) (*dataprovider.User, error) { + user, err := dataprovider.UserExists(params.Name, "") + exists := err == nil + if exists && c.Mode == 1 { + err = user.LoadAndApplyGroupSettings() + return &user, err + } + if err != nil && !errors.Is(err, util.ErrNotFound) { + return nil, err + } + replacements := params.getStringReplacements(false, true) + replacer := strings.NewReplacer(replacements...) + data := replaceWithReplacer(c.TemplateUser, replacer) + + var newUser dataprovider.User + err = json.Unmarshal([]byte(data), &newUser) + if err != nil { + return nil, err + } + if exists { + eventManagerLog(logger.LevelDebug, "updating user %q after IDP login", params.Name) + err = dataprovider.UpdateUser(&newUser, dataprovider.ActionExecutorSystem, "", "") + } else { + eventManagerLog(logger.LevelDebug, "creating user %q after IDP login", params.Name) + err = dataprovider.AddUser(&newUser, dataprovider.ActionExecutorSystem, "", "") + } + if err != nil { + return nil, err + } + u, err := dataprovider.GetUserWithGroupSettings(params.Name, "") + return &u, err +} + func executeRuleAction(action dataprovider.BaseEventAction, params *EventParams, conditions dataprovider.ConditionOptions, ) error { @@ -2252,6 +2456,43 @@ func executeRuleAction(action dataprovider.BaseEventAction, params *EventParams, return err } +func executeIDPAccountCheckRule(rule dataprovider.EventRule, params EventParams) (*dataprovider.User, + *dataprovider.Admin, error, +) { + for _, action := range rule.Actions { + if action.Type == dataprovider.ActionTypeIDPAccountCheck { + startTime := time.Now() + var user *dataprovider.User + var admin *dataprovider.Admin + var err error + var failedActions []string + paramsCopy := params.getACopy() + + switch params.Event { + case IDPLoginAdmin: + admin, err = executeAdminCheckAction(&action.BaseEventAction.Options.IDPConfig, paramsCopy) + case IDPLoginUser: + user, err = executeUserCheckAction(&action.BaseEventAction.Options.IDPConfig, paramsCopy) + default: + err = fmt.Errorf("unsupported IDP login event: %q", params.Event) + } + if err != nil { + paramsCopy.AddError(fmt.Errorf("unable to handle %q: %w", params.Event, err)) + eventManagerLog(logger.LevelError, "unable to handle IDP login event %q, err: %v", params.Event, err) + failedActions = append(failedActions, action.Name) + } else { + eventManagerLog(logger.LevelDebug, "executed action %q for rule %q, elapsed %s", + action.Name, rule.Name, time.Since(startTime)) + } + // execute async actions if any, including failure actions + go executeRuleAsyncActions(rule, paramsCopy, failedActions) + return user, admin, err + } + } + eventManagerLog(logger.LevelError, "no action executed for IDP login event %q, event rule: %q", params.Event, rule.Name) + return nil, nil, errors.New("no action executed") +} + func executeSyncRulesActions(rules []dataprovider.EventRule, params EventParams) error { var errRes error diff --git a/internal/common/eventmanager_test.go b/internal/common/eventmanager_test.go index 7713d43b..0bfffd31 100644 --- a/internal/common/eventmanager_test.go +++ b/internal/common/eventmanager_test.go @@ -46,7 +46,7 @@ import ( func TestEventRuleMatch(t *testing.T) { role := "role1" - conditions := dataprovider.EventConditions{ + conditions := &dataprovider.EventConditions{ ProviderEvents: []string{"add", "update"}, Options: dataprovider.ConditionOptions{ Names: []dataprovider.ConditionPattern{ @@ -62,40 +62,40 @@ func TestEventRuleMatch(t *testing.T) { }, }, } - res := eventManager.checkProviderEventMatch(conditions, EventParams{ + res := eventManager.checkProviderEventMatch(conditions, &EventParams{ Name: "user1", Role: role, Event: "add", }) assert.False(t, res) - res = eventManager.checkProviderEventMatch(conditions, EventParams{ + res = eventManager.checkProviderEventMatch(conditions, &EventParams{ Name: "user2", Role: role, Event: "update", }) assert.True(t, res) - res = eventManager.checkProviderEventMatch(conditions, EventParams{ + res = eventManager.checkProviderEventMatch(conditions, &EventParams{ Name: "user2", Role: role, Event: "delete", }) assert.False(t, res) conditions.Options.ProviderObjects = []string{"api_key"} - res = eventManager.checkProviderEventMatch(conditions, EventParams{ + res = eventManager.checkProviderEventMatch(conditions, &EventParams{ Name: "user2", Event: "update", Role: role, ObjectType: "share", }) assert.False(t, res) - res = eventManager.checkProviderEventMatch(conditions, EventParams{ + res = eventManager.checkProviderEventMatch(conditions, &EventParams{ Name: "user2", Event: "update", Role: role, ObjectType: "api_key", }) assert.True(t, res) - res = eventManager.checkProviderEventMatch(conditions, EventParams{ + res = eventManager.checkProviderEventMatch(conditions, &EventParams{ Name: "user2", Event: "update", Role: role + "1", @@ -103,7 +103,7 @@ func TestEventRuleMatch(t *testing.T) { }) assert.False(t, res) // now test fs events - conditions = dataprovider.EventConditions{ + conditions = &dataprovider.EventConditions{ FsEvents: []string{operationUpload, operationDownload}, Options: dataprovider.ConditionOptions{ Names: []dataprovider.ConditionPattern{ @@ -138,41 +138,41 @@ func TestEventRuleMatch(t *testing.T) { ObjectName: "path.txt", FileSize: 20, } - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.False(t, res) params.Event = operationDownload - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.True(t, res) params.Role = role - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.False(t, res) params.Role = "" params.Name = "name" - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.False(t, res) params.Name = "user5" - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.True(t, res) params.VirtualPath = "/sub/f.jpg" params.ObjectName = path.Base(params.VirtualPath) - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.False(t, res) params.VirtualPath = "/sub/f.txt" params.ObjectName = path.Base(params.VirtualPath) - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.True(t, res) params.Protocol = ProtocolHTTP - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.False(t, res) params.Protocol = ProtocolSFTP params.FileSize = 5 - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.False(t, res) params.FileSize = 50 - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.False(t, res) params.FileSize = 25 - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.True(t, res) // bad pattern conditions.Options.Names = []dataprovider.ConditionPattern{ @@ -180,10 +180,10 @@ func TestEventRuleMatch(t *testing.T) { Pattern: "[-]", }, } - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.False(t, res) // check fs events with group name filters - conditions = dataprovider.EventConditions{ + conditions = &dataprovider.EventConditions{ FsEvents: []string{operationUpload, operationDownload}, Options: dataprovider.ConditionOptions{ GroupNames: []dataprovider.ConditionPattern{ @@ -200,7 +200,7 @@ func TestEventRuleMatch(t *testing.T) { Name: "user1", Event: operationUpload, } - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.False(t, res) params.Groups = []sdk.GroupMapping{ { @@ -212,7 +212,7 @@ func TestEventRuleMatch(t *testing.T) { Type: sdk.GroupTypeSecondary, }, } - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.False(t, res) params.Groups = []sdk.GroupMapping{ { @@ -224,7 +224,7 @@ func TestEventRuleMatch(t *testing.T) { Type: sdk.GroupTypeSecondary, }, } - res = eventManager.checkFsEventMatch(conditions, params) + res = eventManager.checkFsEventMatch(conditions, ¶ms) assert.True(t, res) // check user conditions user := dataprovider.User{} @@ -269,6 +269,58 @@ func TestEventRuleMatch(t *testing.T) { }, }) assert.False(t, res) + res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{ + IDPLoginEvent: 0, + }, &EventParams{ + Event: IDPLoginAdmin, + }) + assert.True(t, res) + res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{ + IDPLoginEvent: 2, + }, &EventParams{ + Event: IDPLoginAdmin, + }) + assert.True(t, res) + res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{ + IDPLoginEvent: 1, + }, &EventParams{ + Event: IDPLoginAdmin, + }) + assert.False(t, res) + res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{ + IDPLoginEvent: 1, + }, &EventParams{ + Event: IDPLoginUser, + }) + assert.True(t, res) + res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{ + IDPLoginEvent: 1, + }, &EventParams{ + Name: "user", + Event: IDPLoginUser, + }) + assert.True(t, res) + res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{ + IDPLoginEvent: 1, + Options: dataprovider.ConditionOptions{ + Names: []dataprovider.ConditionPattern{ + { + Pattern: "abc", + }, + }, + }, + }, &EventParams{ + Name: "user", + Event: IDPLoginUser, + }) + assert.False(t, res) + res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{ + IDPLoginEvent: 2, + }, &EventParams{ + Name: "user", + Event: IDPLoginUser, + }) + assert.False(t, res) } func TestDoubleStarMatching(t *testing.T) { @@ -453,6 +505,10 @@ func TestEventManagerErrors(t *testing.T) { err = executePwdExpirationCheckRuleAction(dataprovider.EventActionPasswordExpiration{}, dataprovider.ConditionOptions{}, &EventParams{}) assert.Error(t, err) + _, err = executeAdminCheckAction(&dataprovider.EventActionIDPAccountCheck{}, &EventParams{}) + assert.Error(t, err) + _, err = executeUserCheckAction(&dataprovider.EventActionIDPAccountCheck{}, &EventParams{}) + assert.Error(t, err) groupName := "agroup" err = executeQuotaResetForUser(&dataprovider.User{ @@ -545,7 +601,7 @@ func TestEventManagerErrors(t *testing.T) { }}, dataprovider.EventActionPasswordExpiration{}) assert.Error(t, err) - _, _, err = getHTTPRuleActionBody(dataprovider.EventActionHTTPConfig{ + _, _, err = getHTTPRuleActionBody(&dataprovider.EventActionHTTPConfig{ Method: http.MethodPost, Parts: []dataprovider.HTTPPart{ { @@ -562,7 +618,7 @@ func TestEventManagerErrors(t *testing.T) { Type: sdk.GroupTypePrimary, }, }, - }, &EventParams{}) + }, &EventParams{}, false) assert.Error(t, err) dataRetentionAction := dataprovider.BaseEventAction{ @@ -1181,11 +1237,17 @@ func TestEventRuleActions(t *testing.T) { assert.Contains(t, err.Error(), "no folder quota reset executed") } - body, _, err := getHTTPRuleActionBody(dataprovider.EventActionHTTPConfig{ + body, _, err := getHTTPRuleActionBody(&dataprovider.EventActionHTTPConfig{ Method: http.MethodPost, - }, nil, nil, dataprovider.User{}, &EventParams{}) + }, nil, nil, dataprovider.User{}, &EventParams{}, true) assert.NoError(t, err) assert.Nil(t, body) + body, _, err = getHTTPRuleActionBody(&dataprovider.EventActionHTTPConfig{ + Method: http.MethodPost, + Body: "test body", + }, nil, nil, dataprovider.User{}, &EventParams{}, false) + assert.NoError(t, err) + assert.NotNil(t, body) err = os.RemoveAll(folder1.MappedPath) assert.NoError(t, err) @@ -1195,6 +1257,99 @@ func TestEventRuleActions(t *testing.T) { assert.NoError(t, err) } +func TestIDPAccountCheckRule(t *testing.T) { + _, _, err := executeIDPAccountCheckRule(dataprovider.EventRule{}, EventParams{}) + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "no action executed") + } + _, _, err = executeIDPAccountCheckRule(dataprovider.EventRule{ + Actions: []dataprovider.EventAction{ + { + BaseEventAction: dataprovider.BaseEventAction{ + Name: "n", + Type: dataprovider.ActionTypeIDPAccountCheck, + }, + }, + }, + }, EventParams{Event: "invalid"}) + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "unsupported IDP login event") + } + // invalid json + _, err = executeAdminCheckAction(&dataprovider.EventActionIDPAccountCheck{TemplateAdmin: "{"}, &EventParams{Name: "missing admin"}) + assert.Error(t, err) + _, err = executeUserCheckAction(&dataprovider.EventActionIDPAccountCheck{TemplateUser: "["}, &EventParams{Name: "missing user"}) + assert.Error(t, err) + _, err = executeUserCheckAction(&dataprovider.EventActionIDPAccountCheck{TemplateUser: "{}"}, &EventParams{Name: "invalid user template"}) + assert.ErrorIs(t, err, util.ErrValidation) + username := "u" + c := &dataprovider.EventActionIDPAccountCheck{ + Mode: 1, + TemplateUser: `{"username":"` + username + `","status":1,"home_dir":"` + util.JSONEscape(filepath.Join(os.TempDir())) + `","permissions":{"/":["*"]}}`, + } + params := &EventParams{ + Name: username, + Event: IDPLoginUser, + } + user, err := executeUserCheckAction(c, params) + assert.NoError(t, err) + assert.Equal(t, username, user.Username) + assert.Equal(t, 1, user.Status) + user.Status = 0 + err = dataprovider.UpdateUser(user, "", "", "") + assert.NoError(t, err) + // the user is not changed + user, err = executeUserCheckAction(c, params) + assert.NoError(t, err) + assert.Equal(t, username, user.Username) + assert.Equal(t, 0, user.Status) + // change the mode, the user is now updated + c.Mode = 0 + user, err = executeUserCheckAction(c, params) + assert.NoError(t, err) + assert.Equal(t, username, user.Username) + assert.Equal(t, 1, user.Status) + + err = dataprovider.DeleteUser(username, "", "", "") + assert.NoError(t, err) + // check rule consistency + r := dataprovider.EventRule{ + Actions: []dataprovider.EventAction{ + { + BaseEventAction: dataprovider.BaseEventAction{ + Type: dataprovider.ActionTypeIDPAccountCheck, + }, + Order: 1, + }, + }, + } + err = r.CheckActionsConsistency("") + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "IDP account check action is only supported for IDP login trigger") + } + r.Trigger = dataprovider.EventTriggerIDPLogin + err = r.CheckActionsConsistency("") + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "IDP account check must be a sync action") + } + r.Actions[0].Options.ExecuteSync = true + err = r.CheckActionsConsistency("") + assert.NoError(t, err) + r.Actions = append(r.Actions, dataprovider.EventAction{ + BaseEventAction: dataprovider.BaseEventAction{ + Type: dataprovider.ActionTypeCommand, + }, + Options: dataprovider.EventActionOptions{ + ExecuteSync: true, + }, + Order: 2, + }) + err = r.CheckActionsConsistency("") + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "IDP account check must be the only sync action") + } +} + func TestUserExpirationCheck(t *testing.T) { username := "test_user_expiration_check" user := dataprovider.User{ @@ -1778,6 +1933,22 @@ func TestEventParamsCopy(t *testing.T) { assert.Equal(t, "a_copy", paramsCopy.retentionChecks[0].ActionName) assert.Equal(t, "p_copy", paramsCopy.retentionChecks[0].Results[0].Path) assert.Equal(t, 2, paramsCopy.retentionChecks[0].Results[0].Retention) + assert.Nil(t, params.IDPCustomFields) + params.addIDPCustomFields(nil) + assert.Nil(t, params.IDPCustomFields) + params.IDPCustomFields = &map[string]string{ + "field1": "val1", + } + paramsCopy = params.getACopy() + for k, v := range *paramsCopy.IDPCustomFields { + assert.Equal(t, "field1", k) + assert.Equal(t, "val1", v) + } + assert.Equal(t, params.IDPCustomFields, paramsCopy.IDPCustomFields) + paramsCopy.addIDPCustomFields(&map[string]any{ + "field2": "val2", + }) + assert.NotEqual(t, params.IDPCustomFields, paramsCopy.IDPCustomFields) } func TestEventParamsStatusFromError(t *testing.T) { @@ -1810,14 +1981,14 @@ func TestWriteHTTPPartsError(t *testing.T) { errTest: io.ErrShortWrite, }) - err := writeHTTPPart(m, dataprovider.HTTPPart{}, nil, nil, nil, &EventParams{}) + err := writeHTTPPart(m, dataprovider.HTTPPart{}, nil, nil, nil, &EventParams{}, false) assert.ErrorIs(t, err, io.ErrShortWrite) body := "test body" m = multipart.NewWriter(&testWriter{sentinel: body}) err = writeHTTPPart(m, dataprovider.HTTPPart{ Body: body, - }, nil, nil, nil, &EventParams{}) + }, nil, nil, nil, &EventParams{}, false) assert.ErrorIs(t, err, io.ErrUnexpectedEOF) } diff --git a/internal/common/protocol_test.go b/internal/common/protocol_test.go index 945e83d7..7948ac00 100644 --- a/internal/common/protocol_test.go +++ b/internal/common/protocol_test.go @@ -6033,6 +6033,286 @@ func TestEventRuleRenameEvent(t *testing.T) { require.NoError(t, err) } +func TestEventRuleIDPLogin(t *testing.T) { + smtpCfg := smtp.Config{ + Host: "127.0.0.1", + Port: 2525, + From: "notify@example.com", + TemplatesPath: "templates", + } + err := smtpCfg.Initialize(configDir, true) + require.NoError(t, err) + lastReceivedEmail.reset() + + username := `test_"idp_"login` + custom1 := `cust"oa"1` + u := map[string]any{ + "username": "{{Name}}", + "status": 1, + "home_dir": filepath.Join(os.TempDir(), "{{IDPFieldcustom1}}"), + "permissions": map[string][]string{ + "/": {dataprovider.PermAny}, + }, + } + userTmpl, err := json.Marshal(u) + require.NoError(t, err) + a := map[string]any{ + "username": "{{Name}}", + "status": 1, + "permissions": []string{dataprovider.PermAdminAny}, + } + adminTmpl, err := json.Marshal(a) + require.NoError(t, err) + + a1 := dataprovider.BaseEventAction{ + Name: "a1", + Type: dataprovider.ActionTypeIDPAccountCheck, + Options: dataprovider.BaseEventActionOptions{ + IDPConfig: dataprovider.EventActionIDPAccountCheck{ + Mode: 1, // create if not exists + TemplateUser: string(userTmpl), + TemplateAdmin: string(adminTmpl), + }, + }, + } + 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{"test@example.com"}, + Subject: `"{{Event}} {{StatusString}}"`, + Body: "{{Name}} Custom field: {{IDPFieldcustom1}}", + }, + }, + } + action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated) + assert.NoError(t, err) + r1 := dataprovider.EventRule{ + Name: "test rule IDP login", + Status: 1, + Trigger: dataprovider.EventTriggerIDPLogin, + Conditions: dataprovider.EventConditions{ + IDPLoginEvent: dataprovider.IDPLoginUser, + }, + Actions: []dataprovider.EventAction{ + { + BaseEventAction: dataprovider.BaseEventAction{ + Name: action1.Name, // the rule is not sync and will be skipped + }, + Order: 1, + }, + { + BaseEventAction: dataprovider.BaseEventAction{ + Name: action2.Name, + }, + Order: 2, + }, + }, + } + rule1, resp, err := httpdtest.AddEventRule(r1, http.StatusCreated) + assert.NoError(t, err, string(resp)) + + customFields := map[string]any{ + "custom1": custom1, + } + user, admin, err := common.HandleIDPLoginEvent(common.EventParams{ + Name: username, + Event: common.IDPLoginUser, + Status: 1, + }, &customFields) + assert.Nil(t, user) + assert.Nil(t, admin) + assert.NoError(t, err) + + rule1.Actions[0].Options.ExecuteSync = true + rule1, resp, err = httpdtest.UpdateEventRule(rule1, http.StatusOK) + assert.NoError(t, err, string(resp)) + user, admin, err = common.HandleIDPLoginEvent(common.EventParams{ + Name: username, + Event: common.IDPLoginUser, + Status: 1, + }, &customFields) + if assert.NotNil(t, user) { + assert.Equal(t, filepath.Join(os.TempDir(), custom1), user.GetHomeDir()) + _, err = httpdtest.RemoveUser(*user, http.StatusOK) + assert.NoError(t, err) + } + assert.Nil(t, admin) + assert.NoError(t, err) + 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, util.Contains(email.To, "test@example.com")) + assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, common.IDPLoginUser)) + assert.Contains(t, email.Data, username) + assert.Contains(t, email.Data, custom1) + + user, admin, err = common.HandleIDPLoginEvent(common.EventParams{ + Name: username, + Event: common.IDPLoginAdmin, + Status: 1, + }, &customFields) + assert.Nil(t, user) + assert.Nil(t, admin) + assert.NoError(t, err) + + rule1.Conditions.IDPLoginEvent = dataprovider.IDPLoginAny + rule1.Actions = []dataprovider.EventAction{ + { + BaseEventAction: dataprovider.BaseEventAction{ + Name: action1.Name, + }, + Options: dataprovider.EventActionOptions{ + ExecuteSync: true, + }, + Order: 1, + }, + } + rule1, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK) + assert.NoError(t, err) + + r2 := dataprovider.EventRule{ + Name: "test email on IDP login", + Status: 1, + Trigger: dataprovider.EventTriggerIDPLogin, + Conditions: dataprovider.EventConditions{ + IDPLoginEvent: dataprovider.IDPLoginAdmin, + }, + Actions: []dataprovider.EventAction{ + { + BaseEventAction: dataprovider.BaseEventAction{ + Name: action2.Name, + }, + Order: 1, + }, + }, + } + rule2, resp, err := httpdtest.AddEventRule(r2, http.StatusCreated) + assert.NoError(t, err, string(resp)) + + lastReceivedEmail.reset() + user, admin, err = common.HandleIDPLoginEvent(common.EventParams{ + Name: username, + Event: common.IDPLoginAdmin, + Status: 1, + }, &customFields) + assert.Nil(t, user) + if assert.NotNil(t, admin) { + assert.Equal(t, 1, admin.Status) + } + assert.NoError(t, err) + 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, util.Contains(email.To, "test@example.com")) + assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, common.IDPLoginAdmin)) + assert.Contains(t, email.Data, username) + assert.Contains(t, email.Data, custom1) + admin.Status = 0 + _, _, err = httpdtest.UpdateAdmin(*admin, http.StatusOK) + assert.NoError(t, err) + user, admin, err = common.HandleIDPLoginEvent(common.EventParams{ + Name: username, + Event: common.IDPLoginAdmin, + Status: 1, + }, &customFields) + assert.Nil(t, user) + if assert.NotNil(t, admin) { + assert.Equal(t, 0, admin.Status) + } + assert.NoError(t, err) + action1.Options.IDPConfig.Mode = 0 + action1, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK) + assert.NoError(t, err) + user, admin, err = common.HandleIDPLoginEvent(common.EventParams{ + Name: username, + Event: common.IDPLoginAdmin, + Status: 1, + }, &customFields) + assert.Nil(t, user) + if assert.NotNil(t, admin) { + assert.Equal(t, 1, admin.Status) + } + assert.NoError(t, err) + _, err = httpdtest.RemoveAdmin(*admin, http.StatusOK) + assert.NoError(t, err) + + r3 := dataprovider.EventRule{ + Name: "test rule2 IDP login", + Status: 1, + Trigger: dataprovider.EventTriggerIDPLogin, + Conditions: dataprovider.EventConditions{ + IDPLoginEvent: dataprovider.IDPLoginAny, + }, + Actions: []dataprovider.EventAction{ + { + BaseEventAction: dataprovider.BaseEventAction{ + Name: action1.Name, + }, + Order: 1, + Options: dataprovider.EventActionOptions{ + ExecuteSync: true, + }, + }, + }, + } + rule3, resp, err := httpdtest.AddEventRule(r3, http.StatusCreated) + assert.NoError(t, err, string(resp)) + user, admin, err = common.HandleIDPLoginEvent(common.EventParams{ + Name: username, + Event: common.IDPLoginAdmin, + Status: 1, + }, &customFields) + assert.Nil(t, user) + assert.Nil(t, admin) + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "more than one account check action rules matches") + } + + _, err = httpdtest.RemoveEventRule(rule3, http.StatusOK) + assert.NoError(t, err) + + action1.Options.IDPConfig.TemplateAdmin = `{}` + action1, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK) + assert.NoError(t, err) + _, _, err = common.HandleIDPLoginEvent(common.EventParams{ + Name: username, + Event: common.IDPLoginAdmin, + Status: 1, + }, &customFields) + assert.ErrorIs(t, err, util.ErrValidation) + + _, err = httpdtest.RemoveEventRule(rule1, http.StatusOK) + assert.NoError(t, err) + + user, admin, err = common.HandleIDPLoginEvent(common.EventParams{ + Name: username, + Event: common.IDPLoginAdmin, + Status: 1, + }, &customFields) + assert.Nil(t, user) + assert.Nil(t, admin) + assert.NoError(t, err) + + _, err = httpdtest.RemoveEventRule(rule2, 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) + + smtpCfg = smtp.Config{} + err = smtpCfg.Initialize(configDir, true) + require.NoError(t, err) +} + func TestEventRuleCertificate(t *testing.T) { smtpCfg := smtp.Config{ Host: "127.0.0.1", diff --git a/internal/dataprovider/bolt.go b/internal/dataprovider/bolt.go index 67d273de..23bbd970 100644 --- a/internal/dataprovider/bolt.go +++ b/internal/dataprovider/bolt.go @@ -729,7 +729,7 @@ func (p *BoltProvider) updateUser(user *User) error { }) } -func (p *BoltProvider) deleteUser(user User, softDelete bool) error { +func (p *BoltProvider) deleteUser(user User, _ bool) error { return p.dbHandle.Update(func(tx *bolt.Tx) error { bucket, err := p.getUsersBucket(tx) if err != nil { @@ -1031,7 +1031,7 @@ func (p *BoltProvider) dumpFolders() ([]vfs.BaseVirtualFolder, error) { return folders, err } -func (p *BoltProvider) getFolders(limit, offset int, order string, minimal bool) ([]vfs.BaseVirtualFolder, error) { +func (p *BoltProvider) getFolders(limit, offset int, order string, _ bool) ([]vfs.BaseVirtualFolder, error) { folders := make([]vfs.BaseVirtualFolder, 0, limit) var err error if limit <= 0 { @@ -1279,7 +1279,7 @@ func (p *BoltProvider) getUsedFolderQuota(name string) (int, int64, error) { return folder.UsedQuotaFiles, folder.UsedQuotaSize, err } -func (p *BoltProvider) getGroups(limit, offset int, order string, minimal bool) ([]Group, error) { +func (p *BoltProvider) getGroups(limit, offset int, order string, _ bool) ([]Group, error) { groups := make([]Group, 0, limit) var err error if limit <= 0 { @@ -2001,75 +2001,75 @@ func (p *BoltProvider) updateShareLastUse(shareID string, numTokens int) error { }) } -func (p *BoltProvider) getDefenderHosts(from int64, limit int) ([]DefenderEntry, error) { +func (p *BoltProvider) getDefenderHosts(_ int64, _ int) ([]DefenderEntry, error) { return nil, ErrNotImplemented } -func (p *BoltProvider) getDefenderHostByIP(ip string, from int64) (DefenderEntry, error) { +func (p *BoltProvider) getDefenderHostByIP(_ string, _ int64) (DefenderEntry, error) { return DefenderEntry{}, ErrNotImplemented } -func (p *BoltProvider) isDefenderHostBanned(ip string) (DefenderEntry, error) { +func (p *BoltProvider) isDefenderHostBanned(_ string) (DefenderEntry, error) { return DefenderEntry{}, ErrNotImplemented } -func (p *BoltProvider) updateDefenderBanTime(ip string, minutes int) error { +func (p *BoltProvider) updateDefenderBanTime(_ string, _ int) error { return ErrNotImplemented } -func (p *BoltProvider) deleteDefenderHost(ip string) error { +func (p *BoltProvider) deleteDefenderHost(_ string) error { return ErrNotImplemented } -func (p *BoltProvider) addDefenderEvent(ip string, score int) error { +func (p *BoltProvider) addDefenderEvent(_ string, _ int) error { return ErrNotImplemented } -func (p *BoltProvider) setDefenderBanTime(ip string, banTime int64) error { +func (p *BoltProvider) setDefenderBanTime(_ string, _ int64) error { return ErrNotImplemented } -func (p *BoltProvider) cleanupDefender(from int64) error { +func (p *BoltProvider) cleanupDefender(_ int64) error { return ErrNotImplemented } -func (p *BoltProvider) addActiveTransfer(transfer ActiveTransfer) error { +func (p *BoltProvider) addActiveTransfer(_ ActiveTransfer) error { return ErrNotImplemented } -func (p *BoltProvider) updateActiveTransferSizes(ulSize, dlSize, transferID int64, connectionID string) error { +func (p *BoltProvider) updateActiveTransferSizes(_, _, _ int64, _ string) error { return ErrNotImplemented } -func (p *BoltProvider) removeActiveTransfer(transferID int64, connectionID string) error { +func (p *BoltProvider) removeActiveTransfer(_ int64, _ string) error { return ErrNotImplemented } -func (p *BoltProvider) cleanupActiveTransfers(before time.Time) error { +func (p *BoltProvider) cleanupActiveTransfers(_ time.Time) error { return ErrNotImplemented } -func (p *BoltProvider) getActiveTransfers(from time.Time) ([]ActiveTransfer, error) { +func (p *BoltProvider) getActiveTransfers(_ time.Time) ([]ActiveTransfer, error) { return nil, ErrNotImplemented } -func (p *BoltProvider) addSharedSession(session Session) error { +func (p *BoltProvider) addSharedSession(_ Session) error { return ErrNotImplemented } -func (p *BoltProvider) deleteSharedSession(key string) error { +func (p *BoltProvider) deleteSharedSession(_ string) error { return ErrNotImplemented } -func (p *BoltProvider) getSharedSession(key string) (Session, error) { +func (p *BoltProvider) getSharedSession(_ string) (Session, error) { return Session{}, ErrNotImplemented } -func (p *BoltProvider) cleanupSharedSessions(sessionType SessionType, before int64) error { +func (p *BoltProvider) cleanupSharedSessions(_ SessionType, _ int64) error { return ErrNotImplemented } -func (p *BoltProvider) getEventActions(limit, offset int, order string, minimal bool) ([]BaseEventAction, error) { +func (p *BoltProvider) getEventActions(limit, offset int, order string, _ bool) ([]BaseEventAction, error) { if limit <= 0 { return nil, nil } @@ -2508,7 +2508,7 @@ func (p *BoltProvider) updateEventRule(rule *EventRule) error { }) } -func (p *BoltProvider) deleteEventRule(rule EventRule, softDelete bool) error { +func (p *BoltProvider) deleteEventRule(rule EventRule, _ bool) error { return p.dbHandle.Update(func(tx *bolt.Tx) error { bucket, err := p.getRulesBucket(tx) if err != nil { @@ -2537,19 +2537,19 @@ func (p *BoltProvider) deleteEventRule(rule EventRule, softDelete bool) error { }) } -func (*BoltProvider) getTaskByName(name string) (Task, error) { +func (*BoltProvider) getTaskByName(_ string) (Task, error) { return Task{}, ErrNotImplemented } -func (*BoltProvider) addTask(name string) error { +func (*BoltProvider) addTask(_ string) error { return ErrNotImplemented } -func (*BoltProvider) updateTask(name string, version int64) error { +func (*BoltProvider) updateTask(_ string, _ int64) error { return ErrNotImplemented } -func (*BoltProvider) updateTaskTimestamp(name string) error { +func (*BoltProvider) updateTaskTimestamp(_ string) error { return ErrNotImplemented } @@ -2557,7 +2557,7 @@ func (*BoltProvider) addNode() error { return ErrNotImplemented } -func (*BoltProvider) getNodeByName(name string) (Node, error) { +func (*BoltProvider) getNodeByName(_ string) (Node, error) { return Node{}, ErrNotImplemented } @@ -2683,7 +2683,7 @@ func (p *BoltProvider) deleteRole(role Role) error { }) } -func (p *BoltProvider) getRoles(limit int, offset int, order string, minimal bool) ([]Role, error) { +func (p *BoltProvider) getRoles(limit int, offset int, order string, _ bool) ([]Role, error) { roles := make([]Role, 0, limit) if limit <= 0 { return roles, nil @@ -2827,7 +2827,7 @@ func (p *BoltProvider) updateIPListEntry(entry *IPListEntry) error { }) } -func (p *BoltProvider) deleteIPListEntry(entry IPListEntry, softDelete bool) error { +func (p *BoltProvider) deleteIPListEntry(entry IPListEntry, _ bool) error { return p.dbHandle.Update(func(tx *bolt.Tx) error { bucket, err := p.getIPListsBucket(tx) if err != nil { @@ -2888,7 +2888,7 @@ func (p *BoltProvider) getIPListEntries(listType IPListType, filter, from, order return entries, err } -func (p *BoltProvider) getRecentlyUpdatedIPListEntries(after int64) ([]IPListEntry, error) { +func (p *BoltProvider) getRecentlyUpdatedIPListEntries(_ int64) ([]IPListEntry, error) { return nil, ErrNotImplemented } diff --git a/internal/dataprovider/eventrule.go b/internal/dataprovider/eventrule.go index 64c80c8b..b37b62d3 100644 --- a/internal/dataprovider/eventrule.go +++ b/internal/dataprovider/eventrule.go @@ -47,13 +47,14 @@ const ( ActionTypeMetadataCheck ActionTypePasswordExpirationCheck ActionTypeUserExpirationCheck + ActionTypeIDPAccountCheck ) var ( supportedEventActions = []int{ActionTypeHTTP, ActionTypeCommand, ActionTypeEmail, ActionTypeFilesystem, ActionTypeBackup, ActionTypeUserQuotaReset, ActionTypeFolderQuotaReset, ActionTypeTransferQuotaReset, ActionTypeDataRetentionCheck, ActionTypeMetadataCheck, ActionTypePasswordExpirationCheck, - ActionTypeUserExpirationCheck} + ActionTypeUserExpirationCheck, ActionTypeIDPAccountCheck} ) func isActionTypeValid(action int) bool { @@ -84,6 +85,8 @@ func getActionTypeAsString(action int) string { return "Password expiration check" case ActionTypeUserExpirationCheck: return "User expiration check" + case ActionTypeIDPAccountCheck: + return "Identity Provider account check" default: return "Command" } @@ -99,11 +102,12 @@ const ( EventTriggerIPBlocked EventTriggerCertificate EventTriggerOnDemand + EventTriggerIDPLogin ) var ( supportedEventTriggers = []int{EventTriggerFsEvent, EventTriggerProviderEvent, EventTriggerSchedule, - EventTriggerIPBlocked, EventTriggerCertificate, EventTriggerOnDemand} + EventTriggerIPBlocked, EventTriggerCertificate, EventTriggerIDPLogin, EventTriggerOnDemand} ) func isEventTriggerValid(trigger int) bool { @@ -122,11 +126,24 @@ func getTriggerTypeAsString(trigger int) string { return "Certificate renewal" case EventTriggerOnDemand: return "On demand" + case EventTriggerIDPLogin: + return "Identity Provider login" default: return "Schedule" } } +// Supported IDP login events +const ( + IDPLoginAny = iota + IDPLoginUser + IDPLoginAdmin +) + +var ( + supportedIDPLoginEvents = []int{IDPLoginAny, IDPLoginUser, IDPLoginAdmin} +) + // Supported filesystem actions const ( FilesystemActionRename = iota + 1 @@ -276,6 +293,16 @@ type EventActionHTTPConfig struct { Parts []HTTPPart `json:"parts,omitempty"` } +// HasJSONBody returns true if the content type header indicates a JSON body +func (c *EventActionHTTPConfig) HasJSONBody() bool { + for _, h := range c.Headers { + if http.CanonicalHeaderKey(h.Key) == "Content-Type" { + return strings.Contains(strings.ToLower(h.Value), "application/json") + } + } + return false +} + func (c *EventActionHTTPConfig) isTimeoutNotValid() bool { if c.HasMultipartFiles() { return false @@ -833,6 +860,24 @@ func (c *EventActionPasswordExpiration) validate() error { return nil } +// EventActionIDPAccountCheck defines the check to execute after a successful IDP login +type EventActionIDPAccountCheck struct { + // 0 create/update, 1 create the account if it doesn't exist + Mode int `json:"mode,omitempty"` + TemplateUser string `json:"template_user,omitempty"` + TemplateAdmin string `json:"template_admin,omitempty"` +} + +func (c *EventActionIDPAccountCheck) validate() error { + if c.TemplateAdmin == "" && c.TemplateUser == "" { + return util.NewValidationError("at least a template must be set") + } + if c.Mode < 0 || c.Mode > 1 { + return util.NewValidationError(fmt.Sprintf("invalid account check mode: %d", c.Mode)) + } + return nil +} + // BaseEventActionOptions defines the supported configuration options for a base event actions type BaseEventActionOptions struct { HTTPConfig EventActionHTTPConfig `json:"http_config"` @@ -841,6 +886,7 @@ type BaseEventActionOptions struct { RetentionConfig EventActionDataRetentionConfig `json:"retention_config"` FsConfig EventActionFilesystemConfig `json:"fs_config"` PwdExpirationConfig EventActionPasswordExpiration `json:"pwd_expiration_config"` + IDPConfig EventActionIDPAccountCheck `json:"idp_config"` } func (o *BaseEventActionOptions) getACopy() BaseEventActionOptions { @@ -901,6 +947,11 @@ func (o *BaseEventActionOptions) getACopy() BaseEventActionOptions { PwdExpirationConfig: EventActionPasswordExpiration{ Threshold: o.PwdExpirationConfig.Threshold, }, + IDPConfig: EventActionIDPAccountCheck{ + Mode: o.IDPConfig.Mode, + TemplateUser: o.IDPConfig.TemplateUser, + TemplateAdmin: o.IDPConfig.TemplateAdmin, + }, FsConfig: o.FsConfig.getACopy(), } } @@ -933,6 +984,7 @@ func (o *BaseEventActionOptions) validate(action int, name string) error { o.RetentionConfig = EventActionDataRetentionConfig{} o.FsConfig = EventActionFilesystemConfig{} o.PwdExpirationConfig = EventActionPasswordExpiration{} + o.IDPConfig = EventActionIDPAccountCheck{} return o.HTTPConfig.validate(name) case ActionTypeCommand: o.HTTPConfig = EventActionHTTPConfig{} @@ -940,6 +992,7 @@ func (o *BaseEventActionOptions) validate(action int, name string) error { o.RetentionConfig = EventActionDataRetentionConfig{} o.FsConfig = EventActionFilesystemConfig{} o.PwdExpirationConfig = EventActionPasswordExpiration{} + o.IDPConfig = EventActionIDPAccountCheck{} return o.CmdConfig.validate() case ActionTypeEmail: o.HTTPConfig = EventActionHTTPConfig{} @@ -947,6 +1000,7 @@ func (o *BaseEventActionOptions) validate(action int, name string) error { o.RetentionConfig = EventActionDataRetentionConfig{} o.FsConfig = EventActionFilesystemConfig{} o.PwdExpirationConfig = EventActionPasswordExpiration{} + o.IDPConfig = EventActionIDPAccountCheck{} return o.EmailConfig.validate() case ActionTypeDataRetentionCheck: o.HTTPConfig = EventActionHTTPConfig{} @@ -954,6 +1008,7 @@ func (o *BaseEventActionOptions) validate(action int, name string) error { o.EmailConfig = EventActionEmailConfig{} o.FsConfig = EventActionFilesystemConfig{} o.PwdExpirationConfig = EventActionPasswordExpiration{} + o.IDPConfig = EventActionIDPAccountCheck{} return o.RetentionConfig.validate() case ActionTypeFilesystem: o.HTTPConfig = EventActionHTTPConfig{} @@ -961,6 +1016,7 @@ func (o *BaseEventActionOptions) validate(action int, name string) error { o.EmailConfig = EventActionEmailConfig{} o.RetentionConfig = EventActionDataRetentionConfig{} o.PwdExpirationConfig = EventActionPasswordExpiration{} + o.IDPConfig = EventActionIDPAccountCheck{} return o.FsConfig.validate() case ActionTypePasswordExpirationCheck: o.HTTPConfig = EventActionHTTPConfig{} @@ -968,7 +1024,16 @@ func (o *BaseEventActionOptions) validate(action int, name string) error { o.EmailConfig = EventActionEmailConfig{} o.RetentionConfig = EventActionDataRetentionConfig{} o.FsConfig = EventActionFilesystemConfig{} + o.IDPConfig = EventActionIDPAccountCheck{} return o.PwdExpirationConfig.validate() + case ActionTypeIDPAccountCheck: + o.HTTPConfig = EventActionHTTPConfig{} + o.CmdConfig = EventActionCommandConfig{} + o.EmailConfig = EventActionEmailConfig{} + o.RetentionConfig = EventActionDataRetentionConfig{} + o.FsConfig = EventActionFilesystemConfig{} + o.PwdExpirationConfig = EventActionPasswordExpiration{} + return o.IDPConfig.validate() default: o.HTTPConfig = EventActionHTTPConfig{} o.CmdConfig = EventActionCommandConfig{} @@ -976,6 +1041,7 @@ func (o *BaseEventActionOptions) validate(action int, name string) error { o.RetentionConfig = EventActionDataRetentionConfig{} o.FsConfig = EventActionFilesystemConfig{} o.PwdExpirationConfig = EventActionPasswordExpiration{} + o.IDPConfig = EventActionIDPAccountCheck{} } return nil } @@ -1086,12 +1152,14 @@ func (a *EventAction) validateAssociation(trigger int, fsEvents []string) error } } if a.Options.ExecuteSync { - if trigger != EventTriggerFsEvent { - return util.NewValidationError("sync execution is only supported for some filesystem events") + if trigger != EventTriggerFsEvent && trigger != EventTriggerIDPLogin { + return util.NewValidationError("sync execution is only supported for some filesystem events and Identity Provider logins") } - for _, ev := range fsEvents { - if !util.Contains(allowedSyncFsEvents, ev) { - return util.NewValidationError("sync execution is only supported for upload and pre-* events") + if trigger == EventTriggerFsEvent { + for _, ev := range fsEvents { + if !util.Contains(allowedSyncFsEvents, ev) { + return util.NewValidationError("sync execution is only supported for upload and pre-* events") + } } } } @@ -1213,10 +1281,12 @@ func (s *Schedule) validate() error { // EventConditions defines the conditions for an event rule type EventConditions struct { // Only one between FsEvents, ProviderEvents and Schedule is allowed - FsEvents []string `json:"fs_events,omitempty"` - ProviderEvents []string `json:"provider_events,omitempty"` - Schedules []Schedule `json:"schedules,omitempty"` - Options ConditionOptions `json:"options"` + FsEvents []string `json:"fs_events,omitempty"` + ProviderEvents []string `json:"provider_events,omitempty"` + Schedules []Schedule `json:"schedules,omitempty"` + // 0 any, 1 user, 2 admin + IDPLoginEvent int `json:"idp_login_event,omitempty"` + Options ConditionOptions `json:"options"` } func (c *EventConditions) getACopy() EventConditions { @@ -1238,16 +1308,30 @@ func (c *EventConditions) getACopy() EventConditions { FsEvents: fsEvents, ProviderEvents: providerEvents, Schedules: schedules, + IDPLoginEvent: c.IDPLoginEvent, Options: c.Options.getACopy(), } } +func (c *EventConditions) validateSchedules() error { + if len(c.Schedules) == 0 { + return util.NewValidationError("at least one schedule is required") + } + for _, schedule := range c.Schedules { + if err := schedule.validate(); err != nil { + return err + } + } + return nil +} + func (c *EventConditions) validate(trigger int) error { switch trigger { case EventTriggerFsEvent: c.ProviderEvents = nil c.Schedules = nil c.Options.ProviderObjects = nil + c.IDPLoginEvent = 0 if len(c.FsEvents) == 0 { return util.NewValidationError("at least one filesystem event is required") } @@ -1264,6 +1348,7 @@ func (c *EventConditions) validate(trigger int) error { c.Options.Protocols = nil c.Options.MinFileSize = 0 c.Options.MaxFileSize = 0 + c.IDPLoginEvent = 0 if len(c.ProviderEvents) == 0 { return util.NewValidationError("at least one provider event is required") } @@ -1280,13 +1365,9 @@ func (c *EventConditions) validate(trigger int) error { c.Options.MinFileSize = 0 c.Options.MaxFileSize = 0 c.Options.ProviderObjects = nil - if len(c.Schedules) == 0 { - return util.NewValidationError("at least one schedule is required") - } - for _, schedule := range c.Schedules { - if err := schedule.validate(); err != nil { - return err - } + c.IDPLoginEvent = 0 + if err := c.validateSchedules(); err != nil { + return err } case EventTriggerIPBlocked, EventTriggerCertificate: c.FsEvents = nil @@ -1299,6 +1380,7 @@ func (c *EventConditions) validate(trigger int) error { c.Options.MinFileSize = 0 c.Options.MaxFileSize = 0 c.Schedules = nil + c.IDPLoginEvent = 0 case EventTriggerOnDemand: c.FsEvents = nil c.ProviderEvents = nil @@ -1308,7 +1390,21 @@ func (c *EventConditions) validate(trigger int) error { c.Options.MaxFileSize = 0 c.Options.ProviderObjects = nil c.Schedules = nil + c.IDPLoginEvent = 0 c.Options.ConcurrentExecution = false + case EventTriggerIDPLogin: + c.FsEvents = nil + c.ProviderEvents = nil + c.Options.GroupNames = nil + c.Options.RoleNames = nil + c.Options.FsPaths = nil + c.Options.Protocols = nil + c.Options.MinFileSize = 0 + c.Options.MaxFileSize = 0 + c.Schedules = nil + if !util.Contains(supportedIDPLoginEvents, c.IDPLoginEvent) { + return util.NewValidationError(fmt.Sprintf("invalid Identity Provider login event %d", c.IDPLoginEvent)) + } default: c.FsEvents = nil c.ProviderEvents = nil @@ -1319,6 +1415,7 @@ func (c *EventConditions) validate(trigger int) error { c.Options.MinFileSize = 0 c.Options.MaxFileSize = 0 c.Schedules = nil + c.IDPLoginEvent = 0 } return c.Options.validate() @@ -1453,7 +1550,7 @@ func (r *EventRule) validateMandatorySyncActions() error { } for _, ev := range r.Conditions.FsEvents { if util.Contains(mandatorySyncFsEvents, ev) { - return util.NewValidationError(fmt.Sprintf("event %s requires at least a sync action", ev)) + return util.NewValidationError(fmt.Sprintf("event %q requires at least a sync action", ev)) } } return nil @@ -1508,6 +1605,39 @@ func (r *EventRule) hasUserAssociated(providerObjectType string) bool { return false } +func (r *EventRule) checkActions(providerObjectType string) error { + numSyncAction := 0 + hasIDPAccountCheck := false + for _, action := range r.Actions { + if action.Options.ExecuteSync { + numSyncAction++ + } + if action.Type == ActionTypeEmail && action.BaseEventAction.Options.EmailConfig.hasFilesAttachments() { + if !r.hasUserAssociated(providerObjectType) { + return errors.New("cannot send an email with attachments for a rule with no user associated") + } + } + if action.Type == ActionTypeHTTP && action.BaseEventAction.Options.HTTPConfig.HasMultipartFiles() { + if !r.hasUserAssociated(providerObjectType) { + return errors.New("cannot upload file/s for a rule with no user associated") + } + } + if action.Type == ActionTypeIDPAccountCheck { + if r.Trigger != EventTriggerIDPLogin { + return errors.New("IDP account check action is only supported for IDP login trigger") + } + if !action.Options.ExecuteSync { + return errors.New("IDP account check must be a sync action") + } + hasIDPAccountCheck = true + } + } + if hasIDPAccountCheck && numSyncAction != 1 { + return errors.New("IDP account check must be the only sync action") + } + return nil +} + // CheckActionsConsistency returns an error if the actions cannot be executed func (r *EventRule) CheckActionsConsistency(providerObjectType string) error { switch r.Trigger { @@ -1528,19 +1658,7 @@ func (r *EventRule) CheckActionsConsistency(providerObjectType string) error { return err } } - for _, action := range r.Actions { - if action.Type == ActionTypeEmail && action.BaseEventAction.Options.EmailConfig.hasFilesAttachments() { - if !r.hasUserAssociated(providerObjectType) { - return errors.New("cannot send an email with attachments for a rule with no user associated") - } - } - if action.Type == ActionTypeHTTP && action.BaseEventAction.Options.HTTPConfig.HasMultipartFiles() { - if !r.hasUserAssociated(providerObjectType) { - return errors.New("cannot upload file/s for a rule with no user associated") - } - } - } - return nil + return r.checkActions(providerObjectType) } // PrepareForRendering prepares an EventRule for rendering. diff --git a/internal/dataprovider/memory.go b/internal/dataprovider/memory.go index 5561addb..82ef6149 100644 --- a/internal/dataprovider/memory.go +++ b/internal/dataprovider/memory.go @@ -431,7 +431,7 @@ func (p *MemoryProvider) updateUser(user *User) error { return nil } -func (p *MemoryProvider) deleteUser(user User, softDelete bool) error { +func (p *MemoryProvider) deleteUser(user User, _ bool) error { p.dbHandle.Lock() defer p.dbHandle.Unlock() if p.dbHandle.isClosed { @@ -898,7 +898,7 @@ func (p *MemoryProvider) updateFolderQuota(name string, filesAdd int, sizeAdd in return nil } -func (p *MemoryProvider) getGroups(limit, offset int, order string, minimal bool) ([]Group, error) { +func (p *MemoryProvider) getGroups(limit, offset int, order string, _ bool) ([]Group, error) { p.dbHandle.Lock() defer p.dbHandle.Unlock() if p.dbHandle.isClosed { @@ -1422,7 +1422,7 @@ func (p *MemoryProvider) folderExistsInternal(name string) (vfs.BaseVirtualFolde return vfs.BaseVirtualFolder{}, util.NewRecordNotFoundError(fmt.Sprintf("folder %q does not exist", name)) } -func (p *MemoryProvider) getFolders(limit, offset int, order string, minimal bool) ([]vfs.BaseVirtualFolder, error) { +func (p *MemoryProvider) getFolders(limit, offset int, order string, _ bool) ([]vfs.BaseVirtualFolder, error) { folders := make([]vfs.BaseVirtualFolder, 0, limit) var err error p.dbHandle.Lock() @@ -2006,75 +2006,75 @@ func (p *MemoryProvider) updateShareLastUse(shareID string, numTokens int) error return nil } -func (p *MemoryProvider) getDefenderHosts(from int64, limit int) ([]DefenderEntry, error) { +func (p *MemoryProvider) getDefenderHosts(_ int64, _ int) ([]DefenderEntry, error) { return nil, ErrNotImplemented } -func (p *MemoryProvider) getDefenderHostByIP(ip string, from int64) (DefenderEntry, error) { +func (p *MemoryProvider) getDefenderHostByIP(_ string, _ int64) (DefenderEntry, error) { return DefenderEntry{}, ErrNotImplemented } -func (p *MemoryProvider) isDefenderHostBanned(ip string) (DefenderEntry, error) { +func (p *MemoryProvider) isDefenderHostBanned(_ string) (DefenderEntry, error) { return DefenderEntry{}, ErrNotImplemented } -func (p *MemoryProvider) updateDefenderBanTime(ip string, minutes int) error { +func (p *MemoryProvider) updateDefenderBanTime(_ string, _ int) error { return ErrNotImplemented } -func (p *MemoryProvider) deleteDefenderHost(ip string) error { +func (p *MemoryProvider) deleteDefenderHost(_ string) error { return ErrNotImplemented } -func (p *MemoryProvider) addDefenderEvent(ip string, score int) error { +func (p *MemoryProvider) addDefenderEvent(_ string, _ int) error { return ErrNotImplemented } -func (p *MemoryProvider) setDefenderBanTime(ip string, banTime int64) error { +func (p *MemoryProvider) setDefenderBanTime(_ string, _ int64) error { return ErrNotImplemented } -func (p *MemoryProvider) cleanupDefender(from int64) error { +func (p *MemoryProvider) cleanupDefender(_ int64) error { return ErrNotImplemented } -func (p *MemoryProvider) addActiveTransfer(transfer ActiveTransfer) error { +func (p *MemoryProvider) addActiveTransfer(_ ActiveTransfer) error { return ErrNotImplemented } -func (p *MemoryProvider) updateActiveTransferSizes(ulSize, dlSize, transferID int64, connectionID string) error { +func (p *MemoryProvider) updateActiveTransferSizes(_, _, _ int64, _ string) error { return ErrNotImplemented } -func (p *MemoryProvider) removeActiveTransfer(transferID int64, connectionID string) error { +func (p *MemoryProvider) removeActiveTransfer(_ int64, _ string) error { return ErrNotImplemented } -func (p *MemoryProvider) cleanupActiveTransfers(before time.Time) error { +func (p *MemoryProvider) cleanupActiveTransfers(_ time.Time) error { return ErrNotImplemented } -func (p *MemoryProvider) getActiveTransfers(from time.Time) ([]ActiveTransfer, error) { +func (p *MemoryProvider) getActiveTransfers(_ time.Time) ([]ActiveTransfer, error) { return nil, ErrNotImplemented } -func (p *MemoryProvider) addSharedSession(session Session) error { +func (p *MemoryProvider) addSharedSession(_ Session) error { return ErrNotImplemented } -func (p *MemoryProvider) deleteSharedSession(key string) error { +func (p *MemoryProvider) deleteSharedSession(_ string) error { return ErrNotImplemented } -func (p *MemoryProvider) getSharedSession(key string) (Session, error) { +func (p *MemoryProvider) getSharedSession(_ string) (Session, error) { return Session{}, ErrNotImplemented } -func (p *MemoryProvider) cleanupSharedSessions(sessionType SessionType, before int64) error { +func (p *MemoryProvider) cleanupSharedSessions(_ SessionType, _ int64) error { return ErrNotImplemented } -func (p *MemoryProvider) getEventActions(limit, offset int, order string, minimal bool) ([]BaseEventAction, error) { +func (p *MemoryProvider) getEventActions(limit, offset int, order string, _ bool) ([]BaseEventAction, error) { p.dbHandle.Lock() defer p.dbHandle.Unlock() if p.dbHandle.isClosed { @@ -2395,7 +2395,7 @@ func (p *MemoryProvider) updateEventRule(rule *EventRule) error { return nil } -func (p *MemoryProvider) deleteEventRule(rule EventRule, softDelete bool) error { +func (p *MemoryProvider) deleteEventRule(rule EventRule, _ bool) error { p.dbHandle.Lock() defer p.dbHandle.Unlock() if p.dbHandle.isClosed { @@ -2420,19 +2420,19 @@ func (p *MemoryProvider) deleteEventRule(rule EventRule, softDelete bool) error return nil } -func (*MemoryProvider) getTaskByName(name string) (Task, error) { +func (*MemoryProvider) getTaskByName(_ string) (Task, error) { return Task{}, ErrNotImplemented } -func (*MemoryProvider) addTask(name string) error { +func (*MemoryProvider) addTask(_ string) error { return ErrNotImplemented } -func (*MemoryProvider) updateTask(name string, version int64) error { +func (*MemoryProvider) updateTask(_ string, _ int64) error { return ErrNotImplemented } -func (*MemoryProvider) updateTaskTimestamp(name string) error { +func (*MemoryProvider) updateTaskTimestamp(_ string) error { return ErrNotImplemented } @@ -2440,7 +2440,7 @@ func (*MemoryProvider) addNode() error { return ErrNotImplemented } -func (*MemoryProvider) getNodeByName(name string) (Node, error) { +func (*MemoryProvider) getNodeByName(_ string) (Node, error) { return Node{}, ErrNotImplemented } @@ -2550,7 +2550,7 @@ func (p *MemoryProvider) deleteRole(role Role) error { return nil } -func (p *MemoryProvider) getRoles(limit int, offset int, order string, minimal bool) ([]Role, error) { +func (p *MemoryProvider) getRoles(limit int, offset int, order string, _ bool) ([]Role, error) { p.dbHandle.Lock() defer p.dbHandle.Unlock() @@ -2662,7 +2662,7 @@ func (p *MemoryProvider) updateIPListEntry(entry *IPListEntry) error { return nil } -func (p *MemoryProvider) deleteIPListEntry(entry IPListEntry, softDelete bool) error { +func (p *MemoryProvider) deleteIPListEntry(entry IPListEntry, _ bool) error { if err := entry.validate(); err != nil { return err } @@ -2721,7 +2721,7 @@ func (p *MemoryProvider) getIPListEntries(listType IPListType, filter, from, ord return entries, nil } -func (p *MemoryProvider) getRecentlyUpdatedIPListEntries(after int64) ([]IPListEntry, error) { +func (p *MemoryProvider) getRecentlyUpdatedIPListEntries(_ int64) ([]IPListEntry, error) { return nil, ErrNotImplemented } @@ -3293,7 +3293,7 @@ func (p *MemoryProvider) migrateDatabase() error { return ErrNoInitRequired } -func (p *MemoryProvider) revertDatabase(targetVersion int) error { +func (p *MemoryProvider) revertDatabase(_ int) error { return errors.New("memory provider does not store data, revert not possible") } diff --git a/internal/dataprovider/sqlcommon.go b/internal/dataprovider/sqlcommon.go index abf99219..934e9d7d 100644 --- a/internal/dataprovider/sqlcommon.go +++ b/internal/dataprovider/sqlcommon.go @@ -3491,7 +3491,7 @@ func sqlCommonUpdateEventAction(action *BaseEventAction, dbHandle *sql.DB) error return err } q = getUpdateRulesTimestampQuery() - _, err = tx.ExecContext(ctx, q, util.GetTimeAsMsSinceEpoch(time.Now()), action.ID) + _, err = tx.ExecContext(ctx, q, util.GetTimeAsMsSinceEpoch(time.Now()), action.Name) return err }) } diff --git a/internal/dataprovider/sqlite.go b/internal/dataprovider/sqlite.go index a2500464..60b4dd2b 100644 --- a/internal/dataprovider/sqlite.go +++ b/internal/dataprovider/sqlite.go @@ -603,7 +603,7 @@ func (*SQLiteProvider) addNode() error { return ErrNotImplemented } -func (*SQLiteProvider) getNodeByName(name string) (Node, error) { +func (*SQLiteProvider) getNodeByName(_ string) (Node, error) { return Node{}, ErrNotImplemented } diff --git a/internal/dataprovider/sqlqueries.go b/internal/dataprovider/sqlqueries.go index 0078cacb..caef2956 100644 --- a/internal/dataprovider/sqlqueries.go +++ b/internal/dataprovider/sqlqueries.go @@ -1106,8 +1106,8 @@ func getClearRuleActionMappingQuery() string { } func getUpdateRulesTimestampQuery() string { - return fmt.Sprintf(`UPDATE %s SET updated_at=%s WHERE id IN (SELECT rule_id FROM %s WHERE action_id = %s)`, - sqlTableEventsRules, sqlPlaceholders[0], sqlTableRulesActionsMapping, sqlPlaceholders[1]) + return fmt.Sprintf(`UPDATE %s SET updated_at=%s WHERE id IN (SELECT rule_id FROM %s WHERE action_id = (SELECT id from %s WHERE name = %s))`, + sqlTableEventsRules, sqlPlaceholders[0], sqlTableRulesActionsMapping, sqlTableEventsActions, sqlPlaceholders[1]) } func getRelatedActionsForRulesQuery(rules []EventRule) string { diff --git a/internal/ftpd/handler.go b/internal/ftpd/handler.go index 2a947464..cd7449b9 100644 --- a/internal/ftpd/handler.go +++ b/internal/ftpd/handler.go @@ -90,29 +90,29 @@ func (c *Connection) GetCommand() string { } // Create is not implemented we use ClientDriverExtentionFileTransfer -func (c *Connection) Create(name string) (afero.File, error) { +func (c *Connection) Create(_ string) (afero.File, error) { return nil, errNotImplemented } // Mkdir creates a directory using the connection filesystem -func (c *Connection) Mkdir(name string, perm os.FileMode) error { +func (c *Connection) Mkdir(name string, _ os.FileMode) error { c.UpdateLastActivity() return c.CreateDir(name, true) } // MkdirAll is not implemented, we don't need it -func (c *Connection) MkdirAll(path string, perm os.FileMode) error { +func (c *Connection) MkdirAll(_ string, _ os.FileMode) error { return errNotImplemented } // Open is not implemented we use ClientDriverExtentionFileTransfer and ClientDriverExtensionFileList -func (c *Connection) Open(name string) (afero.File, error) { +func (c *Connection) Open(_ string) (afero.File, error) { return nil, errNotImplemented } // OpenFile is not implemented we use ClientDriverExtentionFileTransfer -func (c *Connection) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) { +func (c *Connection) OpenFile(_ string, _ int, _ os.FileMode) (afero.File, error) { return nil, errNotImplemented } @@ -140,7 +140,7 @@ func (c *Connection) Remove(name string) error { } // RemoveAll is not implemented, we don't need it -func (c *Connection) RemoveAll(path string) error { +func (c *Connection) RemoveAll(_ string) error { return errNotImplemented } @@ -178,7 +178,7 @@ func (c *Connection) Name() string { } // Chown changes the uid and gid of the named file -func (c *Connection) Chown(name string, uid, gid int) error { +func (c *Connection) Chown(_ string, _, _ int) error { c.UpdateLastActivity() return common.ErrOpUnsupported @@ -270,7 +270,7 @@ func (c *Connection) GetAvailableSpace(dirName string) (int64, error) { } // AllocateSpace implements ClientDriverExtensionAllocate interface -func (c *Connection) AllocateSpace(size int) error { +func (c *Connection) AllocateSpace(_ int) error { c.UpdateLastActivity() // we treat ALLO as NOOP see RFC 959 return nil diff --git a/internal/ftpd/internal_test.go b/internal/ftpd/internal_test.go index f2e74996..6999182a 100644 --- a/internal/ftpd/internal_test.go +++ b/internal/ftpd/internal_test.go @@ -282,11 +282,11 @@ func (cc mockFTPClientContext) Path() string { return "" } -func (cc mockFTPClientContext) SetPath(name string) {} +func (cc mockFTPClientContext) SetPath(_ string) {} -func (cc mockFTPClientContext) SetListPath(name string) {} +func (cc mockFTPClientContext) SetListPath(_ string) {} -func (cc mockFTPClientContext) SetDebug(debug bool) {} +func (cc mockFTPClientContext) SetDebug(_ bool) {} func (cc mockFTPClientContext) Debug() bool { return false @@ -328,7 +328,7 @@ func (cc mockFTPClientContext) HasTLSForTransfers() bool { return false } -func (cc mockFTPClientContext) SetTLSRequirement(requirement ftpserver.TLSRequirement) error { +func (cc mockFTPClientContext) SetTLSRequirement(_ ftpserver.TLSRequirement) error { return nil } @@ -380,7 +380,7 @@ func (fs MockOsFs) Lstat(name string) (os.FileInfo, error) { } // Remove removes the named file or (empty) directory. -func (fs MockOsFs) Remove(name string, isDir bool) error { +func (fs MockOsFs) Remove(name string, _ bool) error { if fs.err != nil { return fs.err } diff --git a/internal/httpd/api_maintenance.go b/internal/httpd/api_maintenance.go index 23e432fd..a542f2d7 100644 --- a/internal/httpd/api_maintenance.go +++ b/internal/httpd/api_maintenance.go @@ -183,7 +183,7 @@ func restoreBackup(content []byte, inputFile string, scanQuota, mode int, execut return util.NewValidationError(fmt.Sprintf("unable to parse backup content: %v", err)) } - if err = RestoreConfigs(dump.Configs, inputFile, mode, executor, ipAddress, role); err != nil { + if err = RestoreConfigs(dump.Configs, mode, executor, ipAddress, role); err != nil { return err } @@ -423,7 +423,7 @@ func RestoreAdmins(admins []dataprovider.Admin, inputFile string, mode int, exec } // RestoreConfigs restores the specified provider configs -func RestoreConfigs(configs *dataprovider.Configs, inputFile string, mode int, executor, ipAddress, +func RestoreConfigs(configs *dataprovider.Configs, mode int, executor, ipAddress, executorRole string, ) error { if configs == nil { diff --git a/internal/httpd/auth_utils.go b/internal/httpd/auth_utils.go index 52082533..c60d9859 100644 --- a/internal/httpd/auth_utils.go +++ b/internal/httpd/auth_utils.go @@ -298,6 +298,7 @@ func (c *jwtTokenClaims) removeCookie(w http.ResponseWriter, r *http.Request, co Secure: isTLS(r), SameSite: http.SameSiteStrictMode, }) + w.Header().Add("Cache-Control", `no-cache="Set-Cookie"`) invalidateToken(r) } diff --git a/internal/httpd/flash.go b/internal/httpd/flash.go index 0c47dd1d..1185df16 100644 --- a/internal/httpd/flash.go +++ b/internal/httpd/flash.go @@ -35,6 +35,7 @@ func setFlashMessage(w http.ResponseWriter, r *http.Request, value string) { Secure: isTLS(r), SameSite: http.SameSiteLaxMode, }) + w.Header().Add("Cache-Control", `no-cache="Set-Cookie"`) } func getFlashMessage(w http.ResponseWriter, r *http.Request) string { diff --git a/internal/httpd/handler.go b/internal/httpd/handler.go index 931effcb..0415c057 100644 --- a/internal/httpd/handler.go +++ b/internal/httpd/handler.go @@ -315,15 +315,15 @@ func (t *throttledReader) HasSizeLimit() bool { return false } -func (t *throttledReader) Truncate(fsPath string, size int64) (int64, error) { +func (t *throttledReader) Truncate(_ string, _ int64) (int64, error) { return 0, vfs.ErrVfsUnsupported } -func (t *throttledReader) GetRealFsPath(fsPath string) string { +func (t *throttledReader) GetRealFsPath(_ string) string { return "" } -func (t *throttledReader) SetTimes(fsPath string, atime time.Time, mtime time.Time) bool { +func (t *throttledReader) SetTimes(_ string, _ time.Time, _ time.Time) bool { return false } diff --git a/internal/httpd/httpd_test.go b/internal/httpd/httpd_test.go index 0cfec07b..be4fb2f3 100644 --- a/internal/httpd/httpd_test.go +++ b/internal/httpd/httpd_test.go @@ -1973,6 +1973,58 @@ func TestOnDemandEventRules(t *testing.T) { assert.NoError(t, err) } +func TestIDPLoginEventRule(t *testing.T) { + ruleName := "test IDP login rule" + a := dataprovider.BaseEventAction{ + Name: "a", + Type: dataprovider.ActionTypeIDPAccountCheck, + Options: dataprovider.BaseEventActionOptions{ + IDPConfig: dataprovider.EventActionIDPAccountCheck{ + Mode: 1, + TemplateUser: `{"username": "user"}`, + TemplateAdmin: `{"username": "admin"}`, + }, + }, + } + action, resp, err := httpdtest.AddEventAction(a, http.StatusCreated) + assert.NoError(t, err, string(resp)) + r := dataprovider.EventRule{ + Name: ruleName, + Status: 1, + Trigger: dataprovider.EventTriggerIDPLogin, + Conditions: dataprovider.EventConditions{ + IDPLoginEvent: 1, + Options: dataprovider.ConditionOptions{ + Names: []dataprovider.ConditionPattern{ + { + Pattern: "username", + }, + }, + }, + }, + Actions: []dataprovider.EventAction{ + { + BaseEventAction: dataprovider.BaseEventAction{ + Name: a.Name, + }, + Options: dataprovider.EventActionOptions{ + ExecuteSync: true, + }, + }, + }, + } + rule, _, err := httpdtest.AddEventRule(r, http.StatusCreated) + assert.NoError(t, err) + rule.Status = 0 + _, _, err = httpdtest.UpdateEventRule(rule, http.StatusOK) + assert.NoError(t, err) + + _, err = httpdtest.RemoveEventRule(rule, http.StatusOK) + assert.NoError(t, err) + _, err = httpdtest.RemoveEventAction(action, http.StatusOK) + assert.NoError(t, err) +} + func TestEventActionValidation(t *testing.T) { action := dataprovider.BaseEventAction{ Name: "", @@ -2235,6 +2287,15 @@ func TestEventActionValidation(t *testing.T) { _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest) assert.NoError(t, err) assert.Contains(t, string(resp), "threshold must be greater than 0") + action.Type = dataprovider.ActionTypeIDPAccountCheck + _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest) + assert.NoError(t, err) + assert.Contains(t, string(resp), "at least a template must be set") + action.Options.IDPConfig.TemplateAdmin = "{}" + action.Options.IDPConfig.Mode = 100 + _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest) + assert.NoError(t, err) + assert.Contains(t, string(resp), "invalid account check mode") } func TestEventRuleValidation(t *testing.T) { @@ -2367,7 +2428,7 @@ func TestEventRuleValidation(t *testing.T) { } _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest) assert.NoError(t, err) - assert.Contains(t, string(resp), "event pre-upload requires at least a sync action") + assert.Contains(t, string(resp), "requires at least a sync action") rule.Actions = []dataprovider.EventAction{ { BaseEventAction: dataprovider.BaseEventAction{ @@ -2431,6 +2492,11 @@ func TestEventRuleValidation(t *testing.T) { } _, resp, err = httpdtest.AddEventRule(rule, http.StatusInternalServerError) assert.NoError(t, err, string(resp)) + rule.Trigger = dataprovider.EventTriggerIDPLogin + rule.Conditions.IDPLoginEvent = 100 + _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest) + assert.NoError(t, err) + assert.Contains(t, string(resp), "invalid Identity Provider login event") } func TestUserTransferLimits(t *testing.T) { @@ -21532,6 +21598,26 @@ func TestWebEventAction(t *testing.T) { assert.Equal(t, 0, actionGet.Options.CmdConfig.Timeout) assert.Len(t, actionGet.Options.CmdConfig.EnvVars, 0) + action.Type = dataprovider.ActionTypeIDPAccountCheck + form.Set("type", fmt.Sprintf("%d", action.Type)) + form.Set("idp_mode", "1") + form.Set("idp_user", `{"username":"user"}`) + form.Set("idp_admin", `{"username":"admin"}`) + form.Set("pwd_expiration_threshold", strconv.Itoa(action.Options.PwdExpirationConfig.Threshold)) + req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name), + bytes.NewBuffer([]byte(form.Encode()))) + assert.NoError(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + setJWTCookieForReq(req, webToken) + rr = executeRequest(req) + checkResponseCode(t, http.StatusSeeOther, rr) + actionGet, _, err = httpdtest.GetEventActionByName(action.Name, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, action.Type, actionGet.Type) + assert.Equal(t, 1, actionGet.Options.IDPConfig.Mode) + assert.Contains(t, actionGet.Options.IDPConfig.TemplateUser, `"user"`) + assert.Contains(t, actionGet.Options.IDPConfig.TemplateAdmin, `"admin"`) + req, err = http.NewRequest(http.MethodDelete, path.Join(webAdminEventActionPath, action.Name), nil) assert.NoError(t, err) setBearerForReq(req, apiToken) @@ -21785,6 +21871,36 @@ func TestWebEventRule(t *testing.T) { assert.Equal(t, rule.Actions[0].Name, ruleGet.Actions[0].Name) assert.Equal(t, rule.Actions[0].Order, ruleGet.Actions[0].Order) } + rule.Trigger = dataprovider.EventTriggerIDPLogin + form.Set("trigger", fmt.Sprintf("%d", rule.Trigger)) + form.Set("idp_login_event", "1") + req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventRulePath, rule.Name), + bytes.NewBuffer([]byte(form.Encode()))) + assert.NoError(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + setJWTCookieForReq(req, webToken) + rr = executeRequest(req) + checkResponseCode(t, http.StatusSeeOther, rr) + // check the rule + ruleGet, _, err = httpdtest.GetEventRuleByName(rule.Name, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, rule.Trigger, ruleGet.Trigger) + assert.Equal(t, 1, ruleGet.Conditions.IDPLoginEvent) + + form.Set("idp_login_event", "2") + req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventRulePath, rule.Name), + bytes.NewBuffer([]byte(form.Encode()))) + assert.NoError(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + setJWTCookieForReq(req, webToken) + rr = executeRequest(req) + checkResponseCode(t, http.StatusSeeOther, rr) + // check the rule + ruleGet, _, err = httpdtest.GetEventRuleByName(rule.Name, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, rule.Trigger, ruleGet.Trigger) + assert.Equal(t, 2, ruleGet.Conditions.IDPLoginEvent) + // update a missing rule req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventRulePath, rule.Name+"1"), bytes.NewBuffer([]byte(form.Encode()))) diff --git a/internal/httpd/internal_test.go b/internal/httpd/internal_test.go index 6f6312d8..7861f0e0 100644 --- a/internal/httpd/internal_test.go +++ b/internal/httpd/internal_test.go @@ -292,11 +292,11 @@ var ( type failingWriter struct { } -func (r *failingWriter) Write(p []byte) (n int, err error) { +func (r *failingWriter) Write(_ []byte) (n int, err error) { return 0, errors.New("write error") } -func (r *failingWriter) WriteHeader(statusCode int) {} +func (r *failingWriter) WriteHeader(_ int) {} func (r *failingWriter) Header() http.Header { return make(http.Header) diff --git a/internal/httpd/oidc.go b/internal/httpd/oidc.go index 35a0d8ec..bfa33c2a 100644 --- a/internal/httpd/oidc.go +++ b/internal/httpd/oidc.go @@ -410,47 +410,70 @@ func (t *oidcToken) refreshUser(r *http.Request) error { } func (t *oidcToken) getUser(r *http.Request) error { + ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) + params := common.EventParams{ + Name: t.Username, + IP: ipAddr, + Protocol: common.ProtocolOIDC, + Timestamp: time.Now().UnixNano(), + Status: 1, + } if t.isAdmin() { - admin, err := dataprovider.AdminExists(t.Username) + params.Event = common.IDPLoginAdmin + _, admin, err := common.HandleIDPLoginEvent(params, t.CustomFields) if err != nil { return err } - if err := admin.CanLogin(util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil { + if admin == nil { + a, err := dataprovider.AdminExists(t.Username) + if err != nil { + return err + } + admin = &a + } + if err := admin.CanLogin(ipAddr); err != nil { return err } t.Permissions = admin.Permissions t.TokenRole = admin.Role t.HideUserPageSections = admin.Filters.Preferences.HideUserPageSections - dataprovider.UpdateAdminLastLogin(&admin) + dataprovider.UpdateAdminLastLogin(admin) return nil } - ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) - user, err := dataprovider.GetUserAfterIDPAuth(t.Username, ipAddr, common.ProtocolOIDC, t.CustomFields) + params.Event = common.IDPLoginUser + user, _, err := common.HandleIDPLoginEvent(params, t.CustomFields) if err != nil { return err } + if user == nil { + u, err := dataprovider.GetUserAfterIDPAuth(t.Username, ipAddr, common.ProtocolOIDC, t.CustomFields) + if err != nil { + return err + } + user = &u + } if err := common.Config.ExecutePostConnectHook(ipAddr, common.ProtocolOIDC); err != nil { - updateLoginMetrics(&user, dataprovider.LoginMethodIDP, ipAddr, err) + updateLoginMetrics(user, dataprovider.LoginMethodIDP, ipAddr, err) return fmt.Errorf("access denied: %w", err) } if err := user.CheckLoginConditions(); err != nil { - updateLoginMetrics(&user, dataprovider.LoginMethodIDP, ipAddr, err) + updateLoginMetrics(user, dataprovider.LoginMethodIDP, ipAddr, err) return err } - connectionID := fmt.Sprintf("%v_%v", common.ProtocolOIDC, xid.New().String()) - if err := checkHTTPClientUser(&user, r, connectionID, true); err != nil { - updateLoginMetrics(&user, dataprovider.LoginMethodIDP, ipAddr, err) + connectionID := fmt.Sprintf("%s_%s", common.ProtocolOIDC, xid.New().String()) + if err := checkHTTPClientUser(user, r, connectionID, true); err != nil { + updateLoginMetrics(user, dataprovider.LoginMethodIDP, ipAddr, err) return err } defer user.CloseFs() //nolint:errcheck err = user.CheckFsRoot(connectionID) if err != nil { logger.Warn(logSender, connectionID, "unable to check fs root: %v", err) - updateLoginMetrics(&user, dataprovider.LoginMethodIDP, ipAddr, common.ErrInternalFailure) + updateLoginMetrics(user, dataprovider.LoginMethodIDP, ipAddr, common.ErrInternalFailure) return err } - updateLoginMetrics(&user, dataprovider.LoginMethodIDP, ipAddr, nil) - dataprovider.UpdateLastLogin(&user) + updateLoginMetrics(user, dataprovider.LoginMethodIDP, ipAddr, nil) + dataprovider.UpdateLastLogin(user) t.Permissions = user.Filters.WebClient t.TokenRole = user.Role return nil diff --git a/internal/httpd/oidc_test.go b/internal/httpd/oidc_test.go index cc74f575..9ab9e94c 100644 --- a/internal/httpd/oidc_test.go +++ b/internal/httpd/oidc_test.go @@ -66,15 +66,15 @@ type mockOAuth2Config struct { err error } -func (c *mockOAuth2Config) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string { +func (c *mockOAuth2Config) AuthCodeURL(_ string, _ ...oauth2.AuthCodeOption) string { return c.authCodeURL } -func (c *mockOAuth2Config) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) { +func (c *mockOAuth2Config) Exchange(_ context.Context, _ string, _ ...oauth2.AuthCodeOption) (*oauth2.Token, error) { return c.token, c.err } -func (c *mockOAuth2Config) TokenSource(ctx context.Context, t *oauth2.Token) oauth2.TokenSource { +func (c *mockOAuth2Config) TokenSource(_ context.Context, _ *oauth2.Token) oauth2.TokenSource { return c.tokenSource } @@ -83,7 +83,7 @@ type mockOIDCVerifier struct { err error } -func (v *mockOIDCVerifier) Verify(ctx context.Context, rawIDToken string) (*oidc.IDToken, error) { +func (v *mockOIDCVerifier) Verify(_ context.Context, _ string) (*oidc.IDToken, error) { return v.token, v.err } @@ -1130,6 +1130,180 @@ func TestMemoryOIDCManager(t *testing.T) { require.Len(t, oidcMgr.tokens, 0) } +func TestOIDCEvMgrIntegration(t *testing.T) { + providerConf := dataprovider.GetProviderConfig() + err := dataprovider.Close() + assert.NoError(t, err) + newProviderConf := providerConf + newProviderConf.NamingRules = 5 + err = dataprovider.Initialize(newProviderConf, configDir, true) + assert.NoError(t, err) + // add a special chars to check json replacer + username := `test_"oidc_eventmanager` + u := map[string]any{ + "username": "{{Name}}", + "status": 1, + "home_dir": filepath.Join(os.TempDir(), "{{IDPFieldcustom1}}"), + "permissions": map[string][]string{ + "/": {dataprovider.PermAny}, + }, + } + userTmpl, err := json.Marshal(u) + require.NoError(t, err) + a := map[string]any{ + "username": "{{Name}}", + "status": 1, + "permissions": []string{dataprovider.PermAdminAny}, + } + adminTmpl, err := json.Marshal(a) + require.NoError(t, err) + + action := &dataprovider.BaseEventAction{ + Name: "a", + Type: dataprovider.ActionTypeIDPAccountCheck, + Options: dataprovider.BaseEventActionOptions{ + IDPConfig: dataprovider.EventActionIDPAccountCheck{ + Mode: 0, + TemplateUser: string(userTmpl), + TemplateAdmin: string(adminTmpl), + }, + }, + } + err = dataprovider.AddEventAction(action, "", "", "") + assert.NoError(t, err) + rule := &dataprovider.EventRule{ + Name: "r", + Status: 1, + Trigger: dataprovider.EventTriggerIDPLogin, + Conditions: dataprovider.EventConditions{ + IDPLoginEvent: 0, + }, + Actions: []dataprovider.EventAction{ + { + BaseEventAction: dataprovider.BaseEventAction{ + Name: action.Name, + }, + Options: dataprovider.EventActionOptions{ + ExecuteSync: true, + }, + }, + }, + } + err = dataprovider.AddEventRule(rule, "", "", "") + assert.NoError(t, err) + + oidcMgr, ok := oidcMgr.(*memoryOIDCManager) + require.True(t, ok) + server := getTestOIDCServer() + server.binding.OIDC.ImplicitRoles = true + server.binding.OIDC.CustomFields = []string{"custom1", "custom2"} + err = server.binding.OIDC.initialize() + assert.NoError(t, err) + server.initializeRouter() + // login a user with OIDC + _, err = dataprovider.UserExists(username, "") + assert.ErrorIs(t, err, util.ErrNotFound) + authReq := newOIDCPendingAuth(tokenAudienceWebClient) + oidcMgr.addPendingAuth(authReq) + token := &oauth2.Token{ + AccessToken: "1234", + Expiry: time.Now().Add(5 * time.Minute), + } + token = token.WithExtra(map[string]any{ + "id_token": "id_token_val", + }) + server.binding.OIDC.oauth2Config = &mockOAuth2Config{ + tokenSource: &mockTokenSource{}, + authCodeURL: webOIDCRedirectPath, + token: token, + } + idToken := &oidc.IDToken{ + Nonce: authReq.Nonce, + Expiry: time.Now().Add(5 * time.Minute), + } + setIDTokenClaims(idToken, []byte(`{"preferred_username":"`+util.JSONEscape(username)+`","custom1":"val1"}`)) + server.binding.OIDC.verifier = &mockOIDCVerifier{ + err: nil, + token: idToken, + } + rr := httptest.NewRecorder() + r, err := http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil) + assert.NoError(t, err) + server.router.ServeHTTP(rr, r) + assert.Equal(t, http.StatusFound, rr.Code) + assert.Equal(t, webClientFilesPath, rr.Header().Get("Location")) + user, err := dataprovider.UserExists(username, "") + assert.NoError(t, err) + assert.Equal(t, filepath.Join(os.TempDir(), "val1"), user.GetHomeDir()) + + err = dataprovider.DeleteUser(username, "", "", "") + assert.NoError(t, err) + err = os.RemoveAll(user.GetHomeDir()) + assert.NoError(t, err) + // login an admin with OIDC + _, err = dataprovider.AdminExists(username) + assert.ErrorIs(t, err, util.ErrNotFound) + authReq = newOIDCPendingAuth(tokenAudienceWebAdmin) + oidcMgr.addPendingAuth(authReq) + idToken = &oidc.IDToken{ + Nonce: authReq.Nonce, + Expiry: time.Now().Add(5 * time.Minute), + } + setIDTokenClaims(idToken, []byte(`{"preferred_username":"`+util.JSONEscape(username)+`"}`)) + server.binding.OIDC.verifier = &mockOIDCVerifier{ + err: nil, + token: idToken, + } + rr = httptest.NewRecorder() + r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil) + assert.NoError(t, err) + server.router.ServeHTTP(rr, r) + assert.Equal(t, http.StatusFound, rr.Code) + assert.Equal(t, webUsersPath, rr.Header().Get("Location")) + + _, err = dataprovider.AdminExists(username) + assert.NoError(t, err) + err = dataprovider.DeleteAdmin(username, "", "", "") + assert.NoError(t, err) + // set invalid templates and try again + action.Options.IDPConfig.TemplateUser = `{}` + action.Options.IDPConfig.TemplateAdmin = `{}` + err = dataprovider.UpdateEventAction(action, "", "", "") + assert.NoError(t, err) + + for _, audience := range []string{tokenAudienceWebAdmin, tokenAudienceWebClient} { + authReq = newOIDCPendingAuth(audience) + oidcMgr.addPendingAuth(authReq) + idToken = &oidc.IDToken{ + Nonce: authReq.Nonce, + Expiry: time.Now().Add(5 * time.Minute), + } + setIDTokenClaims(idToken, []byte(`{"preferred_username":"`+util.JSONEscape(username)+`"}`)) + server.binding.OIDC.verifier = &mockOIDCVerifier{ + err: nil, + token: idToken, + } + rr = httptest.NewRecorder() + r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil) + assert.NoError(t, err) + server.router.ServeHTTP(rr, r) + assert.Equal(t, http.StatusFound, rr.Code) + } + for k := range oidcMgr.tokens { + oidcMgr.removeToken(k) + } + + err = dataprovider.DeleteEventRule(rule.Name, "", "", "") + assert.NoError(t, err) + err = dataprovider.DeleteEventAction(action.Name, "", "", "") + assert.NoError(t, err) + + err = dataprovider.Close() + assert.NoError(t, err) + err = dataprovider.Initialize(providerConf, configDir, true) + assert.NoError(t, err) +} + func TestOIDCPreLoginHook(t *testing.T) { if runtime.GOOS == osWindows { t.Skip("this test is not available on Windows") diff --git a/internal/httpd/webadmin.go b/internal/httpd/webadmin.go index 0c963322..92438c90 100644 --- a/internal/httpd/webadmin.go +++ b/internal/httpd/webadmin.go @@ -2280,6 +2280,10 @@ func getEventActionOptionsFromPostFields(r *http.Request) (dataprovider.BaseEven if r.Form.Get("cmd_arguments") != "" { cmdArgs = getSliceFromDelimitedValues(r.Form.Get("cmd_arguments"), ",") } + idpMode := 0 + if r.Form.Get("idp_mode") == "1" { + idpMode = 1 + } options := dataprovider.BaseEventActionOptions{ HTTPConfig: dataprovider.EventActionHTTPConfig{ Endpoint: r.Form.Get("http_endpoint"), @@ -2323,6 +2327,11 @@ func getEventActionOptionsFromPostFields(r *http.Request) (dataprovider.BaseEven PwdExpirationConfig: dataprovider.EventActionPasswordExpiration{ Threshold: pwdExpirationThreshold, }, + IDPConfig: dataprovider.EventActionIDPAccountCheck{ + Mode: idpMode, + TemplateUser: strings.TrimSpace(r.Form.Get("idp_user")), + TemplateAdmin: strings.TrimSpace(r.Form.Get("idp_admin")), + }, } return options, nil } @@ -2349,6 +2358,17 @@ func getEventActionFromPostFields(r *http.Request) (dataprovider.BaseEventAction return action, nil } +func getIDPLoginEventFromPostField(r *http.Request) int { + switch r.Form.Get("idp_login_event") { + case "1": + return 1 + case "2": + return 2 + default: + return 0 + } +} + func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventConditions, error) { var schedules []dataprovider.Schedule var names, groupNames, roleNames, fsPaths []dataprovider.ConditionPattern @@ -2424,6 +2444,7 @@ func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventCo conditions := dataprovider.EventConditions{ FsEvents: r.Form["fs_events"], ProviderEvents: r.Form["provider_events"], + IDPLoginEvent: getIDPLoginEventFromPostField(r), Schedules: schedules, Options: dataprovider.ConditionOptions{ Names: names, diff --git a/internal/httpdtest/httpdtest.go b/internal/httpdtest/httpdtest.go index a97e11e9..9796a7d2 100644 --- a/internal/httpdtest/httpdtest.go +++ b/internal/httpdtest/httpdtest.go @@ -1596,6 +1596,9 @@ func checkEventAction(expected, actual dataprovider.BaseEventAction) error { if expected.Options.PwdExpirationConfig.Threshold != actual.Options.PwdExpirationConfig.Threshold { return errors.New("password expiration threshold mismatch") } + if err := compareEventActionIDPConfigFields(expected.Options.IDPConfig, actual.Options.IDPConfig); err != nil { + return err + } if err := compareEventActionCmdConfigFields(expected.Options.CmdConfig, actual.Options.CmdConfig); err != nil { return err } @@ -1707,6 +1710,9 @@ func checkEventConditions(expected, actual dataprovider.EventConditions) error { if err := checkEventConditionOptions(expected.Options, actual.Options); err != nil { return err } + if expected.IDPLoginEvent != actual.IDPLoginEvent { + return errors.New("IDP login event mismatch") + } return checkEventSchedules(expected.Schedules, actual.Schedules) } @@ -2707,6 +2713,19 @@ func compareEventActionFsConfigFields(expected, actual dataprovider.EventActionF return compareEventActionFsCompressFields(expected.Compress, actual.Compress) } +func compareEventActionIDPConfigFields(expected, actual dataprovider.EventActionIDPAccountCheck) error { + if expected.Mode != actual.Mode { + return errors.New("mode mismatch") + } + if expected.TemplateAdmin != actual.TemplateAdmin { + return errors.New("admin template mismatch") + } + if expected.TemplateUser != actual.TemplateUser { + return errors.New("user template mismatch") + } + return nil +} + func compareEventActionCmdConfigFields(expected, actual dataprovider.EventActionCommandConfig) error { if expected.Cmd != actual.Cmd { return errors.New("command mismatch") diff --git a/internal/kms/builtin.go b/internal/kms/builtin.go index fed91f49..4de06be6 100644 --- a/internal/kms/builtin.go +++ b/internal/kms/builtin.go @@ -38,7 +38,7 @@ func init() { RegisterSecretProvider(sdkkms.SchemeBuiltin, sdkkms.SecretStatusAES256GCM, newBuiltinSecret) } -func newBuiltinSecret(base BaseSecret, url, masterKey string) SecretProvider { +func newBuiltinSecret(base BaseSecret, _, _ string) SecretProvider { return &builtinSecret{ BaseSecret: base, } diff --git a/internal/kms/local.go b/internal/kms/local.go index 81dc9a26..34da4de3 100644 --- a/internal/kms/local.go +++ b/internal/kms/local.go @@ -36,7 +36,7 @@ type localSecret struct { } // NewLocalSecret returns a SecretProvider that use a locally provided symmetric key -func NewLocalSecret(base BaseSecret, url, masterKey string) SecretProvider { +func NewLocalSecret(base BaseSecret, _, masterKey string) SecretProvider { return &localSecret{ BaseSecret: base, masterKey: masterKey, diff --git a/internal/logger/hclog.go b/internal/logger/hclog.go index 932148ae..50ee45db 100644 --- a/internal/logger/hclog.go +++ b/internal/logger/hclog.go @@ -81,11 +81,11 @@ func (l *HCLogAdapter) Named(name string) hclog.Logger { } // StandardLogger returns a value that conforms to the stdlib log.Logger interface -func (l *HCLogAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger { +func (l *HCLogAdapter) StandardLogger(_ *hclog.StandardLoggerOptions) *log.Logger { return log.New(&StdLoggerWrapper{Sender: l.Name()}, "", 0) } // StandardWriter returns a value that conforms to io.Writer, which can be passed into log.SetOutput() -func (l *HCLogAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer { +func (l *HCLogAdapter) StandardWriter(_ *hclog.StandardLoggerOptions) io.Writer { return &StdLoggerWrapper{Sender: l.Name()} } diff --git a/internal/logger/request_logger.go b/internal/logger/request_logger.go index e3b6a6ec..752a3e38 100644 --- a/internal/logger/request_logger.go +++ b/internal/logger/request_logger.go @@ -71,7 +71,7 @@ func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry { } // Write logs a new entry at the end of the HTTP request -func (l *StructuredLoggerEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra any) { +func (l *StructuredLoggerEntry) Write(status, bytes int, _ http.Header, elapsed time.Duration, _ any) { metric.HTTPRequestServed(status) l.Logger.Info(). Timestamp(). diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go index a233622d..970062d3 100644 --- a/internal/plugin/plugin.go +++ b/internal/plugin/plugin.go @@ -338,7 +338,7 @@ func (m *Manager) SetModificationTime(storageID, objectPath string, mTime int64) } // GetModificationTime returns the modification time for the specified path -func (m *Manager) GetModificationTime(storageID, objectPath string, isDir bool) (int64, error) { +func (m *Manager) GetModificationTime(storageID, objectPath string, _ bool) (int64, error) { if !m.hasMetadater { return 0, ErrNoMetadater } diff --git a/internal/service/service.go b/internal/service/service.go index d16ddf99..0ec5daba 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -351,7 +351,7 @@ func (s *Service) LoadInitialData() error { } func (s *Service) restoreDump(dump *dataprovider.BackupData) error { - err := httpd.RestoreConfigs(dump.Configs, s.LoadDataFrom, s.LoadDataMode, dataprovider.ActionExecutorSystem, "", "") + err := httpd.RestoreConfigs(dump.Configs, s.LoadDataMode, dataprovider.ActionExecutorSystem, "", "") if err != nil { return fmt.Errorf("unable to restore configs from file %q: %v", s.LoadDataFrom, err) } diff --git a/internal/sftpd/internal_test.go b/internal/sftpd/internal_test.go index f0a5733a..36caac36 100644 --- a/internal/sftpd/internal_test.go +++ b/internal/sftpd/internal_test.go @@ -83,7 +83,7 @@ func (c *MockChannel) CloseWrite() error { return nil } -func (c *MockChannel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) { +func (c *MockChannel) SendRequest(_ string, _ bool, _ []byte) (bool, error) { return true, nil } @@ -131,7 +131,7 @@ func (fs MockOsFs) Lstat(name string) (os.FileInfo, error) { } // Remove removes the named file or (empty) directory. -func (fs MockOsFs) Remove(name string, isDir bool) error { +func (fs MockOsFs) Remove(name string, _ bool) error { if fs.err != nil { return fs.err } diff --git a/internal/sftpd/transfer.go b/internal/sftpd/transfer.go index 65331c1e..b9139d00 100644 --- a/internal/sftpd/transfer.go +++ b/internal/sftpd/transfer.go @@ -40,7 +40,7 @@ type failingReader struct { errRead error } -func (r *failingReader) ReadAt(p []byte, off int64) (n int, err error) { +func (r *failingReader) ReadAt(_ []byte, _ int64) (n int, err error) { return 0, r.errRead } diff --git a/internal/smtp/smtp.go b/internal/smtp/smtp.go index 0b1f2d32..9512bd52 100644 --- a/internal/smtp/smtp.go +++ b/internal/smtp/smtp.go @@ -30,6 +30,7 @@ import ( "github.com/drakkan/sftpgo/v2/internal/dataprovider" "github.com/drakkan/sftpgo/v2/internal/logger" "github.com/drakkan/sftpgo/v2/internal/util" + "github.com/drakkan/sftpgo/v2/internal/version" ) const ( @@ -287,7 +288,9 @@ func (c *Config) getMailClientOptions() []mail.Option { func (c *Config) getSMTPClientAndMsg(to []string, subject, body string, contentType EmailContentType, attachments ...*mail.File) (*mail.Client, *mail.Msg, error) { + version := version.Get() msg := mail.NewMsg() + msg.SetUserAgent(fmt.Sprintf("SFTPGo-%s-%s", version.Version, version.CommitHash)) var from string if c.From != "" { diff --git a/internal/util/util.go b/internal/util/util.go index 0bad44c8..56526614 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -24,6 +24,7 @@ import ( "crypto/rsa" "crypto/tls" "crypto/x509" + "encoding/json" "encoding/pem" "errors" "fmt" @@ -589,7 +590,7 @@ func HTTPListenAndServe(srv *http.Server, address string, port int, isTLS bool, return err } - logger.Info(logSender, "", "server listener registered, address: %v TLS enabled: %v", listener.Addr().String(), isTLS) + logger.Info(logSender, "", "server listener registered, address: %s TLS enabled: %t", listener.Addr().String(), isTLS) defer listener.Close() @@ -834,3 +835,15 @@ func GetLastIPForPrefix(p netip.Prefix) netip.Addr { } return netip.AddrFrom16(a16) // doesn't unmap } + +// JSONEscape returns the JSON escaped format for the input string +func JSONEscape(val string) string { + if val == "" { + return val + } + b, err := json.Marshal(val) + if err != nil { + return "" + } + return string(b[1 : len(b)-1]) +} diff --git a/internal/vfs/azblobfs.go b/internal/vfs/azblobfs.go index 5b0bb1a3..1189670e 100644 --- a/internal/vfs/azblobfs.go +++ b/internal/vfs/azblobfs.go @@ -326,27 +326,27 @@ func (fs *AzureBlobFs) Mkdir(name string) error { } // Symlink creates source as a symbolic link to target. -func (*AzureBlobFs) Symlink(source, target string) error { +func (*AzureBlobFs) Symlink(_, _ string) error { return ErrVfsUnsupported } // Readlink returns the destination of the named symbolic link -func (*AzureBlobFs) Readlink(name string) (string, error) { +func (*AzureBlobFs) Readlink(_ string) (string, error) { return "", ErrVfsUnsupported } // Chown changes the numeric uid and gid of the named file. -func (*AzureBlobFs) Chown(name string, uid int, gid int) error { +func (*AzureBlobFs) Chown(_ string, _ int, _ int) error { return ErrVfsUnsupported } // Chmod changes the mode of the named file to mode. -func (*AzureBlobFs) Chmod(name string, mode os.FileMode) error { +func (*AzureBlobFs) Chmod(_ string, _ os.FileMode) error { return ErrVfsUnsupported } // Chtimes changes the access and modification times of the named file. -func (fs *AzureBlobFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error { +func (fs *AzureBlobFs) Chtimes(name string, _, mtime time.Time, isUploading bool) error { if !plugin.Handler.HasMetadater() { return ErrVfsUnsupported } @@ -367,7 +367,7 @@ func (fs *AzureBlobFs) Chtimes(name string, atime, mtime time.Time, isUploading // Truncate changes the size of the named file. // Truncate by path is not supported, while truncating an opened // file is handled inside base transfer -func (*AzureBlobFs) Truncate(name string, size int64) error { +func (*AzureBlobFs) Truncate(_ string, _ int64) error { return ErrVfsUnsupported } @@ -609,7 +609,7 @@ func (fs *AzureBlobFs) GetDirSize(dirname string) (int, int64, error) { // GetAtomicUploadPath returns the path to use for an atomic upload. // Azure Blob Storage uploads are already atomic, we never call this method -func (*AzureBlobFs) GetAtomicUploadPath(name string) string { +func (*AzureBlobFs) GetAtomicUploadPath(_ string) string { return "" } @@ -702,7 +702,7 @@ func (fs *AzureBlobFs) ResolvePath(virtualPath string) (string, error) { } // CopyFile implements the FsFileCopier interface -func (fs *AzureBlobFs) CopyFile(source, target string, srcSize int64) error { +func (fs *AzureBlobFs) CopyFile(source, target string, _ int64) error { return fs.copyFileInternal(source, target) } @@ -731,7 +731,7 @@ func (*AzureBlobFs) Close() error { } // GetAvailableDiskSize returns the available size for the specified path -func (*AzureBlobFs) GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error) { +func (*AzureBlobFs) GetAvailableDiskSize(_ string) (*sftp.StatVFS, error) { return nil, ErrStorageSizeUnavailable } diff --git a/internal/vfs/cryptfs.go b/internal/vfs/cryptfs.go index ace2a28e..f39f9816 100644 --- a/internal/vfs/cryptfs.go +++ b/internal/vfs/cryptfs.go @@ -206,7 +206,7 @@ func (fs *CryptFs) Create(name string, flag int) (File, *PipeWriter, func(), err } // Truncate changes the size of the named file -func (*CryptFs) Truncate(name string, size int64) error { +func (*CryptFs) Truncate(_ string, _ int64) error { return ErrVfsUnsupported } diff --git a/internal/vfs/gcsfs.go b/internal/vfs/gcsfs.go index 965a54aa..6e898e80 100644 --- a/internal/vfs/gcsfs.go +++ b/internal/vfs/gcsfs.go @@ -284,27 +284,27 @@ func (fs *GCSFs) Mkdir(name string) error { } // Symlink creates source as a symbolic link to target. -func (*GCSFs) Symlink(source, target string) error { +func (*GCSFs) Symlink(_, _ string) error { return ErrVfsUnsupported } // Readlink returns the destination of the named symbolic link -func (*GCSFs) Readlink(name string) (string, error) { +func (*GCSFs) Readlink(_ string) (string, error) { return "", ErrVfsUnsupported } // Chown changes the numeric uid and gid of the named file. -func (*GCSFs) Chown(name string, uid int, gid int) error { +func (*GCSFs) Chown(_ string, _ int, _ int) error { return ErrVfsUnsupported } // Chmod changes the mode of the named file to mode. -func (*GCSFs) Chmod(name string, mode os.FileMode) error { +func (*GCSFs) Chmod(_ string, _ os.FileMode) error { return ErrVfsUnsupported } // Chtimes changes the access and modification times of the named file. -func (fs *GCSFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error { +func (fs *GCSFs) Chtimes(name string, _, mtime time.Time, isUploading bool) error { if !plugin.Handler.HasMetadater() { return ErrVfsUnsupported } @@ -325,7 +325,7 @@ func (fs *GCSFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) // Truncate changes the size of the named file. // Truncate by path is not supported, while truncating an opened // file is handled inside base transfer -func (*GCSFs) Truncate(name string, size int64) error { +func (*GCSFs) Truncate(_ string, _ int64) error { return ErrVfsUnsupported } @@ -587,7 +587,7 @@ func (fs *GCSFs) GetDirSize(dirname string) (int, int64, error) { // GetAtomicUploadPath returns the path to use for an atomic upload. // GCS uploads are already atomic, we never call this method for GCS -func (*GCSFs) GetAtomicUploadPath(name string) string { +func (*GCSFs) GetAtomicUploadPath(_ string) string { return "" } @@ -688,7 +688,7 @@ func (fs *GCSFs) ResolvePath(virtualPath string) (string, error) { } // CopyFile implements the FsFileCopier interface -func (fs *GCSFs) CopyFile(source, target string, srcSize int64) error { +func (fs *GCSFs) CopyFile(source, target string, _ int64) error { return fs.copyFileInternal(source, target) } @@ -904,7 +904,7 @@ func (fs *GCSFs) Close() error { } // GetAvailableDiskSize returns the available size for the specified path -func (*GCSFs) GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error) { +func (*GCSFs) GetAvailableDiskSize(_ string) (*sftp.StatVFS, error) { return nil, ErrStorageSizeUnavailable } diff --git a/internal/vfs/httpfs.go b/internal/vfs/httpfs.go index 35f1df69..ab5c1b2e 100644 --- a/internal/vfs/httpfs.go +++ b/internal/vfs/httpfs.go @@ -385,7 +385,7 @@ func (fs *HTTPFs) Rename(source, target string) (int, int64, error) { } // Remove removes the named file or (empty) directory. -func (fs *HTTPFs) Remove(name string, isDir bool) error { +func (fs *HTTPFs) Remove(name string, _ bool) error { ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout)) defer cancelFn() @@ -411,17 +411,17 @@ func (fs *HTTPFs) Mkdir(name string) error { } // Symlink creates source as a symbolic link to target. -func (*HTTPFs) Symlink(source, target string) error { +func (*HTTPFs) Symlink(_, _ string) error { return ErrVfsUnsupported } // Readlink returns the destination of the named symbolic link -func (*HTTPFs) Readlink(name string) (string, error) { +func (*HTTPFs) Readlink(_ string) (string, error) { return "", ErrVfsUnsupported } // Chown changes the numeric uid and gid of the named file. -func (fs *HTTPFs) Chown(name string, uid int, gid int) error { +func (fs *HTTPFs) Chown(_ string, _ int, _ int) error { return ErrVfsUnsupported } @@ -440,7 +440,7 @@ func (fs *HTTPFs) Chmod(name string, mode os.FileMode) error { } // Chtimes changes the access and modification times of the named file. -func (fs *HTTPFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error { +func (fs *HTTPFs) Chtimes(name string, atime, mtime time.Time, _ bool) error { ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout)) defer cancelFn() @@ -562,7 +562,7 @@ func (fs *HTTPFs) GetDirSize(dirname string) (int, int64, error) { } // GetAtomicUploadPath returns the path to use for an atomic upload. -func (*HTTPFs) GetAtomicUploadPath(name string) string { +func (*HTTPFs) GetAtomicUploadPath(_ string) string { return "" } diff --git a/internal/vfs/osfs.go b/internal/vfs/osfs.go index 90bdc87b..1bf59870 100644 --- a/internal/vfs/osfs.go +++ b/internal/vfs/osfs.go @@ -140,7 +140,7 @@ func (fs *OsFs) Rename(source, target string) (int, int64, error) { } // Remove removes the named file or (empty) directory. -func (*OsFs) Remove(name string, isDir bool) error { +func (*OsFs) Remove(name string, _ bool) error { return os.Remove(name) } @@ -181,7 +181,7 @@ func (*OsFs) Chmod(name string, mode os.FileMode) error { } // Chtimes changes the access and modification times of the named file -func (*OsFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error { +func (*OsFs) Chtimes(name string, atime, mtime time.Time, _ bool) error { return os.Chtimes(name, atime, mtime) } diff --git a/internal/vfs/s3fs.go b/internal/vfs/s3fs.go index 68dc4764..472f25e7 100644 --- a/internal/vfs/s3fs.go +++ b/internal/vfs/s3fs.go @@ -337,27 +337,27 @@ func (fs *S3Fs) Mkdir(name string) error { } // Symlink creates source as a symbolic link to target. -func (*S3Fs) Symlink(source, target string) error { +func (*S3Fs) Symlink(_, _ string) error { return ErrVfsUnsupported } // Readlink returns the destination of the named symbolic link -func (*S3Fs) Readlink(name string) (string, error) { +func (*S3Fs) Readlink(_ string) (string, error) { return "", ErrVfsUnsupported } // Chown changes the numeric uid and gid of the named file. -func (*S3Fs) Chown(name string, uid int, gid int) error { +func (*S3Fs) Chown(_ string, _ int, _ int) error { return ErrVfsUnsupported } // Chmod changes the mode of the named file to mode. -func (*S3Fs) Chmod(name string, mode os.FileMode) error { +func (*S3Fs) Chmod(_ string, _ os.FileMode) error { return ErrVfsUnsupported } // Chtimes changes the access and modification times of the named file. -func (fs *S3Fs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error { +func (fs *S3Fs) Chtimes(name string, _, mtime time.Time, isUploading bool) error { if !plugin.Handler.HasMetadater() { return ErrVfsUnsupported } @@ -377,7 +377,7 @@ func (fs *S3Fs) Chtimes(name string, atime, mtime time.Time, isUploading bool) e // Truncate changes the size of the named file. // Truncate by path is not supported, while truncating an opened // file is handled inside base transfer -func (*S3Fs) Truncate(name string, size int64) error { +func (*S3Fs) Truncate(_ string, _ int64) error { return ErrVfsUnsupported } @@ -595,7 +595,7 @@ func (fs *S3Fs) GetDirSize(dirname string) (int, int64, error) { // GetAtomicUploadPath returns the path to use for an atomic upload. // S3 uploads are already atomic, we never call this method for S3 -func (*S3Fs) GetAtomicUploadPath(name string) string { +func (*S3Fs) GetAtomicUploadPath(_ string) string { return "" } @@ -1009,7 +1009,7 @@ func (*S3Fs) Close() error { } // GetAvailableDiskSize returns the available size for the specified path -func (*S3Fs) GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error) { +func (*S3Fs) GetAvailableDiskSize(_ string) (*sftp.StatVFS, error) { return nil, ErrStorageSizeUnavailable } diff --git a/internal/vfs/sftpfs.go b/internal/vfs/sftpfs.go index f4af6afa..88833fd0 100644 --- a/internal/vfs/sftpfs.go +++ b/internal/vfs/sftpfs.go @@ -510,7 +510,7 @@ func (fs *SFTPFs) Chmod(name string, mode os.FileMode) error { } // Chtimes changes the access and modification times of the named file. -func (fs *SFTPFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error { +func (fs *SFTPFs) Chtimes(name string, atime, mtime time.Time, _ bool) error { client, err := fs.conn.getClient() if err != nil { return err diff --git a/internal/vfs/sys_unix.go b/internal/vfs/sys_unix.go index 2c091ee3..ddcf5885 100644 --- a/internal/vfs/sys_unix.go +++ b/internal/vfs/sys_unix.go @@ -27,6 +27,6 @@ func isCrossDeviceError(err error) bool { return errors.Is(err, unix.EXDEV) } -func isInvalidNameError(err error) bool { +func isInvalidNameError(_ error) bool { return false } diff --git a/internal/webdavd/file.go b/internal/webdavd/file.go index 1635f7ea..5e6fd88f 100644 --- a/internal/webdavd/file.go +++ b/internal/webdavd/file.go @@ -82,7 +82,7 @@ type webDavFileInfo struct { } // ContentType implements webdav.ContentTyper interface -func (fi *webDavFileInfo) ContentType(ctx context.Context) (string, error) { +func (fi *webDavFileInfo) ContentType(_ context.Context) (string, error) { extension := path.Ext(fi.virtualPath) if ctype, ok := customMimeTypeMapping[extension]; ok { return ctype, nil @@ -107,7 +107,7 @@ func (fi *webDavFileInfo) ContentType(ctx context.Context) (string, error) { } // Readdir reads directory entries from the handle -func (f *webDavFile) Readdir(count int) ([]os.FileInfo, error) { +func (f *webDavFile) Readdir(_ int) ([]os.FileInfo, error) { if !f.Connection.User.HasPerm(dataprovider.PermListItems, f.GetVirtualPath()) { return nil, f.Connection.GetPermissionDeniedError() } diff --git a/internal/webdavd/handler.go b/internal/webdavd/handler.go index dd126e43..e2074fdc 100644 --- a/internal/webdavd/handler.go +++ b/internal/webdavd/handler.go @@ -85,7 +85,7 @@ func (c *Connection) GetCommand() string { } // Mkdir creates a directory using the connection filesystem -func (c *Connection) Mkdir(ctx context.Context, name string, perm os.FileMode) error { +func (c *Connection) Mkdir(_ context.Context, name string, _ os.FileMode) error { c.UpdateLastActivity() name = util.CleanPath(name) @@ -93,7 +93,7 @@ func (c *Connection) Mkdir(ctx context.Context, name string, perm os.FileMode) e } // Rename renames a file or a directory -func (c *Connection) Rename(ctx context.Context, oldName, newName string) error { +func (c *Connection) Rename(_ context.Context, oldName, newName string) error { c.UpdateLastActivity() oldName = util.CleanPath(oldName) @@ -116,7 +116,7 @@ func (c *Connection) Rename(ctx context.Context, oldName, newName string) error // Stat returns a FileInfo describing the named file/directory, or an error, // if any happens -func (c *Connection) Stat(ctx context.Context, name string) (os.FileInfo, error) { +func (c *Connection) Stat(_ context.Context, name string) (os.FileInfo, error) { c.UpdateLastActivity() name = util.CleanPath(name) @@ -133,7 +133,7 @@ func (c *Connection) Stat(ctx context.Context, name string) (os.FileInfo, error) // RemoveAll removes path and any children it contains. // If the path does not exist, RemoveAll returns nil (no error). -func (c *Connection) RemoveAll(ctx context.Context, name string) error { +func (c *Connection) RemoveAll(_ context.Context, name string) error { c.UpdateLastActivity() name = util.CleanPath(name) @@ -142,7 +142,7 @@ func (c *Connection) RemoveAll(ctx context.Context, name string) error { // OpenFile opens the named file with specified flag. // This method is used for uploads and downloads but also for Stat and Readdir -func (c *Connection) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { +func (c *Connection) OpenFile(_ context.Context, name string, flag int, _ os.FileMode) (webdav.File, error) { c.UpdateLastActivity() name = util.CleanPath(name) diff --git a/internal/webdavd/internal_test.go b/internal/webdavd/internal_test.go index 8304b1eb..ffe936ce 100644 --- a/internal/webdavd/internal_test.go +++ b/internal/webdavd/internal_test.go @@ -303,7 +303,7 @@ func (fs *MockOsFs) IsAtomicUploadSupported() bool { } // Remove removes the named file or (empty) directory. -func (fs *MockOsFs) Remove(name string, isDir bool) error { +func (fs *MockOsFs) Remove(name string, _ bool) error { if fs.err != nil { return fs.err } @@ -317,7 +317,7 @@ func (fs *MockOsFs) Rename(source, target string) (int, int64, error) { } // GetMimeType returns the content type -func (fs *MockOsFs) GetMimeType(name string) (string, error) { +func (fs *MockOsFs) GetMimeType(_ string) (string, error) { if fs.err != nil { return "", fs.err } diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 5fb68a2f..198cd57d 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -4874,6 +4874,7 @@ components: - 10 - 11 - 12 + - 13 description: | Supported event action types: * `1` - HTTP @@ -4888,6 +4889,7 @@ components: * `10` - Metadata check * `11` - Password expiration check * `12` - User expiration check + * `13` - Identity Provider account check FilesystemActionTypes: type: integer enum: @@ -4910,6 +4912,7 @@ components: - 4 - 5 - 6 + - 7 description: | Supported event trigger types: * `1` - Filesystem event @@ -4918,6 +4921,7 @@ components: * `4` - IP blocked * `5` - Certificate renewal * `6` - On demand, like schedule but executed on demand + * `7` - Identity provider login LoginMethods: type: string enum: @@ -6870,6 +6874,24 @@ components: threshold: type: integer description: 'An email notification will be generated for users whose password expires in a number of days less than or equal to this threshold' + EventActionIDPAccountCheck: + type: object + properties: + mode: + type: integer + enum: + - 0 + - 1 + description: | + Account check mode: + * `0` Create or update the account + * `1` Create the account if it doesn't exist + template_user: + type: string + description: 'SFTPGo user template in JSON format' + template_admin: + type: string + description: 'SFTPGo admin template in JSON format' BaseEventActionOptions: type: object properties: @@ -6885,6 +6907,8 @@ components: $ref: '#/components/schemas/EventActionFilesystemConfig' pwd_expiration_config: $ref: '#/components/schemas/EventActionPasswordExpiration' + idp_config: + $ref: '#/components/schemas/EventActionIDPAccountCheck' BaseEventAction: type: object properties: @@ -7026,6 +7050,8 @@ components: - pre-upload - pre-download - pre-delete + - first-upload + - first-download provider_events: type: array items: @@ -7038,6 +7064,17 @@ components: type: array items: $ref: '#/components/schemas/Schedule' + idp_login_event: + type: integer + enum: + - 0 + - 1 + - 2 + description: | + IDP login events: + - `0` any login event + - `1` user login event + - `2` admin login event options: $ref: '#/components/schemas/ConditionOptions' BaseEventRule: diff --git a/pkgs/build.sh b/pkgs/build.sh index b0dc031b..9c342d11 100755 --- a/pkgs/build.sh +++ b/pkgs/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -NFPM_VERSION=2.26.0 +NFPM_VERSION=2.27.1 NFPM_ARCH=${NFPM_ARCH:-amd64} if [ -z ${SFTPGO_VERSION} ] then diff --git a/templates/webadmin/eventaction.html b/templates/webadmin/eventaction.html index 6bc4ddc6..a5d94c32 100644 --- a/templates/webadmin/eventaction.html +++ b/templates/webadmin/eventaction.html @@ -88,6 +88,38 @@ along with this program. If not, see . +
+ +
+ +
+
+ +
+ +
+ + + Template for SFTPGo users in JSON format. Placeholders are supported + +
+
+ +
+ +
+ + + Template for SFTPGo admins in JSON format. Placeholders are supported + +
+
+
@@ -781,6 +813,9 @@ along with this program. If not, see .

{{`{{RetentionReports}}`}} => Data retention reports as zip compressed CSV files. Supported as email attachment, file path for multipart HTTP request and as single parameter for HTTP requests body.

+

+ {{`{{IDPField}}`}} => Identity Provider custom fields containing a string. +

+
+ +
+ +
+
+
Schedules @@ -196,7 +207,7 @@ along with this program. If not, see .
-
+
Name filters
@@ -438,7 +449,7 @@ along with this program. If not, see . Actions
-
One or more actions to execute. The "Execute sync" options is supported for upload events and required for pre-* events
+
One or more actions to execute. The "Execute sync" options is supported for upload events and required for pre-* events and Identity provider login events if the action checks the account
{{range $idx, $val := .Rule.Actions}} @@ -727,6 +738,9 @@ along with this program. If not, see . case '6': $('.trigger-on-demand').show(); break; + case '7': + $('.trigger-idp').show(); + break; default: console.log(`unsupported event trigger type: ${val}`); } diff --git a/tests/eventsearcher/go.mod b/tests/eventsearcher/go.mod index 6e34bdb9..b41cfb3b 100644 --- a/tests/eventsearcher/go.mod +++ b/tests/eventsearcher/go.mod @@ -10,16 +10,16 @@ require ( require ( github.com/fatih/color v1.15.0 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/hashicorp/go-hclog v1.4.0 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/oklog/run v1.1.0 // indirect golang.org/x/net v0.8.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect - google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect - google.golang.org/grpc v1.53.0 // indirect - google.golang.org/protobuf v1.29.1 // indirect + google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect ) diff --git a/tests/eventsearcher/go.sum b/tests/eventsearcher/go.sum index 39272178..22932a08 100644 --- a/tests/eventsearcher/go.sum +++ b/tests/eventsearcher/go.sum @@ -9,8 +9,8 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I= -github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.4.10-0.20230306173702-d78f3fc2891d h1:I5vZgh+FLXZcmwesw2ZbfW4WKiPKlZxNoxJdUNwN/wE= github.com/hashicorp/go-plugin v1.4.10-0.20230306173702-d78f3fc2891d/go.mod h1:6/1TEzT0eQznvI/gV2CM29DLSkAK/e58mUWKVsPaph0= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= @@ -23,8 +23,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= @@ -49,14 +49,14 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 h1:khxVcsk/FhnzxMKOyD+TDGwjbEOpcPuIpmafPGFmhMA= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM= -google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/tests/ipfilter/go.mod b/tests/ipfilter/go.mod index 1332a76a..9d598b18 100644 --- a/tests/ipfilter/go.mod +++ b/tests/ipfilter/go.mod @@ -10,16 +10,16 @@ require ( require ( github.com/fatih/color v1.15.0 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/hashicorp/go-hclog v1.4.0 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/oklog/run v1.1.0 // indirect golang.org/x/net v0.8.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect - google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect - google.golang.org/grpc v1.53.0 // indirect - google.golang.org/protobuf v1.29.1 // indirect + google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect ) diff --git a/tests/ipfilter/go.sum b/tests/ipfilter/go.sum index 39272178..22932a08 100644 --- a/tests/ipfilter/go.sum +++ b/tests/ipfilter/go.sum @@ -9,8 +9,8 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I= -github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.4.10-0.20230306173702-d78f3fc2891d h1:I5vZgh+FLXZcmwesw2ZbfW4WKiPKlZxNoxJdUNwN/wE= github.com/hashicorp/go-plugin v1.4.10-0.20230306173702-d78f3fc2891d/go.mod h1:6/1TEzT0eQznvI/gV2CM29DLSkAK/e58mUWKVsPaph0= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= @@ -23,8 +23,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= @@ -49,14 +49,14 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 h1:khxVcsk/FhnzxMKOyD+TDGwjbEOpcPuIpmafPGFmhMA= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM= -google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=