EventManager: add IDP login trigger and check account action

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2023-03-22 19:02:54 +01:00
parent 40344ec0ff
commit e29f6857db
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
58 changed files with 1660 additions and 385 deletions

View file

@ -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<fieldname>}}`. 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 rules actions are taken. For example you can react to uploads only if they are performed by a particular user or using a specified protocol.

View file

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

64
go.mod
View file

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

126
go.sum
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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, &params) {
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, &params) {
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, &params) {
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 &params
}
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

View file

@ -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, &params)
assert.False(t, res)
params.Event = operationDownload
res = eventManager.checkFsEventMatch(conditions, params)
res = eventManager.checkFsEventMatch(conditions, &params)
assert.True(t, res)
params.Role = role
res = eventManager.checkFsEventMatch(conditions, params)
res = eventManager.checkFsEventMatch(conditions, &params)
assert.False(t, res)
params.Role = ""
params.Name = "name"
res = eventManager.checkFsEventMatch(conditions, params)
res = eventManager.checkFsEventMatch(conditions, &params)
assert.False(t, res)
params.Name = "user5"
res = eventManager.checkFsEventMatch(conditions, params)
res = eventManager.checkFsEventMatch(conditions, &params)
assert.True(t, res)
params.VirtualPath = "/sub/f.jpg"
params.ObjectName = path.Base(params.VirtualPath)
res = eventManager.checkFsEventMatch(conditions, params)
res = eventManager.checkFsEventMatch(conditions, &params)
assert.False(t, res)
params.VirtualPath = "/sub/f.txt"
params.ObjectName = path.Base(params.VirtualPath)
res = eventManager.checkFsEventMatch(conditions, params)
res = eventManager.checkFsEventMatch(conditions, &params)
assert.True(t, res)
params.Protocol = ProtocolHTTP
res = eventManager.checkFsEventMatch(conditions, params)
res = eventManager.checkFsEventMatch(conditions, &params)
assert.False(t, res)
params.Protocol = ProtocolSFTP
params.FileSize = 5
res = eventManager.checkFsEventMatch(conditions, params)
res = eventManager.checkFsEventMatch(conditions, &params)
assert.False(t, res)
params.FileSize = 50
res = eventManager.checkFsEventMatch(conditions, params)
res = eventManager.checkFsEventMatch(conditions, &params)
assert.False(t, res)
params.FileSize = 25
res = eventManager.checkFsEventMatch(conditions, params)
res = eventManager.checkFsEventMatch(conditions, &params)
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, &params)
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, &params)
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, &params)
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, &params)
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)
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 != "" {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -88,6 +88,38 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</div>
</div>
<div class="form-group row action-type action-idp">
<label for="idIDPMode" class="col-sm-2 col-form-label">Mode</label>
<div class="col-sm-10">
<select class="form-control selectpicker" id="idIDPMode" name="idp_mode">
<option value="0" {{ if eq .Action.Options.IDPConfig.Mode 0 }}selected{{end}}>Create or update</option>
<option value="1" {{ if eq .Action.Options.IDPConfig.Mode 1 }}selected{{end}}>Create if it doesn't exist</option>
</select>
</div>
</div>
<div class="form-group row action-type action-idp">
<label for="idIDPUser" class="col-sm-2 col-form-label">User template</label>
<div class="col-sm-10">
<textarea class="form-control" id="idIDPUser" name="idp_user" rows="4" placeholder=""
aria-describedby="idpUserHelpBlock">{{.Action.Options.IDPConfig.TemplateUser}}</textarea>
<small id="idpUserHelpBlock" class="form-text text-muted">
Template for SFTPGo users in JSON format. Placeholders are supported
</small>
</div>
</div>
<div class="form-group row action-type action-idp">
<label for="idIDAdmin" class="col-sm-2 col-form-label">Admin template</label>
<div class="col-sm-10">
<textarea class="form-control" id="idIDPAdmin" name="idp_admin" rows="4" placeholder=""
aria-describedby="idpAdminHelpBlock">{{.Action.Options.IDPConfig.TemplateAdmin}}</textarea>
<small id="idpAdminHelpBlock" class="form-text text-muted">
Template for SFTPGo admins in JSON format. Placeholders are supported
</small>
</div>
</div>
<div class="form-group row action-type action-http">
<label for="idHTTPEndpoint" class="col-sm-2 col-form-label">Endpoint</label>
<div class="col-sm-10">
@ -781,6 +813,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<p>
<span class="shortcut"><b>{{`{{RetentionReports}}`}}</b></span> => 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.
</p>
<p>
<span class="shortcut"><b>{{`{{IDPField<fieldname>}}`}}</b></span> => Identity Provider custom fields containing a string.
</p>
</div>
<div class="modal-footer">
<button class="btn btn-primary" type="button" data-dismiss="modal">OK</button>
@ -1028,6 +1063,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
case '11':
$('.action-pwd-expiration').show();
break;
case '13':
$('.action-idp').show();
break;
}
}

View file

@ -98,6 +98,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</div>
</div>
<div class="form-group row trigger trigger-idp">
<label for="idIDPEvent" class="col-sm-2 col-form-label">IDP Login event</label>
<div class="col-sm-10">
<select class="form-control selectpicker" id="idIDPEvent" name="idp_login_event">
<option value="0" {{if eq .Rule.Conditions.IDPLoginEvent 0}}selected{{end}}>Any</option>
<option value="1" {{if eq .Rule.Conditions.IDPLoginEvent 1}}selected{{end}}>User login</option>
<option value="2" {{if eq .Rule.Conditions.IDPLoginEvent 2}}selected{{end}}>Admin login</option>
</select>
</div>
</div>
<div class="card bg-light mb-3 trigger trigger-schedule">
<div class="card-header">
<b>Schedules</b>
@ -196,7 +207,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</div>
</div>
<div class="card bg-light mb-3 trigger trigger-fs trigger-provider trigger-schedule trigger-on-demand">
<div class="card bg-light mb-3 trigger trigger-fs trigger-provider trigger-schedule trigger-on-demand trigger-idp">
<div class="card-header">
<b>Name filters</b>
</div>
@ -438,7 +449,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<b>Actions</b>
</div>
<div class="card-body">
<h6 class="card-title mb-4">One or more actions to execute. The "Execute sync" options is supported for upload events and required for pre-* events</h6>
<h6 class="card-title mb-4">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</h6>
<div class="form-group row">
<div class="col-md-12 form_field_action_outer">
{{range $idx, $val := .Rule.Actions}}
@ -727,6 +738,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
case '6':
$('.trigger-on-demand').show();
break;
case '7':
$('.trigger-idp').show();
break;
default:
console.log(`unsupported event trigger type: ${val}`);
}

View file

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

View file

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

View file

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

View file

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