From edf1775d4963d8f62a8c4ab2b2e56d40d0b2b804 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Thu, 25 May 2023 15:38:30 +0200 Subject: [PATCH] AuthN: Embed an OAuth2 server for external service authentication (#68086) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Moving POC files from #64283 to a new branch Co-authored-by: Mihály Gyöngyösi * Adding missing permission definition Co-authored-by: Mihály Gyöngyösi * Force the service instantiation while client isn't merged Co-authored-by: Mihály Gyöngyösi * Merge conf with main Co-authored-by: Mihály Gyöngyösi * Leave go-sqlite3 version unchanged Co-authored-by: Mihály Gyöngyösi * tidy Co-authored-by: Mihály Gyöngyösi * User SearchUserPermissions instead of SearchUsersPermissions * Replace DummyKeyService with signingkeys.Service * Use user:id: as subject * Fix introspection endpoint issue * Add X-Grafana-Org-Id to get_resources.bash script * Regenerate toggles_gen.go * Fix basic.go * Add GetExternalService tests * Add GetPublicKeyScopes tests * Add GetScopesOnUser tests * Add GetScopes tests * Add ParsePublicKeyPem tests * Add database test for GetByName * re-add comments * client tests added * Add GetExternalServicePublicKey tests * Add other test case to GetExternalServicePublicKey * client_credentials grant test * Add test to jwtbearer grant * Test Comments * Add handleKeyOptions tests * Add RSA key generation test * Add ECDSA by default to EmbeddedSigningKeysService * Clean up org id scope and audiences * Add audiences to the DB * Fix check on Audience * Fix double import * Add AC Store mock and align oauthserver tests * Fix test after rebase * Adding missing store function to mock * Fix double import * Add CODEOWNER * Fix some linting errors * errors don't need type assertion * Typo codeowners * use mockery for oauthserver store * Add feature toggle check * Fix db tests to handle the feature flag * Adding call to DeleteExternalServiceRole * Fix flaky test * Re-organize routes comments and plan futur work * Add client_id check to Extended JWT client * Clean up * Fix * Remove background service registry instantiation of the OAuth server * Comment cleanup * Remove unused client function * Update go.mod to use the latest ory/fosite commit * Remove oauth2_server related configs from defaults.ini * Add audiences to DTO * Fix flaky test * Remove registration endpoint and demo scripts. Document code * Rename packages * Remove the OAuthService vs OAuthServer confusion * fix incorrect import ext_jwt_test * Comments and order * Comment basic auth * Remove unecessary todo * Clean api * Moving ParsePublicKeyPem to utils * re ordering functions in service.go * Fix comment * comment on the redirect uri * Add RBAC actions, not only scopes * Fix tests * re-import featuremgmt in migrations * Fix wire * Fix scopes in test * Fix flaky test * Remove todo, the intersection should always return the minimal set * Remove unecessary check from intersection code * Allow env overrides on settings * remove the term app name * Remove app keyword for client instead and use Name instead of ExternalServiceName * LogID remove ExternalService ref * Use Name instead of ExternalServiceName * Imports order * Inline * Using ExternalService and ExternalServiceDTO * Remove xorm tags * comment * Rename client files * client -> external service * comments * Move test to correct package * slimmer test * cachedUser -> cachedExternalService * Fix aggregate store test * PluginAuthSession -> AuthSession * Revert the nil cehcks * Remove unecessary extra * Removing custom session * fix typo in test * Use constants for tests * Simplify HandleToken tests * Refactor the HandleTokenRequest test * test message * Review test * Prevent flacky test on client as well * go imports * Revert changes from 526e48ad4550fed7e2b753b9d0a0cc6097155f58 * AuthN: Change the External Service registration form (#68649) * AuthN: change the External Service registration form * Gen default permissions * Change demo script registration form * Remove unecessary comment * Nit. * Reduce cyclomatic complexity * Remove demo_scripts * Handle case with no service account * Comments * Group key gen * Nit. * Check the SaveExternalService test * Rename cachedUser to cachedClient in test * One more test case to database test * Comments * Remove last org scope Co-authored-by: Mihály Gyöngyösi * Update pkg/services/oauthserver/utils/utils_test.go * Update pkg/services/sqlstore/migrations/oauthserver/migrations.go Remove comment * Update pkg/setting/setting.go Co-authored-by: Gabriel MABILLE --------- Co-authored-by: Mihály Gyöngyösi --- .github/CODEOWNERS | 1 + go.mod | 27 + go.sum | 594 +++++++++++++- pkg/server/wire.go | 4 + pkg/services/accesscontrol/accesscontrol.go | 105 +++ .../accesscontrol/accesscontrol_test.go | 208 +++++ pkg/services/accesscontrol/acimpl/service.go | 1 + .../accesscontrol/actest/store_mock.go | 151 ++++ pkg/services/accesscontrol/models.go | 9 +- pkg/services/authn/authnimpl/service.go | 5 +- pkg/services/authn/clients/basic.go | 8 + pkg/services/authn/clients/ext_jwt.go | 12 +- pkg/services/authn/clients/ext_jwt_test.go | 103 ++- pkg/services/oauthserver/api/api.go | 37 + pkg/services/oauthserver/errors.go | 27 + pkg/services/oauthserver/external_service.go | 161 ++++ .../oauthserver/external_service_test.go | 210 +++++ pkg/services/oauthserver/models.go | 91 +++ .../oauthserver/oasimpl/aggregate_store.go | 162 ++++ .../oasimpl/aggregate_store_test.go | 119 +++ .../oauthserver/oasimpl/introspection.go | 21 + pkg/services/oauthserver/oasimpl/service.go | 501 ++++++++++++ .../oauthserver/oasimpl/service_test.go | 545 +++++++++++++ pkg/services/oauthserver/oasimpl/session.go | 16 + pkg/services/oauthserver/oasimpl/token.go | 351 ++++++++ .../oauthserver/oasimpl/token_test.go | 747 ++++++++++++++++++ pkg/services/oauthserver/oastest/fakes.go | 29 + .../oauthserver/oastest/store_mock.go | 138 ++++ pkg/services/oauthserver/store/database.go | 219 +++++ .../oauthserver/store/database_test.go | 376 +++++++++ pkg/services/oauthserver/utils/utils.go | 35 + pkg/services/oauthserver/utils/utils_test.go | 82 ++ pkg/services/serviceaccounts/tests/mocks.go | 52 ++ .../signingkeys/signingkeysimpl/service.go | 8 +- .../signingkeysimpl/service_test.go | 72 +- .../sqlstore/migrations/migrations.go | 7 + .../migrations/oauthserver/migrations.go | 52 ++ pkg/setting/setting.go | 17 + 38 files changed, 5190 insertions(+), 113 deletions(-) create mode 100644 pkg/services/accesscontrol/actest/store_mock.go create mode 100644 pkg/services/oauthserver/api/api.go create mode 100644 pkg/services/oauthserver/errors.go create mode 100644 pkg/services/oauthserver/external_service.go create mode 100644 pkg/services/oauthserver/external_service_test.go create mode 100644 pkg/services/oauthserver/models.go create mode 100644 pkg/services/oauthserver/oasimpl/aggregate_store.go create mode 100644 pkg/services/oauthserver/oasimpl/aggregate_store_test.go create mode 100644 pkg/services/oauthserver/oasimpl/introspection.go create mode 100644 pkg/services/oauthserver/oasimpl/service.go create mode 100644 pkg/services/oauthserver/oasimpl/service_test.go create mode 100644 pkg/services/oauthserver/oasimpl/session.go create mode 100644 pkg/services/oauthserver/oasimpl/token.go create mode 100644 pkg/services/oauthserver/oasimpl/token_test.go create mode 100644 pkg/services/oauthserver/oastest/fakes.go create mode 100644 pkg/services/oauthserver/oastest/store_mock.go create mode 100644 pkg/services/oauthserver/store/database.go create mode 100644 pkg/services/oauthserver/store/database_test.go create mode 100644 pkg/services/oauthserver/utils/utils.go create mode 100644 pkg/services/oauthserver/utils/utils_test.go create mode 100644 pkg/services/serviceaccounts/tests/mocks.go create mode 100644 pkg/services/sqlstore/migrations/oauthserver/migrations.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2cd145c5922..23000a779b8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -545,6 +545,7 @@ lerna.json @grafana/frontend-ops /pkg/services/ldap/ @grafana/grafana-authnz-team /pkg/services/login/ @grafana/grafana-authnz-team /pkg/services/loginattempt/ @grafana/grafana-authnz-team +/pkg/services/oauthserver/ @grafana/grafana-authnz-team /pkg/services/oauthtoken/ @grafana/grafana-authnz-team /pkg/services/serviceaccounts/ @grafana/grafana-authnz-team diff --git a/go.mod b/go.mod index e3e8bf5d6fb..e3661454a40 100644 --- a/go.mod +++ b/go.mod @@ -266,10 +266,12 @@ require ( github.com/grafana/go-mssqldb v0.9.1 github.com/grafana/kindsys v0.0.0-20230508162304-452481b63482 github.com/grafana/thema v0.0.0-20230523123227-db9596a7096e + github.com/ory/fosite v0.44.1-0.20230317114349-45a6785cc54f github.com/redis/go-redis/v9 v9.0.2 github.com/weaveworks/common v0.0.0-20230208133027-16871410fca4 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f go.opentelemetry.io/contrib/samplers/jaegerremote v0.9.0 + gopkg.in/square/go-jose.v2 v2.5.2-0.20210529014059-a5c7eec3c614 k8s.io/utils v0.0.0-20230308161112-d77c459e9343 ) @@ -290,13 +292,19 @@ require ( github.com/cockroachdb/redact v1.1.3 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/cristalhq/jwt/v4 v4.0.2 // indirect + github.com/dave/jennifer v1.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/drone-runners/drone-runner-docker v1.8.2 // indirect github.com/drone/drone-go v1.7.1 // indirect github.com/drone/envsubst v1.0.3 // indirect github.com/drone/runner-go v1.12.0 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/ecordell/optgen v0.0.6 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/getsentry/sentry-go v0.12.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/google/go-querystring v1.1.0 // indirect @@ -304,20 +312,32 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect github.com/grafana/sqlds/v2 v2.3.10 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/memberlist v0.5.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/invopop/yaml v0.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect + github.com/magiconair/properties v1.8.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-ieproxy v0.0.3 // indirect + github.com/mattn/goveralls v0.0.6 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect + github.com/ory/go-acc v0.2.6 // indirect + github.com/ory/go-convenience v0.1.0 // indirect + github.com/ory/viper v1.7.5 // indirect + github.com/ory/x v0.0.214 // indirect + github.com/pborman/uuid v1.2.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect github.com/perimeterx/marshmallow v1.1.4 // indirect github.com/rivo/uniseg v0.3.4 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect @@ -325,7 +345,12 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect + github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/cobra v1.4.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.4.1 // indirect github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect github.com/unknwon/com v1.0.1 // indirect github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 // indirect @@ -423,3 +448,5 @@ replace google.golang.org/genproto => google.golang.org/genproto v0.0.0-20220421 // Use 1.10.6 of pq to avoid a change in 1.10.7 that has certificate validation issues. https://github.com/grafana/grafana/issues/65816 replace github.com/lib/pq => github.com/lib/pq v1.10.6 + +exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible diff --git a/go.sum b/go.sum index 719b15914de..53b3ee14d65 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.28.1-20221127060915-a1ecdc58eccd.4/go.mod h1:92ejKVTiuvnKoAtRlpJpIxKfloI935DDqhs0NCRx+KM= buf.build/gen/go/parca-dev/parca/bufbuild/connect-go v1.4.1-20221222094228-8b1d3d0f62e6.1 h1:wQ75SnlaD0X30PnrmA+07A/5fnQWrAHy1mzv+CPB5Oo= buf.build/gen/go/parca-dev/parca/bufbuild/connect-go v1.4.1-20221222094228-8b1d3d0f62e6.1/go.mod h1:VYzBTKhjl92cl3sv+xznQcJHCezU7qnI0FhBAUb4n8c= @@ -6,6 +7,7 @@ buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.28.1-20221222094228-8b1d3 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= @@ -182,6 +184,7 @@ github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3 github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v4.0.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/FZambia/eagle v0.0.2 h1:35qHDuXSQevZ4w9A51k4wU7OE/tPHTEWXoywA93hvkY= github.com/FZambia/eagle v0.0.2/go.mod h1:xq6u/JeNZ5/8mrAQ76MMhzNTodASh9FavQlCgg4j48w= @@ -194,8 +197,10 @@ github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKz github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= @@ -232,6 +237,7 @@ github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE= @@ -286,6 +292,7 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.23.19/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= @@ -338,6 +345,7 @@ github.com/aws/aws-sdk-go-v2/service/sso v1.11.3 h1:frW4ikGcxfAEDfmQqWgMLp+F1n4n github.com/aws/aws-sdk-go-v2/service/sso v1.11.3/go.mod h1:7UQ/e69kU7LDPtY40OyoHYgRmgfGM4mgsLYtcObdveU= github.com/aws/aws-sdk-go-v2/service/sts v1.16.3 h1:cJGRyzCSVwZC7zZZ1xbx9m32UnrKydRYhOvcD1NYP9Q= github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8= +github.com/aws/aws-xray-sdk-go v0.9.4/go.mod h1:XtMKdBQfpVut+tJEwI7+dJFRxxRdxHDyVNp2tHXRq04= github.com/aws/smithy-go v1.5.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE= @@ -381,6 +389,7 @@ github.com/blugelabs/ice v1.0.0 h1:um7wf9e6jbkTVCrOyQq3tKK43fBMOvLUYxbj3Qtc4eo= github.com/blugelabs/ice v1.0.0/go.mod h1:gNfFPk5zM+yxJROhthxhVQYjpBO9amuxWXJQ2Lo+IbQ= github.com/bmatcuk/doublestar v1.1.1 h1:YroD6BJCZBYx06yYFEWvUuKVWQn3vLLQAVmDmvTSaiQ= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/bmatcuk/doublestar/v2 v2.0.3/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= @@ -399,7 +408,9 @@ github.com/caio/go-tdigest v3.1.0+incompatible/go.mod h1:sHQM/ubZStBUmF1WbB8FAm8 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.31.6/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= +github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= @@ -426,6 +437,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= @@ -443,6 +455,8 @@ github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMe github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/cockroachdb/cockroach-go v0.0.0-20200312223839-f565e4789405/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= @@ -453,8 +467,13 @@ github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5w github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -481,7 +500,10 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= +github.com/cristalhq/jwt/v4 v4.0.2 h1:g/AD3h0VicDamtlM70GWGElp8kssQEv+5wYd7L9WOhU= +github.com/cristalhq/jwt/v4 v4.0.2/go.mod h1:HnYraSNKDRag1DZP92rYHyrjyQHnVEHPNqesmzs+miQ= github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b/go.mod h1:v9FBN7gdVTpiD/+LZ7Po0UKvROyT87uLVxTHVky/dlQ= +github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= @@ -493,9 +515,18 @@ github.com/cznic/ql v1.2.0/go.mod h1:FbpzhyZrqr0PVlK6ury+PoW3T0ODUV22OeWIxcaOrSE github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ= github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc/go.mod h1:Y1SNZ4dRUOKXshKUbwUapqNncRrho4mkjQebgEHZLj8= +github.com/dave/astrid v0.0.0-20170323122508-8c2895878b14/go.mod h1:Sth2QfxfATb/nW4EsrSi2KyJmbcniZ8TgTaji17D6ms= +github.com/dave/brenda v1.1.0/go.mod h1:4wCUr6gSlu5/1Tk7akE5X7UorwiQ8Rij0SKH3/BGMOM= +github.com/dave/courtney v0.3.0/go.mod h1:BAv3hA06AYfNUjfjQr+5gc6vxeBVOupLqrColj+QSD8= github.com/dave/dst v0.27.2 h1:4Y5VFTkhGLC1oddtNwuxxe36pnyLxMFXT51FOzH8Ekc= github.com/dave/dst v0.27.2/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= +github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ= +github.com/dave/jennifer v1.4.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/dave/jennifer v1.5.0 h1:HmgPN93bVDpkQyYbqhCHj5QlgvUkvEOzMyEvKLgCRrg= +github.com/dave/jennifer v1.5.0/go.mod h1:4MnyiFIlZS3l5tSDn8VnzE6ffAhYBMB2SZntBsZGUok= +github.com/dave/kerr v0.0.0-20170318121727-bc25dd6abe8e/go.mod h1:qZqlPyPvfsDJt+3wHJ1EvSXDuVjFTK0j2p/ca+gtsb8= +github.com/dave/patsy v0.0.0-20210517141501-957256f50cba/go.mod h1:qfR88CgEGLoiqDaE+xxDCi5QA5v4vUoW0UCX2Nd5Tlc= +github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWEmXBA= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -510,10 +541,14 @@ github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgz github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger/v3 v3.2103.0/go.mod h1:GHMCYxuDWyzbHkh4k3yyg4PM61tJPFfEGSMbE3Vd5QE= +github.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.4-0.20210309073149-3836124cdc5a/go.mod h1:MIonLggsKgZLUSt414ExgwNtlOL5MuEoAJP514mwGe8= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0= @@ -555,17 +590,24 @@ github.com/drone/runner-go v1.12.0 h1:zUjDj9ylsJ4n4Mvy4znddq/Z4EBzcUXzTltpzokKtg github.com/drone/runner-go v1.12.0/go.mod h1:vu4pPPYDoeN6vdYQAY01GGGsAIW4aLganJNaa8Fx8zE= github.com/drone/signal v1.0.0/go.mod h1:S8t92eFT0g4WUgEc/LxG+LCuiskpMNsG0ajAMGnyZpc= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ecordell/optgen v0.0.6 h1:aSknPe6ZUBrjwHGp2+6XfmfCGYGD6W0ZDfCmmsrS7s4= +github.com/ecordell/optgen v0.0.6/go.mod h1:bAPkLVWcBlTX5EkXW0UTPRj3+yjq2I6VLgH8OasuQEM= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= +github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20181003060214-f58a169a71a5/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac h1:XDAn206aIqKPdF5YczuuJXSQPx+WOen0Pxbxp5Fq8Pg= github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= @@ -594,6 +636,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -644,6 +687,7 @@ github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= @@ -757,6 +801,7 @@ github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29g github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98= github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk= +github.com/go-openapi/runtime v0.19.26/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M= github.com/go-openapi/runtime v0.19.28/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M= github.com/go-openapi/runtime v0.25.0 h1:7yQTCdRbWhX8vnIjdzU8S00tBYf7Sg71EBeorlPHvhc= github.com/go-openapi/runtime v0.25.0/go.mod h1:Ux6fikcHXyyob6LNWxtE96hWwjBPYF0DXgVFuMTneOs= @@ -847,29 +892,253 @@ github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/attrs v0.1.0/go.mod h1:fmNpaWyHM0tRm8gCZWKx8yY9fvaNLo2PyzBNSrBZ5Hw= +github.com/gobuffalo/buffalo v0.12.8-0.20181004233540-fac9bb505aa8/go.mod h1:sLyT7/dceRXJUxSsE813JTQtA3Eb1vjxWfo/N//vXIY= +github.com/gobuffalo/buffalo v0.13.0/go.mod h1:Mjn1Ba9wpIbpbrD+lIDMy99pQ0H0LiddMIIDGse7qT4= +github.com/gobuffalo/buffalo-plugins v1.0.2/go.mod h1:pOp/uF7X3IShFHyobahTkTLZaeUXwb0GrUTb9ngJWTs= +github.com/gobuffalo/buffalo-plugins v1.0.4/go.mod h1:pWS1vjtQ6uD17MVFWf7i3zfThrEKWlI5+PYLw/NaDB4= +github.com/gobuffalo/buffalo-plugins v1.4.3/go.mod h1:uCzTY0woez4nDMdQjkcOYKanngeUVRO2HZi7ezmAjWY= +github.com/gobuffalo/buffalo-plugins v1.5.1/go.mod h1:jbmwSZK5+PiAP9cC09VQOrGMZFCa/P0UMlIS3O12r5w= +github.com/gobuffalo/buffalo-plugins v1.6.4/go.mod h1:/+N1aophkA2jZ1ifB2O3Y9yGwu6gKOVMtUmJnbg+OZI= +github.com/gobuffalo/buffalo-plugins v1.6.5/go.mod h1:0HVkbgrVs/MnPZ/FOseDMVanCTm2RNcdM0PuXcL1NNI= +github.com/gobuffalo/buffalo-plugins v1.6.7/go.mod h1:ZGZRkzz2PiKWHs0z7QsPBOTo2EpcGRArMEym6ghKYgk= +github.com/gobuffalo/buffalo-plugins v1.6.9/go.mod h1:yYlYTrPdMCz+6/+UaXg5Jm4gN3xhsvsQ2ygVatZV5vw= +github.com/gobuffalo/buffalo-plugins v1.6.11/go.mod h1:eAA6xJIL8OuynJZ8amXjRmHND6YiusVAaJdHDN1Lu8Q= +github.com/gobuffalo/buffalo-plugins v1.8.2/go.mod h1:9te6/VjEQ7pKp7lXlDIMqzxgGpjlKoAcAANdCgoR960= +github.com/gobuffalo/buffalo-plugins v1.8.3/go.mod h1:IAWq6vjZJVXebIq2qGTLOdlXzmpyTZ5iJG5b59fza5U= +github.com/gobuffalo/buffalo-plugins v1.9.4/go.mod h1:grCV6DGsQlVzQwk6XdgcL3ZPgLm9BVxlBmXPMF8oBHI= +github.com/gobuffalo/buffalo-plugins v1.10.0/go.mod h1:4osg8d9s60txLuGwXnqH+RCjPHj9K466cDFRl3PErHI= +github.com/gobuffalo/buffalo-plugins v1.11.0/go.mod h1:rtIvAYRjYibgmWhnjKmo7OadtnxuMG5ZQLr25ozAzjg= +github.com/gobuffalo/buffalo-plugins v1.15.0/go.mod h1:BqSx01nwgKUQr/MArXzFkSD0QvdJidiky1OKgyfgrK8= +github.com/gobuffalo/buffalo-pop v1.0.5/go.mod h1:Fw/LfFDnSmB/vvQXPvcXEjzP98Tc+AudyNWUBWKCwQ8= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.4/go.mod h1:Abh+Jfw475/NWtYMEt+hnJWRiC8INKWibIMyNt1w2Mc= +github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.6/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.7/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.8/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.9/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.10/go.mod h1:X0CFllQjTV5ogsnUrg+Oks2yTI+PU2dGYBJOEI2D1Uo= +github.com/gobuffalo/envy v1.6.11/go.mod h1:Fiq52W7nrHGDggFPhn2ZCcHw4u/rqXkqo+i7FB6EAcg= +github.com/gobuffalo/envy v1.6.12/go.mod h1:qJNrJhKkZpEW0glh5xP2syQHH5kgdmgsKss2Kk8PTP0= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/envy v1.8.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/events v1.0.3/go.mod h1:Txo8WmqScapa7zimEQIwgiJBvMECMe9gJjsKNPN3uZw= +github.com/gobuffalo/events v1.0.7/go.mod h1:z8txf6H9jWhQ5Scr7YPLWg/cgXBRj8Q4uYI+rsVCCSQ= +github.com/gobuffalo/events v1.0.8/go.mod h1:A5KyqT1sA+3GJiBE4QKZibse9mtOcI9nw8gGrDdqYGs= +github.com/gobuffalo/events v1.1.3/go.mod h1:9yPGWYv11GENtzrIRApwQRMYSbUgCsZ1w6R503fCfrk= +github.com/gobuffalo/events v1.1.4/go.mod h1:09/YRRgZHEOts5Isov+g9X2xajxdvOAcUuAHIX/O//A= +github.com/gobuffalo/events v1.1.5/go.mod h1:3YUSzgHfYctSjEjLCWbkXP6djH2M+MLaVRzb4ymbAK0= +github.com/gobuffalo/events v1.1.7/go.mod h1:6fGqxH2ing5XMb3EYRq9LEkVlyPGs4oO/eLzh+S8CxY= +github.com/gobuffalo/events v1.1.8/go.mod h1:UFy+W6X6VbCWS8k2iT81HYX65dMtiuVycMy04cplt/8= +github.com/gobuffalo/events v1.1.9/go.mod h1:/0nf8lMtP5TkgNbzYxR6Bl4GzBy5s5TebgNTdRfRbPM= +github.com/gobuffalo/events v1.3.1/go.mod h1:9JOkQVoyRtailYVE/JJ2ZQ/6i4gTjM5t2HsZK4C1cSA= +github.com/gobuffalo/events v1.4.1/go.mod h1:SjXgWKpeSuvQDvGhgMz5IXx3Czu+IbL+XPLR41NvVQY= +github.com/gobuffalo/fizz v1.0.12/go.mod h1:C0sltPxpYK8Ftvf64kbsQa2yiCZY4RZviurNxXdAKwc= +github.com/gobuffalo/fizz v1.9.8/go.mod h1:w1FEn1yKNVCc49KnADGyYGRPH7jFON3ak4Bj1yUudHo= +github.com/gobuffalo/fizz v1.10.0/go.mod h1:J2XGPO0AfJ1zKw7+2BA+6FEGAkyEsdCOLvN93WCT2WI= +github.com/gobuffalo/flect v0.0.0-20180907193754-dc14d8acaf9f/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181002182613-4571df4b1daf/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181007231023-ae7ed6bfe683/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181018182602-fd24a256709f/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181019110701-3d6f0b585514/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181024204909-8f6be1a8c6c2/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181104133451-1f6e9779237a/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181114183036-47375f6d8328/go.mod h1:0HvNbHdfh+WOvDSIASqJOSxTOWSxCCUF++k/Y53v9rI= +github.com/gobuffalo/flect v0.0.0-20181210151238-24a2b68e0316/go.mod h1:en58vff74S9b99Eg42Dr+/9yPu437QjlNsO/hBYPuOk= +github.com/gobuffalo/flect v0.0.0-20190104192022-4af577e09bf2/go.mod h1:en58vff74S9b99Eg42Dr+/9yPu437QjlNsO/hBYPuOk= +github.com/gobuffalo/flect v0.0.0-20190117212819-a62e61d96794/go.mod h1:397QT6v05LkZkn07oJXXT6y9FCfwC8Pug0WA2/2mE9k= github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= +github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= +github.com/gobuffalo/flect v0.2.1/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= +github.com/gobuffalo/genny v0.0.0-20180924032338-7af3a40f2252/go.mod h1:tUTQOogrr7tAQnhajMSH6rv1BVev34H2sa1xNHMy94g= +github.com/gobuffalo/genny v0.0.0-20181003150629-3786a0744c5d/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM= +github.com/gobuffalo/genny v0.0.0-20181005145118-318a41a134cc/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM= +github.com/gobuffalo/genny v0.0.0-20181007153042-b8de7d566757/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= +github.com/gobuffalo/genny v0.0.0-20181012161047-33e5f43d83a6/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= +github.com/gobuffalo/genny v0.0.0-20181017160347-90a774534246/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= +github.com/gobuffalo/genny v0.0.0-20181024195656-51392254bf53/go.mod h1:o9GEH5gn5sCKLVB5rHFC4tq40rQ3VRUzmx6WwmaqISE= +github.com/gobuffalo/genny v0.0.0-20181025145300-af3f81d526b8/go.mod h1:uZ1fFYvdcP8mu0B/Ynarf6dsGvp7QFIpk/QACUuFUVI= +github.com/gobuffalo/genny v0.0.0-20181027191429-94d6cfb5c7fc/go.mod h1:x7SkrQQBx204Y+O9EwRXeszLJDTaWN0GnEasxgLrQTA= +github.com/gobuffalo/genny v0.0.0-20181027195209-3887b7171c4f/go.mod h1:JbKx8HSWICu5zyqWOa0dVV1pbbXOHusrSzQUprW6g+w= +github.com/gobuffalo/genny v0.0.0-20181106193839-7dcb0924caf1/go.mod h1:x61yHxvbDCgQ/7cOAbJCacZQuHgB0KMSzoYcw5debjU= +github.com/gobuffalo/genny v0.0.0-20181107223128-f18346459dbe/go.mod h1:utQD3aKKEsdb03oR+Vi/6ztQb1j7pO10N3OBoowRcSU= +github.com/gobuffalo/genny v0.0.0-20181114215459-0a4decd77f5d/go.mod h1:kN2KZ8VgXF9VIIOj/GM0Eo7YK+un4Q3tTreKOf0q1ng= +github.com/gobuffalo/genny v0.0.0-20181119162812-e8ff4adce8bb/go.mod h1:BA9htSe4bZwBDJLe8CUkoqkypq3hn3+CkoHqVOW718E= +github.com/gobuffalo/genny v0.0.0-20181127225641-2d959acc795b/go.mod h1:l54xLXNkteX/PdZ+HlgPk1qtcrgeOr3XUBBPDbH+7CQ= +github.com/gobuffalo/genny v0.0.0-20181128191930-77e34f71ba2a/go.mod h1:FW/D9p7cEEOqxYA71/hnrkOWm62JZ5ZNxcNIVJEaWBU= +github.com/gobuffalo/genny v0.0.0-20181203165245-fda8bcce96b1/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM= +github.com/gobuffalo/genny v0.0.0-20181203201232-849d2c9534ea/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM= +github.com/gobuffalo/genny v0.0.0-20181206121324-d6fb8a0dbe36/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM= +github.com/gobuffalo/genny v0.0.0-20181207164119-84844398a37d/go.mod h1:y0ysCHGGQf2T3vOhCrGHheYN54Y/REj0ayd0Suf4C/8= +github.com/gobuffalo/genny v0.0.0-20181211165820-e26c8466f14d/go.mod h1:sHnK+ZSU4e2feXP3PA29ouij6PUEiN+RCwECjCTB3yM= +github.com/gobuffalo/genny v0.0.0-20190104222617-a71664fc38e7/go.mod h1:QPsQ1FnhEsiU8f+O0qKWXz2RE4TiDqLVChWkBuh1WaY= +github.com/gobuffalo/genny v0.0.0-20190112155932-f31a84fcacf5/go.mod h1:CIaHCrSIuJ4il6ka3Hub4DR4adDrGoXGEEt2FbBxoIo= github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/genny v0.2.0/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.3.0/go.mod h1:ywJ2CoXrTZj7rbS8HTbzv7uybnLKlsNSBhEQ+yFI3E8= +github.com/gobuffalo/genny v0.6.0/go.mod h1:Vigx9VDiNscYpa/LwrURqGXLSIbzTfapt9+K6gF1kTA= +github.com/gobuffalo/genny/v2 v2.0.5/go.mod h1:kRkJuAw9mdI37AiEYjV4Dl+TgkBDYf8HZVjLkqe5eBg= github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/github_flavored_markdown v1.0.4/go.mod h1:uRowCdK+q8d/RF0Kt3/DSalaIXbb0De/dmTqMQdkQ4I= +github.com/gobuffalo/github_flavored_markdown v1.0.5/go.mod h1:U0643QShPF+OF2tJvYNiYDLDGDuQmJZXsf/bHOJPsMY= +github.com/gobuffalo/github_flavored_markdown v1.0.7/go.mod h1:w93Pd9Lz6LvyQXEG6DktTPHkOtCbr+arAD5mkwMzXLI= +github.com/gobuffalo/github_flavored_markdown v1.1.0/go.mod h1:TSpTKWcRTI0+v7W3x8dkSKMLJSUpuVitlptCkpeY8ic= github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/gogen v0.2.0/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/helpers v0.2.2/go.mod h1:xYbzUdCUpVzLwLnqV8HIjT6hmG0Cs7YIBCJkNM597jw= +github.com/gobuffalo/helpers v0.2.4/go.mod h1:NX7v27yxPDOPTgUFYmJ5ow37EbxdoLraucOGvMNawyk= +github.com/gobuffalo/helpers v0.5.0/go.mod h1:stpgxJ2C7T99NLyAxGUnYMM2zAtBk5NKQR0SIbd05j4= +github.com/gobuffalo/helpers v0.6.0/go.mod h1:pncVrer7x/KRvnL5aJABLAuT/RhKRR9klL6dkUOhyv8= +github.com/gobuffalo/helpers v0.6.1/go.mod h1:wInbDi0vTJKZBviURTLRMFLE4+nF2uRuuL2fnlYo7w4= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/gobuffalo/httptest v1.0.2/go.mod h1:7T1IbSrg60ankme0aDLVnEY0h056g9M1/ZvpVThtB7E= +github.com/gobuffalo/licenser v0.0.0-20180924033006-eae28e638a42/go.mod h1:Ubo90Np8gpsSZqNScZZkVXXAo5DGhTb+WYFIjlnog8w= +github.com/gobuffalo/licenser v0.0.0-20181025145548-437d89de4f75/go.mod h1:x3lEpYxkRG/XtGCUNkio+6RZ/dlOvLzTI9M1auIwFcw= +github.com/gobuffalo/licenser v0.0.0-20181027200154-58051a75da95/go.mod h1:BzhaaxGd1tq1+OLKObzgdCV9kqVhbTulxOpYbvMQWS0= +github.com/gobuffalo/licenser v0.0.0-20181109171355-91a2a7aac9a7/go.mod h1:m+Ygox92pi9bdg+gVaycvqE8RVSjZp7mWw75+K5NPHk= +github.com/gobuffalo/licenser v0.0.0-20181128165715-cc7305f8abed/go.mod h1:oU9F9UCE+AzI/MueCKZamsezGOOHfSirltllOVeRTAE= +github.com/gobuffalo/licenser v0.0.0-20181203160806-fe900bbede07/go.mod h1:ph6VDNvOzt1CdfaWC+9XwcBnlSTBz2j49PBwum6RFaU= +github.com/gobuffalo/licenser v0.0.0-20181211173111-f8a311c51159/go.mod h1:ve/Ue99DRuvnTaLq2zKa6F4KtHiYf7W046tDjuGYPfM= +github.com/gobuffalo/licenser v1.1.0/go.mod h1:ZVWE6uKUE3rGf7sedUHWVjNWrEgxaUQLVFL+pQiWpfY= +github.com/gobuffalo/logger v0.0.0-20181022175615-46cfb361fc27/go.mod h1:8sQkgyhWipz1mIctHF4jTxmJh1Vxhp7mP8IqbljgJZo= +github.com/gobuffalo/logger v0.0.0-20181027144941-73d08d2bb969/go.mod h1:7uGg2duHKpWnN4+YmyKBdLXfhopkAdVM6H3nKbyFbz8= +github.com/gobuffalo/logger v0.0.0-20181027193913-9cf4dd0efe46/go.mod h1:7uGg2duHKpWnN4+YmyKBdLXfhopkAdVM6H3nKbyFbz8= +github.com/gobuffalo/logger v0.0.0-20181109185836-3feeab578c17/go.mod h1:oNErH0xLe+utO+OW8ptXMSA5DkiSEDW1u3zGIt8F9Ew= +github.com/gobuffalo/logger v0.0.0-20181117211126-8e9b89b7c264/go.mod h1:5etB91IE0uBlw9k756fVKZJdS+7M7ejVhmpXXiSFj0I= +github.com/gobuffalo/logger v0.0.0-20181127160119-5b956e21995c/go.mod h1:+HxKANrR9VGw9yN3aOAppJKvhO05ctDi63w4mDnKv2U= github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= +github.com/gobuffalo/makr v1.1.5/go.mod h1:Y+o0btAH1kYAMDJW/TX3+oAXEu0bmSLLoC9mIFxtzOw= +github.com/gobuffalo/mapi v1.0.0/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.1.0/go.mod h1:pqQ1XAqvpy/JYtRwoieNps2yU8MFiMxBUpAm2FBtQ50= +github.com/gobuffalo/mapi v1.2.1/go.mod h1:giGJ2AUESRepOFYAzWpq8Gf/s/QDryxoEHisQtFN3cY= +github.com/gobuffalo/meta v0.0.0-20181018155829-df62557efcd3/go.mod h1:XTTOhwMNryif3x9LkTTBO/Llrveezd71u3quLd0u7CM= +github.com/gobuffalo/meta v0.0.0-20181018192820-8c6cef77dab3/go.mod h1:E94EPzx9NERGCY69UWlcj6Hipf2uK/vnfrF4QD0plVE= +github.com/gobuffalo/meta v0.0.0-20181025145500-3a985a084b0a/go.mod h1:YDAKBud2FP7NZdruCSlmTmDOZbVSa6bpK7LJ/A/nlKg= +github.com/gobuffalo/meta v0.0.0-20181114191255-b130ebedd2f7/go.mod h1:K6cRZ29ozr4Btvsqkjvg5nDFTLOgTqf03KA70Ks0ypE= +github.com/gobuffalo/meta v0.0.0-20181127070345-0d7e59dd540b/go.mod h1:RLO7tMvE0IAKAM8wny1aN12pvEKn7EtkBLkUZR00Qf8= +github.com/gobuffalo/meta v0.0.0-20190120163247-50bbb1fa260d/go.mod h1:KKsH44nIK2gA8p0PJmRT9GvWJUdphkDUA8AJEvFWiqM= +github.com/gobuffalo/meta v0.0.0-20190329152330-e161e8a93e3b/go.mod h1:mCRSy5F47tjK8yaIDcJad4oe9fXxY5gLrx3Xx2spK+0= +github.com/gobuffalo/meta v0.3.0/go.mod h1:cpr6mrUX5H/B4wEP86Gdq568TK4+dKUD8oRPl698RUw= +github.com/gobuffalo/mw-basicauth v1.0.3/go.mod h1:dg7+ilMZOKnQFHDefUzUHufNyTswVUviCBgF244C1+0= +github.com/gobuffalo/mw-contenttype v0.0.0-20180802152300-74f5a47f4d56/go.mod h1:7EvcmzBbeCvFtQm5GqF9ys6QnCxz2UM1x0moiWLq1No= +github.com/gobuffalo/mw-csrf v0.0.0-20180802151833-446ff26e108b/go.mod h1:sbGtb8DmDZuDUQoxjr8hG1ZbLtZboD9xsn6p77ppcHo= +github.com/gobuffalo/mw-forcessl v0.0.0-20180802152810-73921ae7a130/go.mod h1:JvNHRj7bYNAMUr/5XMkZaDcw3jZhUZpsmzhd//FFWmQ= +github.com/gobuffalo/mw-i18n v0.0.0-20180802152014-e3060b7e13d6/go.mod h1:91AQfukc52A6hdfIfkxzyr+kpVYDodgAeT5cjX1UIj4= +github.com/gobuffalo/mw-paramlogger v0.0.0-20181005191442-d6ee392ec72e/go.mod h1:6OJr6VwSzgJMqWMj7TYmRUqzNe2LXu/W1rRW4MAz/ME= +github.com/gobuffalo/mw-tokenauth v0.0.0-20181001105134-8545f626c189/go.mod h1:UqBF00IfKvd39ni5+yI5MLMjAf4gX7cDKN/26zDOD6c= +github.com/gobuffalo/nulls v0.2.0/go.mod h1:w4q8RoSCEt87Q0K0sRIZWYeIxkxog5mh3eN3C/n+dUc= +github.com/gobuffalo/nulls v0.3.0/go.mod h1:UP49vd/k+bcaz6m0cHMyuk8oQ7XgLnkfxeiVoPAvBSs= +github.com/gobuffalo/packd v0.0.0-20181027182251-01ad393492c8/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc= +github.com/gobuffalo/packd v0.0.0-20181027190505-aafc0d02c411/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc= +github.com/gobuffalo/packd v0.0.0-20181027194105-7ae579e6d213/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc= +github.com/gobuffalo/packd v0.0.0-20181031195726-c82734870264/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181104210303-d376b15f8e96/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181111195323-b2e760a5f0ff/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181114190715-f25c5d2471d7/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181124090624-311c6248e5fb/go.mod h1:Foenia9ZvITEvG05ab6XpiD5EfBHPL8A6hush8SJ0o8= +github.com/gobuffalo/packd v0.0.0-20181207120301-c49825f8f6f4/go.mod h1:LYc0TGKFBBFTRC9dg2pcRcMqGCTMD7T2BIMP7OBuQAA= +github.com/gobuffalo/packd v0.0.0-20181212173646-eca3b8fd6687/go.mod h1:LYc0TGKFBBFTRC9dg2pcRcMqGCTMD7T2BIMP7OBuQAA= github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.2.0/go.mod h1:k2CkHP3bjbqL2GwxwhxUy1DgnlbW644hkLC9iIUvZwY= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= +github.com/gobuffalo/packr v1.13.7/go.mod h1:KkinLIn/n6+3tVXMwg6KkNvWwVsrRAz4ph+jgpk3Z24= +github.com/gobuffalo/packr v1.15.0/go.mod h1:t5gXzEhIviQwVlNx/+3SfS07GS+cZ2hn76WLzPp6MGI= +github.com/gobuffalo/packr v1.15.1/go.mod h1:IeqicJ7jm8182yrVmNbM6PR4g79SjN9tZLH8KduZZwE= +github.com/gobuffalo/packr v1.19.0/go.mod h1:MstrNkfCQhd5o+Ct4IJ0skWlxN8emOq8DsoT1G98VIU= +github.com/gobuffalo/packr v1.20.0/go.mod h1:JDytk1t2gP+my1ig7iI4NcVaXr886+N0ecUga6884zw= +github.com/gobuffalo/packr v1.21.0/go.mod h1:H00jGfj1qFKxscFJSw8wcL4hpQtPe1PfU2wa6sg/SR0= +github.com/gobuffalo/packr v1.22.0/go.mod h1:Qr3Wtxr3+HuQEwWqlLnNW4t1oTvK+7Gc/Rnoi/lDFvA= +github.com/gobuffalo/packr/v2 v2.0.0-rc.8/go.mod h1:y60QCdzwuMwO2R49fdQhsjCPv7tLQFR0ayzxxla9zes= +github.com/gobuffalo/packr/v2 v2.0.0-rc.9/go.mod h1:fQqADRfZpEsgkc7c/K7aMew3n4aF1Kji7+lIZeR98Fc= +github.com/gobuffalo/packr/v2 v2.0.0-rc.10/go.mod h1:4CWWn4I5T3v4c1OsJ55HbHlUEKNWMITG5iIkdr4Px4w= +github.com/gobuffalo/packr/v2 v2.0.0-rc.11/go.mod h1:JoieH/3h3U4UmatmV93QmqyPUdf4wVM9HELaHEu+3fk= +github.com/gobuffalo/packr/v2 v2.0.0-rc.12/go.mod h1:FV1zZTsVFi1DSCboO36Xgs4pzCZBjB/tDV9Cz/lSaR8= +github.com/gobuffalo/packr/v2 v2.0.0-rc.13/go.mod h1:2Mp7GhBFMdJlOK8vGfl7SYtfMP3+5roE39ejlfjw0rA= +github.com/gobuffalo/packr/v2 v2.0.0-rc.14/go.mod h1:06otbrNvDKO1eNQ3b8hst+1010UooI2MFg+B2Ze4MV8= +github.com/gobuffalo/packr/v2 v2.0.0-rc.15/go.mod h1:IMe7H2nJvcKXSF90y4X1rjYIRlNMJYCxEhssBXNZwWs= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/packr/v2 v2.4.0/go.mod h1:ra341gygw9/61nSjAbfwcwh8IrYL4WmR4IsPkPBhQiY= +github.com/gobuffalo/packr/v2 v2.5.2/go.mod h1:sgEE1xNZ6G0FNN5xn9pevVu4nywaxHvgup67xisti08= +github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= +github.com/gobuffalo/packr/v2 v2.8.0/go.mod h1:PDk2k3vGevNE3SwVyVRgQCCXETC9SaONCNSXT1Q8M1g= +github.com/gobuffalo/plush v3.7.16+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.20+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.21+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.22+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.23+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.30+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.31+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.32+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.8.2+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.8.3+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush/v4 v4.0.0/go.mod h1:ErFS3UxKqEb8fpFJT7lYErfN/Nw6vHGiDMTjxpk5bQ0= +github.com/gobuffalo/plushgen v0.0.0-20181128164830-d29dcb966cb2/go.mod h1:r9QwptTFnuvSaSRjpSp4S2/4e2D3tJhARYbvEBcKSb4= +github.com/gobuffalo/plushgen v0.0.0-20181203163832-9fc4964505c2/go.mod h1:opEdT33AA2HdrIwK1aibqnTJDVVKXC02Bar/GT1YRVs= +github.com/gobuffalo/plushgen v0.0.0-20181207152837-eedb135bd51b/go.mod h1:Lcw7HQbEVm09sAQrCLzIxuhFbB3nAgp4c55E+UlynR0= +github.com/gobuffalo/plushgen v0.0.0-20190104222512-177cd2b872b3/go.mod h1:tYxCozi8X62bpZyKXYHw1ncx2ZtT2nFvG42kuLwYjoc= +github.com/gobuffalo/plushgen v0.1.2/go.mod h1:3U71v6HWZpVER1nInTXeAwdoRNsRd4W8aeIa1Lyp+Bk= +github.com/gobuffalo/pop v4.8.2+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop v4.8.3+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop v4.8.4+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop v4.13.1+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop/v5 v5.0.11/go.mod h1:mZJHJbA3cy2V18abXYuVop2ldEJ8UZ2DK6qOekC5u5g= +github.com/gobuffalo/pop/v5 v5.3.1/go.mod h1:vcEDhh6cJ3WVENqJDFt/6z7zNb7lLnlN8vj3n5G9rYA= +github.com/gobuffalo/release v1.0.35/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4= +github.com/gobuffalo/release v1.0.38/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4= +github.com/gobuffalo/release v1.0.42/go.mod h1:RPs7EtafH4oylgetOJpGP0yCZZUiO4vqHfTHJjSdpug= +github.com/gobuffalo/release v1.0.52/go.mod h1:RPs7EtafH4oylgetOJpGP0yCZZUiO4vqHfTHJjSdpug= +github.com/gobuffalo/release v1.0.53/go.mod h1:FdF257nd8rqhNaqtDWFGhxdJ/Ig4J7VcS3KL7n/a+aA= +github.com/gobuffalo/release v1.0.54/go.mod h1:Pe5/RxRa/BE8whDpGfRqSI7D1a0evGK1T4JDm339tJc= +github.com/gobuffalo/release v1.0.61/go.mod h1:mfIO38ujUNVDlBziIYqXquYfBF+8FDHUjKZgYC1Hj24= +github.com/gobuffalo/release v1.0.72/go.mod h1:NP5NXgg/IX3M5XmHmWR99D687/3Dt9qZtTK/Lbwc1hU= +github.com/gobuffalo/release v1.1.1/go.mod h1:Sluak1Xd6kcp6snkluR1jeXAogdJZpFFRzTYRs/2uwg= +github.com/gobuffalo/release v1.1.3/go.mod h1:CuXc5/m+4zuq8idoDt1l4va0AXAn/OSs08uHOfMVr8E= +github.com/gobuffalo/release v1.1.6/go.mod h1:18naWa3kBsqO0cItXZNJuefCKOENpbbUIqRL1g+p6z0= +github.com/gobuffalo/release v1.7.0/go.mod h1:xH2NjAueVSY89XgC4qx24ojEQ4zQ9XCGVs5eXwJTkEs= +github.com/gobuffalo/shoulders v1.0.1/go.mod h1:V33CcVmaQ4gRUmHKwq1fiTXuf8Gp/qjQBUL5tHPmvbA= +github.com/gobuffalo/shoulders v1.0.4/go.mod h1:LqMcHhKRuBPMAYElqOe3POHiZ1x7Ry0BE8ZZ84Bx+k4= +github.com/gobuffalo/syncx v0.0.0-20181120191700-98333ab04150/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobuffalo/syncx v0.0.0-20181120194010-558ac7de985f/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobuffalo/syncx v0.1.0/go.mod h1:Mg/s+5pv7IgxEp6sA+NFpqS4o2x+R9dQNwbwT0iuOGQ= +github.com/gobuffalo/tags v2.0.11+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.0.14+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.0.15+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.1.0+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.1.7+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags/v3 v3.0.2/go.mod h1:ZQeN6TCTiwAFnS0dNcbDtSgZDwNKSpqajvVtt6mlYpA= +github.com/gobuffalo/tags/v3 v3.1.0/go.mod h1:ZQeN6TCTiwAFnS0dNcbDtSgZDwNKSpqajvVtt6mlYpA= +github.com/gobuffalo/uuid v2.0.3+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= +github.com/gobuffalo/uuid v2.0.4+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= +github.com/gobuffalo/uuid v2.0.5+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= +github.com/gobuffalo/validate v2.0.3+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM= +github.com/gobuffalo/validate v2.0.4+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM= +github.com/gobuffalo/validate/v3 v3.0.0/go.mod h1:HFpjq+AIiA2RHoQnQVTFKF/ZpUPXwyw82LgyDPxQ9r0= +github.com/gobuffalo/validate/v3 v3.1.0/go.mod h1:HFpjq+AIiA2RHoQnQVTFKF/ZpUPXwyw82LgyDPxQ9r0= +github.com/gobuffalo/validate/v3 v3.2.0/go.mod h1:PrhDOdDHxtN8KUgMvF3TDL0r1YZXV4sQnyFX/EmeETY= +github.com/gobuffalo/x v0.0.0-20181003152136-452098b06085/go.mod h1:WevpGD+5YOreDJznWevcn8NTmQEW5STSBgIkpkjzqXc= +github.com/gobuffalo/x v0.0.0-20181007152206-913e47c59ca7/go.mod h1:9rDPXaB3kXdKWzMc4odGQQdG2e2DIEmANy5aSJ9yesY= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= @@ -879,9 +1148,12 @@ github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3K github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/gocql/gocql v0.0.0-20200228163523-cd4b606dd2fb/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid/v3 v3.1.2/go.mod h1:xPwMqoocQ1L5G6pXX5BcE7N5jlzn2o19oqAKxwZW/kI= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= @@ -906,6 +1178,8 @@ github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyH github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/gddo v0.0.0-20180828051604-96d2a289f41e/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= +github.com/golang/gddo v0.0.0-20190904175337-72a348e765d2/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= @@ -916,6 +1190,7 @@ github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= @@ -925,6 +1200,7 @@ github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71 github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -986,6 +1262,7 @@ github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4r github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI= github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= +github.com/google/go-jsonnet v0.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= @@ -1025,6 +1302,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -1053,18 +1331,23 @@ github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+ github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gophercloud/gophercloud v1.2.0 h1:1oXyj4g54KBg/kFtCdMM6jtxSzeIyg8wv4z1HoGPp1E= github.com/gophercloud/gophercloud v1.2.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= +github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.1.2/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -1072,6 +1355,8 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gotestyourself/gotestyourself v1.3.0/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/grafana/alerting v0.0.0-20230428095912-33c5aa68a5ba h1:aNTu22ojw4XY24DYNAuvw8v/5iFUNk2bkdTeeWQ5+0o= github.com/grafana/alerting v0.0.0-20230428095912-33c5aa68a5ba/go.mod h1:5edgy6tQY4+W2wuJdi8g2GjbVmpJufguy7QGIRl2q4o= github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw= @@ -1174,6 +1459,7 @@ github.com/hashicorp/go-plugin v1.4.9 h1:ESiK220/qE0aGxWdzKIvRH69iLiuN/PjoLTm69R github.com/hashicorp/go-plugin v1.4.9/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= @@ -1202,6 +1488,7 @@ github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU= github.com/hashicorp/golang-lru/v2 v2.0.2/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -1249,6 +1536,7 @@ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb v1.7.6/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb-client-go/v2 v2.6.0 h1:bIOaGTgvvv1Na2hG+nIvqyv7PK2UiU2WrJN1ck1ykyM= @@ -1258,6 +1546,7 @@ github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 h1:vilfsDSy7TDxedi9gyBkMvAirat/oRcL0lFdJBf6tdM= github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/inhies/go-bytesize v0.0.0-20201103132853-d0aed0d254f8/go.mod h1:KrtyD5PFj++GKkFS/7/RRrfnRhAMGQwy75GLCHWrCNs= github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/ionos-cloud/sdk-go/v6 v6.1.4 h1:BJHhFA8Q1SZC7VOXqKKr2BV2ysQ2/4hlk1e4hZte7GY= @@ -1274,6 +1563,9 @@ github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80s github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.3.2/go.mod h1:LvCquS3HbBKwgl7KbX9KyqEIumJAbm1UMcTvGaIf3bM= +github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.6.0/go.mod h1:yeseQo4xhQbgyJs2c87RAXOH2i624N0Fh1KSPJya7qo= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= @@ -1288,26 +1580,36 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.0.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= +github.com/jackc/pgtype v1.3.0/go.mod h1:b0JqxHvPmljG+HQ5IsvQ0yqeSi4nGcDTVjFoiLDb0Ik= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.4.1/go.mod h1:6iSW+JznC0YT+SgBn7rNxoEBsBgSmnC5FwyCekOGUiE= +github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oAlxAg= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jaegertracing/jaeger v1.24.0/go.mod h1:mqdtFDA447va5j0UewDaAWyNlGreGQyhGxXVhbF58gQ= +github.com/jandelgado/gcov2lcov v1.0.4-0.20210120124023-b83752c6dc08/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= @@ -1316,6 +1618,7 @@ github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/U github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= @@ -1326,8 +1629,12 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= +github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= @@ -1359,13 +1666,22 @@ github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5Pt github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/karrick/godirwalk v1.7.7/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/karrick/godirwalk v1.7.8/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/karrick/godirwalk v1.10.9/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/karrick/godirwalk v1.15.3/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/karrick/godirwalk v1.15.5/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= @@ -1386,9 +1702,11 @@ github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/knadh/koanf v0.14.1-0.20201201075439-e0853799f9ec/go.mod h1:H5mEFsTeWizwFXHKtsITL5ipsLTuAMQoGuQpp+1JL9U= github.com/knadh/koanf v1.2.0/go.mod h1:xpPTwMhsA/aaQLAilyCCqfpEiY1gpa160AiCuWHJUjY= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -1401,6 +1719,7 @@ github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NB github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -1432,6 +1751,9 @@ github.com/linode/linodego v1.14.1 h1:uGxQyy0BidoEpLGdvfi4cPgEW+0YUFsEGrLEhcTfjN github.com/linode/linodego v1.14.1/go.mod h1:NJlzvlNtdMRRkXb0oN6UWzUkj6t+IBsyveHgZ5Ppjyk= github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/luna-duclos/instrumentedsql v0.0.0-20181127104832-b7d587d28109/go.mod h1:PWUIzhtavmOR965zfawVsHXbEuU1G29BPZ/CB3C7jXk= +github.com/luna-duclos/instrumentedsql v1.1.2/go.mod h1:4LGbEqDnopzNAiyxPPDXhLspyunZxgPTMJBKtC6U0BQ= +github.com/luna-duclos/instrumentedsql v1.1.3/go.mod h1:9J1njvFds+zN7y85EDhN9XNQLANWwZt2ULeIC8yMNYs= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/m3db/prometheus_remote_client_golang v0.4.4 h1:DsAIjVKoCp7Ym35tAOFL1OuMLIdIikAEHeNPHY+yyM8= github.com/m3db/prometheus_remote_client_golang v0.4.4/go.mod h1:wHfVbA3eAK6dQvKjCkHhusWYegCk3bDGkA15zymSHdc= @@ -1439,7 +1761,9 @@ github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXq github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -1449,8 +1773,28 @@ github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7 github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/markbates/deplist v1.0.4/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= +github.com/markbates/deplist v1.0.5/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= +github.com/markbates/deplist v1.1.3/go.mod h1:BF7ioVzAJYEtzQN/os4rt8H8Ti3h0T7EoN+7eyALktE= +github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= +github.com/markbates/going v1.0.2/go.mod h1:UWCk3zm0UKefHZ7l8BNqi26UyiEMniznk8naLdTcy6c= +github.com/markbates/grift v1.0.4/go.mod h1:wbmtW74veyx+cgfwFhlnnMWqhoz55rnHR47oMXzsyVs= +github.com/markbates/hmax v1.0.0/go.mod h1:cOkR9dktiESxIMu+65oc/r/bdY4bE8zZw3OLhLx0X2c= +github.com/markbates/inflect v1.0.0/go.mod h1:oTeZL2KHA7CUX6X+fovmK9OvIOFuqu0TwdQrZjLTh88= +github.com/markbates/inflect v1.0.1/go.mod h1:uv3UVNBe5qBIfCm8O8Q+DW+S1EopeyINj+Ikhc7rnCk= +github.com/markbates/inflect v1.0.3/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= +github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= +github.com/markbates/oncer v0.0.0-20180924031910-e862a676800b/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v0.0.0-20180924034138-723ad0170a46/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= +github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= +github.com/markbates/refresh v1.4.10/go.mod h1:NDPHvotuZmTmesXxr95C9bjlw1/0frJwtME2dzcVKhc= +github.com/markbates/safe v1.0.0/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/markbates/sigtx v1.0.0/go.mod h1:QF1Hv6Ic6Ca6W+T+DL0Y/ypborFKyvUY9HmuCD4VeTc= +github.com/markbates/willie v1.0.9/go.mod h1:fsrFVWl91+gXpx/6dv715j7i11fYPfZ9ZGfH0DQzY7w= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= @@ -1494,16 +1838,21 @@ github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/mattn/goveralls v0.0.6 h1:cr8Y0VMo/MnEZBjxNN/vh6G90SZ7IMb6lms1dzMoO+Y= +github.com/mattn/goveralls v0.0.6/go.mod h1:h8b4ow6FxSPMQHF6o2ve3qsclnffZjYTNEKmLesRwqw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= @@ -1531,7 +1880,9 @@ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTS github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -1548,6 +1899,7 @@ github.com/mjibson/esc v0.2.0/go.mod h1:9Hw9gxxfHulMF5OJKCyhYD7PzlSdhzXyaGEBRPH1 github.com/moby/moby v23.0.4+incompatible h1:A/pe8vi9KIKhNbzR0G3wW4ACKDsMgXILBveMqiJNa8M= github.com/moby/moby v23.0.4+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2/go.mod h1:TjQg8pa4iejrUrjiz0MCtMV38jdMNW4doKSiBrEvCQQ= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1560,10 +1912,12 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto= @@ -1596,6 +1950,7 @@ github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1t github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -1606,12 +1961,17 @@ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/oleiade/reflections v1.0.0/go.mod h1:RbATFBbKYkVdqmSFtx13Bb/tVhR0lgOBXunWTZKeL4w= +github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olivere/elastic v6.2.35+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.9.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -1623,7 +1983,12 @@ github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47 github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.6.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= @@ -1643,6 +2008,8 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02/go.mod h1:JNdpVEzCpXBgIiv4ds+TzhN1hrtxq6ClLrTlT9OQRSc= github.com/opentracing-contrib/go-grpc v0.0.0-20191001143057-db30781987df/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= @@ -1657,19 +2024,53 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/ory/analytics-go/v4 v4.0.0/go.mod h1:FMx9cLRD9xN+XevPvZ5FDMfignpmcqPP6FUKnJ9/MmE= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/ory/dockertest/v3 v3.5.4/go.mod h1:J8ZUbNB2FOhm1cFZW9xBpDsODqsSWcyYgtJYVPcnF70= +github.com/ory/dockertest/v3 v3.6.3/go.mod h1:EFLcVUOl8qCwp9NyDAcCDtq/QviLtYswW/VbWzUnTNE= +github.com/ory/fosite v0.29.0/go.mod h1:0atSZmXO7CAcs6NPMI/Qtot8tmZYj04Nddoold4S2h0= +github.com/ory/fosite v0.44.1-0.20230317114349-45a6785cc54f h1:OFA3y3TJ2qsBXCBMXUNvTzHNBS8/kXdk4cHpJGzBKO4= +github.com/ory/fosite v0.44.1-0.20230317114349-45a6785cc54f/go.mod h1:N0WZtyPBAuXedTpwzbKl4tSYU8wpjlMQoxnKcL2m8dU= +github.com/ory/go-acc v0.0.0-20181118080137-ddc355013f90/go.mod h1:sxnvPCxChFuSmTJGj8FdMupeq1BezCiEpDjTUXQ4hf4= +github.com/ory/go-acc v0.2.6 h1:YfI+L9dxI7QCtWn2RbawqO0vXhiThdXu/RgizJBbaq0= +github.com/ory/go-acc v0.2.6/go.mod h1:4Kb/UnPcT8qRAk3IAxta+hvVapdxTLWtrr7bFLlEgpw= +github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTsTS8= +github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs= +github.com/ory/gojsonreference v0.0.0-20190720135523-6b606c2d8ee8/go.mod h1:wsH1C4nIeeQClDtD5AH7kF1uTS6zWyqfjVDTmB0Em7A= +github.com/ory/gojsonschema v1.1.1-0.20190919112458-f254ca73d5e9/go.mod h1:BNZpdJgB74KOLSsWFvzw6roXg1I6O51WO8roMmW+T7Y= +github.com/ory/herodot v0.6.2/go.mod h1:3BOneqcyBsVybCPAJoi92KN2BpJHcmDqAMcAAaJiJow= +github.com/ory/herodot v0.7.0/go.mod h1:YXKOfAXYdQojDP5sD8m0ajowq3+QXNdtxA+QiUXBwn0= +github.com/ory/herodot v0.8.3/go.mod h1:rvLjxOAlU5omtmgjCfazQX2N82EpMfl3BytBWc1jjsk= +github.com/ory/herodot v0.9.2/go.mod h1:Da2HXR8mpwPbPrH+Gv9qV8mM5gI3v+PoJ69BA4l2RAk= +github.com/ory/jsonschema/v3 v3.0.1/go.mod h1:jgLHekkFk0uiGdEWGleC+tOm6JSSP8cbf17PnBuGXlw= +github.com/ory/viper v1.5.6/go.mod h1:TYmpFpKLxjQwvT4f0QPpkOn4sDXU1kDgAwJpgLYiQ28= +github.com/ory/viper v1.7.4/go.mod h1:T6sodNZKNGPpashUOk7EtXz2isovz8oCd57GNVkkNmE= +github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE= +github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= +github.com/ory/x v0.0.84/go.mod h1:RXLPBG7B+hAViONVg0sHwK+U/ie1Y/NeXrq1JcARfoE= +github.com/ory/x v0.0.93/go.mod h1:lfcTaGXpTZs7IEQAW00r9EtTCOxD//SiP5uWtNiz31g= +github.com/ory/x v0.0.110/go.mod h1:DJfkE3GdakhshNhw4zlKoRaL/ozg/lcTahA9OCih2BE= +github.com/ory/x v0.0.127/go.mod h1:FwUujfFuCj5d+xgLn4fGMYPnzriR5bdAIulFXMtnK0M= +github.com/ory/x v0.0.214 h1:nz5ijvm5MVhYxWsQSuUrW1hj9F5QLZvPn/nLo5s06T4= +github.com/ory/x v0.0.214/go.mod h1:aRl57gzyD4GF0HQCekovXhv0xTZgAgiht3o8eVhsm9Q= github.com/ovh/go-ovh v1.3.0 h1:mvZaddk4E4kLcXhzb+cxBsMPYp2pHqiQpWYkInsuZPQ= github.com/ovh/go-ovh v1.3.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= @@ -1677,6 +2078,8 @@ github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6 github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= @@ -1692,6 +2095,7 @@ github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1762,8 +2166,10 @@ github.com/prometheus/exporter-toolkit v0.9.1/go.mod h1:iFlTmFISCix0vyuyBmm0UqOU github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -1784,6 +2190,8 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.0.2 h1:BA426Zqe/7r56kCcvxYLWe1mkaz71LKF77GwgFzSxfE= github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps= +github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rhnvrm/simples3 v0.5.0/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -1795,20 +2203,26 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= +github.com/rogpeppe/go-internal v1.0.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/rubenv/sql-migrate v0.0.0-20190212093014-1007f53448d7/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= github.com/rueian/rueidis v0.0.100-go1.18 h1:uMGspTtI5baJcF7ZUzn9lz58SzY8lhkj02OEGTNUrlg= github.com/rueian/rueidis v0.0.100-go1.18/go.mod h1:BQELtOQXfogKB9dqRmbq044YyXjLtKlTN07Ap4ImPgo= github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg= @@ -1824,6 +2238,8 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4= +github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= +github.com/santhosh-tekuri/jsonschema/v2 v2.1.0/go.mod h1:yzJzKUGV4RbWqWIBBP4wSOBqavX5saE02yirLS0OTyg= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.14 h1:yFl3jyaSVLNYXlnNYM5z2pagEk1dYQhfr1p20T1NyKY= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.14/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= @@ -1832,13 +2248,22 @@ github.com/sdboyer/cue v0.5.0-beta.2.0.20230419165817-251c3ae823d8 h1:R7AphSCwo1 github.com/sdboyer/cue v0.5.0-beta.2.0.20230419165817-251c3ae823d8/go.mod h1:okjJBHFQFer+a41sAe2SaGm1glWS8oEb6CmJvn5Zdws= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210219220335-367fa274be2c/go.mod h1:/THDZYi7F/BsVEcYzYPqdcWFQ+1C2InkawTKfLOAnzg= github.com/securego/gosec v0.0.0-20200203094520-d13bb6d2420c/go.mod h1:gp0gaHj0WlmPh9BdsTmo1aq6C27yIPWdxCKGFGdVKBE= +github.com/segmentio/analytics-go v3.0.1+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= +github.com/segmentio/analytics-go v3.1.0+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M= +github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= +github.com/segmentio/conf v1.2.0/go.mod h1:Y3B9O/PqqWqjyxyWWseyj/quPEtMu1zDp/kVbSWWaB0= github.com/segmentio/encoding v0.3.6 h1:E6lVLyDPseWEulBmCmAKPanDd3jiyGDo5gMcugCRwZQ= github.com/segmentio/encoding v0.3.6/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM= +github.com/segmentio/go-snakecase v1.1.0/go.mod h1:jk1miR5MS7Na32PZUykG89Arm+1BUSYhuGR6b7+hJto= +github.com/segmentio/objconv v1.0.1/go.mod h1:auayaH5k3137Cl4SoXTgrzQcuQDmvuVtZgS0fb1Ahys= github.com/sercand/kuberesolver v2.4.0+incompatible/go.mod h1:lWF3GL0xptCB/vCiJPl/ZshwPsX/n4Y7u0CW9E7aQIQ= +github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= @@ -1848,15 +2273,27 @@ github.com/shoenig/test v0.6.2/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnj github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20170515013102-78fb10f4a5f8/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -1865,40 +2302,58 @@ github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.0/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.2-0.20200723214538-8d17101741c8/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI= +github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A= +github.com/square/go-jose/v3 v3.0.0-20200630053402-0a67ce9b0693/go.mod h1:6hSY48PjDm4UObWmGLyJE9DxYVKTgR9kbCspXXJEhcU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -1926,18 +2381,28 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= +github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= +github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/gjson v1.7.1/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/sjson v1.0.4/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y= +github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE= github.com/tidwall/tinylru v1.0.2/go.mod h1:HDVL7TsWeezQ4g44Um84TOVBMFcq7Xa9giqNc805KJ8= github.com/tidwall/wal v0.1.4/go.mod h1:ww7Pd44/KnyETODJPUPKrzLlYjI72GZWlucNKt7pOt0= +github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -1945,9 +2410,13 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f h1:A+MmlgpvrHLeUP8dkBVn4Pnf5Bp5Yk2OALm7SEJLLE8= github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f/go.mod h1:OBcG9bn7sHtXgarhUEb3OfCnNsgtGnkVf41ilSZ3K3E= +github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= +github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-client-go v2.28.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-client-go v2.29.1+incompatible h1:R9ec3zO3sGpzs0abd43Y+fBZRJ9uiH6lXyR/+u6brW4= github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= @@ -1965,6 +2434,8 @@ github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 h1:4EYQaWAatQokdji3zqZloVIW/Ke1RQjYw2zHULyrHJg= github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= +github.com/unrolled/secure v0.0.0-20180918153822-f340ee86eb8b/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= +github.com/unrolled/secure v0.0.0-20181005190816-ff9db2ff917f/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= @@ -2020,6 +2491,7 @@ github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/yalue/merged_fs v1.2.2 h1:vXHTpJBluJryju7BBpytr3PDIkzsPMpiEknxVGPhN/I= github.com/yalue/merged_fs v1.2.2/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= @@ -2044,6 +2516,10 @@ github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxt github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= +go.elastic.co/apm v1.8.0/go.mod h1:tCw6CkOJgkWnzEthFN9HUP1uL3Gjc/Ur6m7gRPLaoH0= +go.elastic.co/apm/module/apmhttp v1.8.0/go.mod h1:9LPFlEON51/lRbnWDfqAWErihIiAFDUMfMV27YjoWQ8= +go.elastic.co/apm/module/apmot v1.8.0/go.mod h1:Q5Xzabte8G/fkvDjr1jlDuOSUt9hkVWNZEHh6ZNaTjI= +go.elastic.co/fastjson v1.0.0/go.mod h1:PmeUOMMtLHQr9ZS9J9owrAVg0FkaZDRZJEFTTGHtchs= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= @@ -2079,6 +2555,7 @@ go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -2090,10 +2567,12 @@ go.opentelemetry.io/collector v0.31.0 h1:Lxrsl/fhYQTcsrAOwklQpKi8T+ghxLEmmUjFqey go.opentelemetry.io/collector v0.31.0/go.mod h1:A9vKmEa2MI/vJXNUoRinq9w25ZMmxWJLYXYELHkBEw0= go.opentelemetry.io/collector/model v0.31.0 h1:IgMOkSBd/n/gV4EQQ1nJ+/ylddOlqTfMGjku91yC0d8= go.opentelemetry.io/collector/model v0.31.0/go.mod h1:PcHNnM+RUl0uD8VkSn93PO78N7kQYhfqpI/eki57pl4= +go.opentelemetry.io/contrib v0.18.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib v0.21.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.21.0/go.mod h1:Vm5u/mtkj1OMhtao0v+BGo2LUoLCgHYXvRmj0jWITlE= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0 h1:5jD3teb4Qh7mx/nfzq4jO2WFFpvXD0vYWFDrdvNWmXk= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0/go.mod h1:UMklln0+MRhZC4e3PwmN3pCtq4DyIadWw4yikh6bNrw= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.18.0/go.mod h1:iK1G0FgHurSJ/aYLg5LpnPI0pqdanM73S3dhyDp0Lk4= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.37.0 h1:H0wsFGpY3uD/zB/5UubZgkgnd378/ogV9BH2itqEFbc= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.37.0/go.mod h1:xXATK4LOREcHuSE4sWsK1VO7FUxa6L58rAORHFTdhAI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.21.0/go.mod h1:JQAtechjxLEL81EjmbRwxBq/XEzGaHcsPuDHAx54hg4= @@ -2103,6 +2582,7 @@ go.opentelemetry.io/contrib/propagators/jaeger v1.15.0/go.mod h1:tU0nwW4QTvKceNU go.opentelemetry.io/contrib/samplers/jaegerremote v0.9.0 h1:zRi6a8uX+cJGTPLXRPjFEzN27a26k5R7LiLK87ntXgg= go.opentelemetry.io/contrib/samplers/jaegerremote v0.9.0/go.mod h1:pzJOLTppaPbiPjPZEwGRf0VWx6G07hhOqznjKXIMkEk= go.opentelemetry.io/contrib/zpages v0.0.0-20210722161726-7668016acb73/go.mod h1:NAkejuYm41lpyL43Fu1XdnCOYxN5NVV80/MJ03JQ/X8= +go.opentelemetry.io/otel v0.18.0/go.mod h1:PT5zQj4lTsR1YeARt8YNKcFb88/c2IKoSABK9mX0r78= go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I= go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg= go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= @@ -2118,15 +2598,18 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 h1:ap+y8 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0/go.mod h1:5w41DY6S9gZrbjuq6Y+753e96WfPha5IcsOSZTtullM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0/go.mod h1:+N7zNjIJv4K+DeX67XXET0P+eIciESgaFDBqh+ZJFS4= go.opentelemetry.io/otel/internal/metric v0.21.0/go.mod h1:iOfAaY2YycsXfYD4kaRSbLx2LKmfpKObWBEv9QK5zFo= +go.opentelemetry.io/otel/metric v0.18.0/go.mod h1:kEH2QtzAyBy3xDVQfGZKIcok4ZZFvd5xyKPfPcuK6pE= go.opentelemetry.io/otel/metric v0.21.0/go.mod h1:JWCt1bjivC4iCrz/aCrM1GSw+ZcvY44KCbaeeRhzHnc= go.opentelemetry.io/otel/metric v0.37.0 h1:pHDQuLQOZwYD+Km0eb657A25NaRzy0a+eLyKfDXedEs= go.opentelemetry.io/otel/metric v0.37.0/go.mod h1:DmdaHfGt54iV6UKxsV9slj2bBRJcKC1B1uvDLIioc1s= +go.opentelemetry.io/otel/oteltest v0.18.0/go.mod h1:NyierCU3/G8DLTva7KRzGii2fdxdR89zXKH1bNWY7Bo= go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4= go.opentelemetry.io/otel/sdk v1.0.0-RC1/go.mod h1:kj6yPn7Pgt5ByRuwesbaWcRLA+V7BSDg3Hf8xRvsvf8= go.opentelemetry.io/otel/sdk v1.0.0/go.mod h1:PCrDHlSy5x1kjezSdL37PhbFUMjrsLRshJ2zCzeXwbM= go.opentelemetry.io/otel/sdk v1.11.1/go.mod h1:/l3FE4SupHJ12TduVjUkZtlfFqDCQJlOlithYrdktys= go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM= +go.opentelemetry.io/otel/trace v0.18.0/go.mod h1:FzdUu3BPwZSZebfQ1vl5/tAa8LyMLXSJN57AXIt/iDk= go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg= go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs= go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= @@ -2170,9 +2653,23 @@ go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= gocloud.dev v0.25.0 h1:Y7vDq8xj7SyM848KXf32Krda2e6jQ4CLh/mTeCSqXtk= gocloud.dev v0.25.0/go.mod h1:7HegHVCYZrMiU3IE1qtnzf/vRrDwLYnRNR3EhWX8x9Y= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181024171144-74cb1d3d52f4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181025113841-85e1b3f9139a/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190102171810-8d7daa0c54b3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -2191,10 +2688,14 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -2229,6 +2730,7 @@ golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= @@ -2271,6 +2773,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -2281,13 +2784,21 @@ golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180816102801-aaf60122140d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180921000356-2f5d2388922f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2309,10 +2820,12 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200219183655-46282727080f/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -2379,6 +2892,7 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2429,17 +2943,31 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180921163948-d47a0f339242/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180927150500-dad3d9fb7b6e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181022134430-8a28ead16f52/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181024145615-5cd93ef61a7c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181025063200-d989b31c8746/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026064943-731415f00dce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181106135930-3a76605856fd/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2455,6 +2983,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2472,7 +3001,9 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2481,6 +3012,7 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2494,11 +3026,13 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2624,9 +3158,31 @@ golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181003024731-2f84ea8ef872/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181006002542-f60d9635b16a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181008205924-a2b3f7f249e9/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181013182035-5e66757b835f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181017214349-06f26fdaaa28/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181024171208-a2dc47679d30/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181105230042-78dc5bac0cac/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181107215632-34b416bd17b3/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181112210238-4b1f3b6b1646/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181114190951-94339b83286c/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181119130350-139d099f6620/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181127195227-b4e97c0ed882/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181127232545-e782529d0ddd/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181203210056-e5f3ab76ea4b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181205224935-3576414c54a4/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181206194817-bcd4e47d0288/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181207183836-8bc39b988060/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181212172921-837e80568c09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190102213336-ca9055ed7d04/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190104182027-498d95493402/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190111214448-fc1d57b08d7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190118193359-16909d206f00/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -2645,16 +3201,20 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190613204242-ed0dc450797f/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190711191110-9a621aea19f8/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -2666,26 +3226,32 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191224055732-dd894d0a8a40/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200203023011-6f24f261dadb/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200203215610-ab391d50b528/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -2702,6 +3268,7 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= @@ -2722,13 +3289,16 @@ golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNq golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.2/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E= gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20191229114700-bbb4dff026f8/go.mod h1:2IgXn/sJaRbePPBA1wRj8OE+QLvVaH0q8SK6TSTKlnk= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.0.0-20200111075622-4abb28f724d5/go.mod h1:+HbaZVpsa73UwN7kXGCECULRHovLRJjH+t5cFPgxErs= gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= @@ -2809,6 +3379,7 @@ google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200910201057-6591123024b3/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc/examples v0.0.0-20210304020650-930c79186c99/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -2826,6 +3397,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.29.0/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/DataDog/dd-trace-go.v1 v1.27.0/go.mod h1:Sp1lku8WJMvNV0kjDI4Ni/T7J/U3BO5ct5kEaoVU8+I= +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/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= @@ -2842,26 +3415,38 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/mold.v2 v2.2.0/go.mod h1:XMyyRsGtakkDPbxXbrA5VODo6bUXyvoDjLd5l3T0XoA= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= +gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.1.9/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.2-0.20210529014059-a5c7eec3c614 h1:lwJmuuJQGclcankpPJwh8rorzB0bNbVALv8phDGh8TQ= +gopkg.in/square/go-jose.v2 v2.5.2-0.20210529014059-a5c7eec3c614/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/telebot.v3 v3.1.3/go.mod h1:GJKwwWqp9nSkIVN51eRKU78aB5f5OnQuWdwiIZfPbko= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/validator.v2 v2.0.0-20180514200540-135c24b11c19/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -2870,6 +3455,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -2895,6 +3481,7 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.2.0/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= k8s.io/api v0.25.3/go.mod h1:o42gKscFrEVjHdQnyRenACrMtbuJsVdP+WVjqejfzmI= k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ= k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= @@ -2920,6 +3507,11 @@ k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/ k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20230308161112-d77c459e9343 h1:m7tbIjXGcGIAtpmQr7/NAi7RsWoW3E7Zcm4jI1HicTc= k8s.io/utils v0.0.0-20230308161112-d77c459e9343/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 9733f2d7ed0..bd2e3aca265 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -85,6 +85,8 @@ import ( ngmetrics "github.com/grafana/grafana/pkg/services/ngalert/metrics" ngstore "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/notifications" + "github.com/grafana/grafana/pkg/services/oauthserver" + "github.com/grafana/grafana/pkg/services/oauthserver/oasimpl" "github.com/grafana/grafana/pkg/services/oauthtoken" "github.com/grafana/grafana/pkg/services/oauthtoken/oauthtokentest" "github.com/grafana/grafana/pkg/services/org/orgimpl" @@ -354,6 +356,8 @@ var wireBasicSet = wire.NewSet( wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, supportbundlesimpl.ProvideService, + oasimpl.ProvideService, + wire.Bind(new(oauthserver.OAuth2Server), new(*oasimpl.OAuth2ServiceImpl)), loggermw.Provide, modules.WireSet, signingkeysimpl.ProvideEmbeddedSigningKeysService, diff --git a/pkg/services/accesscontrol/accesscontrol.go b/pkg/services/accesscontrol/accesscontrol.go index fc05a021ee7..812c0617f79 100644 --- a/pkg/services/accesscontrol/accesscontrol.go +++ b/pkg/services/accesscontrol/accesscontrol.go @@ -196,6 +196,7 @@ func GroupScopesByAction(permissions []Permission) map[string][]string { return m } +// Reduce will reduce a list of permissions to its minimal form, grouping scopes by action func Reduce(ps []Permission) map[string][]string { reduced := make(map[string][]string) scopesByAction := make(map[string]map[string]bool) @@ -260,6 +261,110 @@ func Reduce(ps []Permission) map[string][]string { return reduced } +// intersectScopes computes the minimal list of scopes common to two slices. +func intersectScopes(s1, s2 []string) []string { + if len(s1) == 0 || len(s2) == 0 { + return []string{} + } + + // helpers + splitScopes := func(s []string) (map[string]bool, map[string]bool) { + scopes := make(map[string]bool) + wildcards := make(map[string]bool) + for _, s := range s { + if isWildcard(s) { + wildcards[s] = true + } else { + scopes[s] = true + } + } + return scopes, wildcards + } + includes := func(wildcardsSet map[string]bool, scope string) bool { + for wildcard := range wildcardsSet { + if wildcard == "*" || strings.HasPrefix(scope, wildcard[:len(wildcard)-1]) { + return true + } + } + return false + } + + res := make([]string, 0) + + // split input into scopes and wildcards + s1Scopes, s1Wildcards := splitScopes(s1) + s2Scopes, s2Wildcards := splitScopes(s2) + + // intersect wildcards + wildcards := make(map[string]bool) + for s := range s1Wildcards { + // if s1 wildcard is included in s2 wildcards + // then it is included in the intersection + if includes(s2Wildcards, s) { + wildcards[s] = true + continue + } + } + for s := range s2Wildcards { + // if s2 wildcard is included in s1 wildcards + // then it is included in the intersection + if includes(s1Wildcards, s) { + wildcards[s] = true + } + } + + // intersect scopes + scopes := make(map[string]bool) + for s := range s1Scopes { + // if s1 scope is included in s2 wilcards or s2 scopes + // then it is included in the intersection + if includes(s2Wildcards, s) || s2Scopes[s] { + scopes[s] = true + } + } + for s := range s2Scopes { + // if s2 scope is included in s1 wilcards + // then it is included in the intersection + if includes(s1Wildcards, s) { + scopes[s] = true + } + } + + // merge wildcards and scopes + for w := range wildcards { + res = append(res, w) + } + for s := range scopes { + res = append(res, s) + } + + return res +} + +// Intersect returns the intersection of two slices of permissions, grouping scopes by action. +func Intersect(p1, p2 []Permission) map[string][]string { + if len(p1) == 0 || len(p2) == 0 { + return map[string][]string{} + } + + res := make(map[string][]string) + p1m := Reduce(p1) + p2m := Reduce(p2) + + // Loop over the smallest map + if len(p1m) > len(p2m) { + p1m, p2m = p2m, p1m + } + + for a1, s1 := range p1m { + if s2, ok := p2m[a1]; ok { + res[a1] = intersectScopes(s1, s2) + } + } + + return res +} + func ValidateScope(scope string) bool { prefix, last := scope[:len(scope)-1], scope[len(scope)-1] // verify that last char is either ':' or '/' if last character of scope is '*' diff --git a/pkg/services/accesscontrol/accesscontrol_test.go b/pkg/services/accesscontrol/accesscontrol_test.go index b39ca3b8a80..1b13136cb78 100644 --- a/pkg/services/accesscontrol/accesscontrol_test.go +++ b/pkg/services/accesscontrol/accesscontrol_test.go @@ -1,6 +1,7 @@ package accesscontrol import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -125,3 +126,210 @@ func TestReduce(t *testing.T) { }) } } + +func TestIntersect(t *testing.T) { + tests := []struct { + name string + p1 []Permission + p2 []Permission + want map[string][]string + }{ + { + name: "no permission", + p1: []Permission{}, + p2: []Permission{}, + want: map[string][]string{}, + }, + { + name: "no intersection", + p1: []Permission{{Action: "orgs:read"}}, + p2: []Permission{{Action: "orgs:write"}}, + want: map[string][]string{}, + }, + { + name: "intersection no scopes", + p1: []Permission{{Action: "orgs:read"}}, + p2: []Permission{{Action: "orgs:read"}}, + want: map[string][]string{"orgs:read": {}}, + }, + { + name: "unbalanced intersection", + p1: []Permission{{Action: "teams:read", Scope: "teams:id:1"}}, + p2: []Permission{{Action: "teams:read"}}, + want: map[string][]string{"teams:read": {}}, + }, + { + name: "intersection", + p1: []Permission{ + {Action: "teams:read", Scope: "teams:id:1"}, + {Action: "teams:read", Scope: "teams:id:2"}, + {Action: "teams:write", Scope: "teams:id:1"}, + }, + p2: []Permission{ + {Action: "teams:read", Scope: "teams:id:1"}, + {Action: "teams:read", Scope: "teams:id:3"}, + {Action: "teams:write", Scope: "teams:id:1"}, + }, + want: map[string][]string{ + "teams:read": {"teams:id:1"}, + "teams:write": {"teams:id:1"}, + }, + }, + { + name: "intersection with wildcards", + p1: []Permission{ + {Action: "teams:read", Scope: "teams:id:1"}, + {Action: "teams:read", Scope: "teams:id:2"}, + {Action: "teams:write", Scope: "teams:id:1"}, + }, + p2: []Permission{ + {Action: "teams:read", Scope: "*"}, + {Action: "teams:write", Scope: "*"}, + }, + want: map[string][]string{ + "teams:read": {"teams:id:1", "teams:id:2"}, + "teams:write": {"teams:id:1"}, + }, + }, + { + name: "intersection with wildcards on both sides", + p1: []Permission{ + {Action: "dashboards:read", Scope: "dashboards:uid:1"}, + {Action: "dashboards:read", Scope: "folders:uid:1"}, + {Action: "dashboards:read", Scope: "dashboards:uid:*"}, + {Action: "folders:read", Scope: "folders:uid:1"}, + }, + p2: []Permission{ + {Action: "dashboards:read", Scope: "folders:uid:*"}, + {Action: "dashboards:read", Scope: "dashboards:uid:*"}, + {Action: "folders:read", Scope: "folders:uid:*"}, + }, + want: map[string][]string{ + "dashboards:read": {"dashboards:uid:*", "folders:uid:1"}, + "folders:read": {"folders:uid:1"}, + }, + }, + { + name: "intersection with wildcards of different sizes", + p1: []Permission{ + {Action: "dashboards:read", Scope: "folders:uid:1"}, + {Action: "dashboards:read", Scope: "dashboards:*"}, + {Action: "folders:read", Scope: "folders:*"}, + {Action: "teams:read", Scope: "teams:id:1"}, + }, + p2: []Permission{ + {Action: "dashboards:read", Scope: "folders:uid:*"}, + {Action: "dashboards:read", Scope: "dashboards:uid:*"}, + {Action: "folders:read", Scope: "folders:uid:*"}, + {Action: "teams:read", Scope: "*"}, + }, + want: map[string][]string{ + "dashboards:read": {"dashboards:uid:*", "folders:uid:1"}, + "folders:read": {"folders:uid:*"}, + "teams:read": {"teams:id:1"}, + }, + }, + } + check := func(t *testing.T, want map[string][]string, p1, p2 []Permission) { + intersect := Intersect(p1, p2) + for action, scopes := range intersect { + want, ok := want[action] + require.True(t, ok) + require.ElementsMatch(t, scopes, want, fmt.Sprintf("scopes for %v differs from expected", action)) + } + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Intersect is commutative + check(t, tt.want, tt.p1, tt.p2) + check(t, tt.want, tt.p2, tt.p1) + }) + } +} + +func Test_intersectScopes(t *testing.T) { + tests := []struct { + name string + s1 []string + s2 []string + want []string + }{ + { + name: "no values", + s1: []string{}, + s2: []string{}, + want: []string{}, + }, + { + name: "no values on one side", + s1: []string{}, + s2: []string{"teams:id:1"}, + want: []string{}, + }, + { + name: "empty values on one side", + s1: []string{""}, + s2: []string{"team:id:1"}, + want: []string{}, + }, + { + name: "no intersection", + s1: []string{"teams:id:1"}, + s2: []string{"teams:id:2"}, + want: []string{}, + }, + { + name: "intersection", + s1: []string{"teams:id:1"}, + s2: []string{"teams:id:1"}, + want: []string{"teams:id:1"}, + }, + { + name: "intersection with wildcard", + s1: []string{"teams:id:1", "teams:id:2"}, + s2: []string{"teams:id:*"}, + want: []string{"teams:id:1", "teams:id:2"}, + }, + { + name: "intersection of wildcards", + s1: []string{"teams:id:*"}, + s2: []string{"teams:id:*"}, + want: []string{"teams:id:*"}, + }, + { + name: "intersection with a bigger wildcards", + s1: []string{"teams:id:*"}, + s2: []string{"teams:*"}, + want: []string{"teams:id:*"}, + }, + { + name: "intersection of different wildcards with a bigger one", + s1: []string{"dashboards:uid:*", "folders:uid:*"}, + s2: []string{"*"}, + want: []string{"dashboards:uid:*", "folders:uid:*"}, + }, + { + name: "intersection with wildcards and scopes on both sides", + s1: []string{"dashboards:uid:*", "folders:uid:1"}, + s2: []string{"folders:uid:*", "dashboards:uid:1"}, + want: []string{"dashboards:uid:1", "folders:uid:1"}, + }, + { + name: "intersection of non reduced list of scopes", + s1: []string{"dashboards:uid:*", "dashboards:*", "dashboards:uid:1"}, + s2: []string{"dashboards:uid:*", "dashboards:*", "dashboards:uid:2"}, + want: []string{"dashboards:uid:*", "dashboards:*", "dashboards:uid:1", "dashboards:uid:2"}, + }, + } + check := func(t *testing.T, want []string, s1, s2 []string) { + intersect := intersectScopes(s1, s2) + require.ElementsMatch(t, want, intersect) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Intersect is commutative + check(t, tt.want, tt.s1, tt.s2) + check(t, tt.want, tt.s2, tt.s1) + }) + } +} diff --git a/pkg/services/accesscontrol/acimpl/service.go b/pkg/services/accesscontrol/acimpl/service.go index 55ca349fb56..13f1fee9205 100644 --- a/pkg/services/accesscontrol/acimpl/service.go +++ b/pkg/services/accesscontrol/acimpl/service.go @@ -58,6 +58,7 @@ func ProvideOSSService(cfg *setting.Cfg, store store, cache *localcache.CacheSer return s } +//go:generate mockery --name store --structname MockStore --outpkg actest --filename store_mock.go --output ../actest/ type store interface { GetUserPermissions(ctx context.Context, query accesscontrol.GetUserPermissionsQuery) ([]accesscontrol.Permission, error) SearchUsersPermissions(ctx context.Context, orgID int64, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) diff --git a/pkg/services/accesscontrol/actest/store_mock.go b/pkg/services/accesscontrol/actest/store_mock.go new file mode 100644 index 00000000000..6f38eea198a --- /dev/null +++ b/pkg/services/accesscontrol/actest/store_mock.go @@ -0,0 +1,151 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package actest + +import ( + accesscontrol "github.com/grafana/grafana/pkg/services/accesscontrol" + + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// MockStore is an autogenerated mock type for the store type +type MockStore struct { + mock.Mock +} + +// DeleteExternalServiceRole provides a mock function with given fields: ctx, externalServiceID +func (_m *MockStore) DeleteExternalServiceRole(ctx context.Context, externalServiceID string) error { + ret := _m.Called(ctx, externalServiceID) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, externalServiceID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DeleteUserPermissions provides a mock function with given fields: ctx, orgID, userID +func (_m *MockStore) DeleteUserPermissions(ctx context.Context, orgID int64, userID int64) error { + ret := _m.Called(ctx, orgID, userID) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { + r0 = rf(ctx, orgID, userID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetUserPermissions provides a mock function with given fields: ctx, query +func (_m *MockStore) GetUserPermissions(ctx context.Context, query accesscontrol.GetUserPermissionsQuery) ([]accesscontrol.Permission, error) { + ret := _m.Called(ctx, query) + + var r0 []accesscontrol.Permission + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, accesscontrol.GetUserPermissionsQuery) ([]accesscontrol.Permission, error)); ok { + return rf(ctx, query) + } + if rf, ok := ret.Get(0).(func(context.Context, accesscontrol.GetUserPermissionsQuery) []accesscontrol.Permission); ok { + r0 = rf(ctx, query) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]accesscontrol.Permission) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, accesscontrol.GetUserPermissionsQuery) error); ok { + r1 = rf(ctx, query) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUsersBasicRoles provides a mock function with given fields: ctx, userFilter, orgID +func (_m *MockStore) GetUsersBasicRoles(ctx context.Context, userFilter []int64, orgID int64) (map[int64][]string, error) { + ret := _m.Called(ctx, userFilter, orgID) + + var r0 map[int64][]string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []int64, int64) (map[int64][]string, error)); ok { + return rf(ctx, userFilter, orgID) + } + if rf, ok := ret.Get(0).(func(context.Context, []int64, int64) map[int64][]string); ok { + r0 = rf(ctx, userFilter, orgID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[int64][]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []int64, int64) error); ok { + r1 = rf(ctx, userFilter, orgID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SaveExternalServiceRole provides a mock function with given fields: ctx, cmd +func (_m *MockStore) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error { + ret := _m.Called(ctx, cmd) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, accesscontrol.SaveExternalServiceRoleCommand) error); ok { + r0 = rf(ctx, cmd) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SearchUsersPermissions provides a mock function with given fields: ctx, orgID, options +func (_m *MockStore) SearchUsersPermissions(ctx context.Context, orgID int64, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) { + ret := _m.Called(ctx, orgID, options) + + var r0 map[int64][]accesscontrol.Permission + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error)); ok { + return rf(ctx, orgID, options) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, accesscontrol.SearchOptions) map[int64][]accesscontrol.Permission); ok { + r0 = rf(ctx, orgID, options) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[int64][]accesscontrol.Permission) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, accesscontrol.SearchOptions) error); ok { + r1 = rf(ctx, orgID, options) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewMockStore interface { + mock.TestingT + Cleanup(func()) +} + +// NewMockStore creates a new instance of MockStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewMockStore(t mockConstructorTestingTNewMockStore) *MockStore { + mock := &MockStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index e16c59749e3..fef6e5466a4 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -316,8 +316,10 @@ const ( ActionAPIKeyDelete = "apikeys:delete" // Users actions - ActionUsersRead = "users:read" - ActionUsersWrite = "users:write" + ActionUsersRead = "users:read" + ActionUsersWrite = "users:write" + ActionUsersImpersonate = "users:impersonate" + // We can ignore gosec G101 since this does not contain any credentials. // nolint:gosec ActionUsersAuthTokenList = "users.authtoken:read" @@ -375,7 +377,8 @@ const ( ScopeAPIKeysAll = "apikeys:*" // Users scope - ScopeUsersAll = "users:*" + ScopeUsersAll = "users:*" + ScopeUsersPrefix = "users:id:" // Settings scope ScopeSettingsAll = "settings:*" diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 6cd1825e1de..13ea36b4dc3 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -27,6 +27,7 @@ import ( "github.com/grafana/grafana/pkg/services/ldap/service" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/loginattempt" + "github.com/grafana/grafana/pkg/services/oauthserver" "github.com/grafana/grafana/pkg/services/oauthtoken" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/quota" @@ -64,7 +65,7 @@ func ProvideService( features *featuremgmt.FeatureManager, oauthTokenService oauthtoken.OAuthTokenService, socialService social.Service, cache *remotecache.RemoteCache, ldapService service.LDAP, registerer prometheus.Registerer, - signingKeysService signingkeys.Service, + signingKeysService signingkeys.Service, oauthServer oauthserver.OAuth2Server, ) authn.Service { s := &Service{ log: log.New("authn.service"), @@ -131,7 +132,7 @@ func ProvideService( } if s.cfg.ExtendedJWTAuthEnabled && features.IsEnabled(featuremgmt.FlagExternalServiceAuth) { - s.RegisterClient(clients.ProvideExtendedJWT(userService, cfg, signingKeysService)) + s.RegisterClient(clients.ProvideExtendedJWT(userService, cfg, signingKeysService, oauthServer)) } for name := range socialService.GetOAuthProviders() { diff --git a/pkg/services/authn/clients/basic.go b/pkg/services/authn/clients/basic.go index edfb3859c8b..4d93abd5449 100644 --- a/pkg/services/authn/clients/basic.go +++ b/pkg/services/authn/clients/basic.go @@ -2,6 +2,7 @@ package clients import ( "context" + "strings" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/util/errutil" @@ -39,6 +40,13 @@ func (c *Basic) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden } func (c *Basic) Test(ctx context.Context, r *authn.Request) bool { + if r.HTTPRequest == nil { + return false + } + // The OAuth2 introspection endpoint uses basic auth but is handled by the oauthserver package. + if strings.HasPrefix(r.HTTPRequest.RequestURI, "/oauth2/introspect") { + return false + } return looksLikeBasicAuthRequest(r) } diff --git a/pkg/services/authn/clients/ext_jwt.go b/pkg/services/authn/clients/ext_jwt.go index df44b60570e..8fb21d16910 100644 --- a/pkg/services/authn/clients/ext_jwt.go +++ b/pkg/services/authn/clients/ext_jwt.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/services/oauthserver" "github.com/grafana/grafana/pkg/services/signingkeys" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" @@ -30,12 +31,13 @@ const ( rfc9068MediaType = "application/at+jwt" ) -func ProvideExtendedJWT(userService user.Service, cfg *setting.Cfg, signingKeys signingkeys.Service) *ExtendedJWT { +func ProvideExtendedJWT(userService user.Service, cfg *setting.Cfg, signingKeys signingkeys.Service, oauthServer oauthserver.OAuth2Server) *ExtendedJWT { return &ExtendedJWT{ cfg: cfg, log: log.New(authn.ClientExtendedJWT), userService: userService, signingKeys: signingKeys, + oauthServer: oauthServer, } } @@ -44,6 +46,7 @@ type ExtendedJWT struct { log log.Logger userService user.Service signingKeys signingkeys.Service + oauthServer oauthserver.OAuth2Server } type ExtendedJWTClaims struct { @@ -211,10 +214,9 @@ func (s *ExtendedJWT) validateClientIdClaim(ctx context.Context, claims Extended return fmt.Errorf("missing 'client_id' claim") } - // TODO: Implement the validation for client_id when the OAuth server is ready. - // if _, err := s.oauthService.GetExternalService(ctx, clientId); err != nil { - // return fmt.Errorf("invalid 'client_id' claim: %s", clientIdClaim) - // } + if _, err := s.oauthServer.GetExternalService(ctx, claims.ClientID); err != nil { + return fmt.Errorf("invalid 'client_id' claim: %s", claims.ClientID) + } return nil } diff --git a/pkg/services/authn/clients/ext_jwt_test.go b/pkg/services/authn/clients/ext_jwt_test.go index 480d32d53f3..d88ead74738 100644 --- a/pkg/services/authn/clients/ext_jwt_test.go +++ b/pkg/services/authn/clients/ext_jwt_test.go @@ -15,6 +15,8 @@ import ( "github.com/grafana/grafana/pkg/models/roletype" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" + "github.com/grafana/grafana/pkg/services/oauthserver" + "github.com/grafana/grafana/pkg/services/oauthserver/oastest" "github.com/grafana/grafana/pkg/services/signingkeys/signingkeystest" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user/usertest" @@ -49,7 +51,7 @@ var ( pk, _ = rsa.GenerateKey(rand.Reader, 4096) ) -func TestExtendedJWTTest(t *testing.T) { +func TestExtendedJWT_Test(t *testing.T) { type testCase struct { name string cfg *setting.Cfg @@ -105,7 +107,7 @@ func TestExtendedJWTTest(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - extJwtClient := setupTestCtx(t, nil, tc.cfg) + env := setupTestCtx(t, tc.cfg) validHTTPReq := &http.Request{ Header: map[string][]string{ @@ -113,7 +115,7 @@ func TestExtendedJWTTest(t *testing.T) { }, } - actual := extJwtClient.Test(context.Background(), &authn.Request{ + actual := env.s.Test(context.Background(), &authn.Request{ HTTPRequest: validHTTPReq, Resp: nil, }) @@ -123,22 +125,22 @@ func TestExtendedJWTTest(t *testing.T) { } } -func TestExtendedJWTAuthenticate(t *testing.T) { +func TestExtendedJWT_Authenticate(t *testing.T) { type testCase struct { - name string - payload ExtendedJWTClaims - orgID int64 - want *authn.Identity - userSvcSetup func(userSvc *usertest.FakeUserService) - wantErr bool + name string + payload ExtendedJWTClaims + orgID int64 + want *authn.Identity + initTestEnv func(env *testEnv) + wantErr bool } testCases := []testCase{ { name: "successful authentication", payload: validPayload, orgID: 1, - userSvcSetup: func(userSvc *usertest.FakeUserService) { - userSvc.ExpectedSignedInUser = &user.SignedInUser{ + initTestEnv: func(env *testEnv) { + env.userSvc.ExpectedSignedInUser = &user.SignedInUser{ UserID: 2, OrgID: 1, OrgRole: roletype.RoleAdmin, @@ -242,8 +244,8 @@ func TestExtendedJWTAuthenticate(t *testing.T) { }, orgID: 1, want: nil, - userSvcSetup: func(userSvc *usertest.FakeUserService) { - userSvc.ExpectedError = user.ErrUserNotFound + initTestEnv: func(env *testEnv) { + env.userSvc.ExpectedError = user.ErrUserNotFound }, wantErr: true, }, @@ -265,33 +267,34 @@ func TestExtendedJWTAuthenticate(t *testing.T) { want: nil, wantErr: true, }, - // { - // name: "should return error when the entitlements are not in the correct format", - // payload: ExtendedJWTClaims{ - // Claims: jwt.Claims{ - // Issuer: "http://localhost:3000", - // Subject: "user:id:2", - // Audience: jwt.Audience{"http://localhost:3000"}, - // ID: "1234567890", - // Expiry: jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)), - // IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)), - // }, - // ClientID: "grafana", - // Scopes: []string{"profile", "groups"}, - // Entitlements: []string{"dashboards:create", "folders:read"}, - // }, - // orgID: 1, - // want: nil, - // wantErr: true, - // }, + { + name: "should return error when the client was not found", + payload: ExtendedJWTClaims{ + Claims: jwt.Claims{ + Issuer: "http://localhost:3000", + Subject: "user:id:2", + Audience: jwt.Audience{"http://localhost:3000"}, + ID: "1234567890", + Expiry: jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)), + IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)), + }, + ClientID: "unknown-client-id", + Scopes: []string{"profile", "groups"}, + }, + initTestEnv: func(env *testEnv) { + env.oauthSvc.ExpectedErr = oauthserver.ErrClientNotFound("unknown-client-id") + }, + orgID: 1, + want: nil, + wantErr: true, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - userSvc := &usertest.FakeUserService{} - extJwtClient := setupTestCtx(t, userSvc, nil) - if tc.userSvcSetup != nil { - tc.userSvcSetup(userSvc) + env := setupTestCtx(t, nil) + if tc.initTestEnv != nil { + tc.initTestEnv(env) } validHTTPReq := &http.Request{ @@ -302,7 +305,7 @@ func TestExtendedJWTAuthenticate(t *testing.T) { mockTimeNow(time.Date(2023, 5, 2, 0, 1, 0, 0, time.UTC)) - id, err := extJwtClient.Authenticate(context.Background(), &authn.Request{ + id, err := env.s.Authenticate(context.Background(), &authn.Request{ OrgID: tc.orgID, HTTPRequest: validHTTPReq, Resp: nil, @@ -487,7 +490,7 @@ func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) { }, } - extJwtClient := setupTestCtx(t, nil, nil) + env := setupTestCtx(t, nil) mockTimeNow(time.Date(2023, 5, 2, 0, 1, 0, 0, time.UTC)) for _, tc := range testCases { @@ -496,13 +499,13 @@ func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) { tc.alg = jose.RS256 } tokenToTest := generateToken(tc.payload, pk, tc.alg) - _, err := extJwtClient.verifyRFC9068Token(context.Background(), tokenToTest) + _, err := env.s.verifyRFC9068Token(context.Background(), tokenToTest) require.Error(t, err) }) } } -func setupTestCtx(t *testing.T, userSvc user.Service, cfg *setting.Cfg) *ExtendedJWT { +func setupTestCtx(t *testing.T, cfg *setting.Cfg) *testEnv { if cfg == nil { cfg = &setting.Cfg{ ExtendedJWTAuthEnabled: true, @@ -514,8 +517,22 @@ func setupTestCtx(t *testing.T, userSvc user.Service, cfg *setting.Cfg) *Extende signingKeysSvc := &signingkeystest.FakeSigningKeysService{} signingKeysSvc.ExpectedServerPublicKey = &pk.PublicKey - extJwtClient := ProvideExtendedJWT(userSvc, cfg, signingKeysSvc) - return extJwtClient + userSvc := &usertest.FakeUserService{} + oauthSvc := &oastest.FakeService{} + + extJwtClient := ProvideExtendedJWT(userSvc, cfg, signingKeysSvc, oauthSvc) + + return &testEnv{ + oauthSvc: oauthSvc, + userSvc: userSvc, + s: extJwtClient, + } +} + +type testEnv struct { + oauthSvc *oastest.FakeService + userSvc *usertest.FakeUserService + s *ExtendedJWT } func generateToken(payload ExtendedJWTClaims, signingKey interface{}, alg jose.SignatureAlgorithm) string { diff --git a/pkg/services/oauthserver/api/api.go b/pkg/services/oauthserver/api/api.go new file mode 100644 index 00000000000..0875367bec3 --- /dev/null +++ b/pkg/services/oauthserver/api/api.go @@ -0,0 +1,37 @@ +package api + +import ( + "github.com/grafana/grafana/pkg/api/routing" + contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + "github.com/grafana/grafana/pkg/services/oauthserver" +) + +type api struct { + router routing.RouteRegister + oauthServer oauthserver.OAuth2Server +} + +func NewAPI( + router routing.RouteRegister, + oauthServer oauthserver.OAuth2Server, +) *api { + return &api{ + router: router, + oauthServer: oauthServer, + } +} + +func (a *api) RegisterAPIEndpoints() { + a.router.Group("/oauth2", func(oauthRouter routing.RouteRegister) { + oauthRouter.Post("/introspect", a.handleIntrospectionRequest) + oauthRouter.Post("/token", a.handleTokenRequest) + }) +} + +func (a *api) handleTokenRequest(c *contextmodel.ReqContext) { + a.oauthServer.HandleTokenRequest(c.Resp, c.Req) +} + +func (a *api) handleIntrospectionRequest(c *contextmodel.ReqContext) { + a.oauthServer.HandleIntrospectionRequest(c.Resp, c.Req) +} diff --git a/pkg/services/oauthserver/errors.go b/pkg/services/oauthserver/errors.go new file mode 100644 index 00000000000..b7a1b68153a --- /dev/null +++ b/pkg/services/oauthserver/errors.go @@ -0,0 +1,27 @@ +package oauthserver + +import ( + "fmt" + + "github.com/grafana/grafana/pkg/util/errutil" +) + +var ( + ErrClientNotFoundMessageID = "oauthserver.client-not-found" +) + +var ( + ErrClientRequiredID = errutil.NewBase(errutil.StatusBadRequest, + "oauthserver.required-client-id", + errutil.WithPublicMessage("client ID is required")).Errorf("Client ID is required") + ErrClientRequiredName = errutil.NewBase(errutil.StatusBadRequest, + "oauthserver.required-client-name", + errutil.WithPublicMessage("client name is required")).Errorf("Client name is required") +) + +func ErrClientNotFound(clientID string) error { + return errutil.NewBase(errutil.StatusNotFound, + ErrClientNotFoundMessageID, + errutil.WithPublicMessage(fmt.Sprintf("Client '%s' not found", clientID))). + Errorf("client '%s' not found", clientID) +} diff --git a/pkg/services/oauthserver/external_service.go b/pkg/services/oauthserver/external_service.go new file mode 100644 index 00000000000..8d0be07372d --- /dev/null +++ b/pkg/services/oauthserver/external_service.go @@ -0,0 +1,161 @@ +package oauthserver + +import ( + "context" + "strconv" + "strings" + + "github.com/ory/fosite" + + ac "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/user" +) + +type KeyResult struct { + URL string `json:"url,omitempty"` + PrivatePem string `json:"private,omitempty"` + PublicPem string `json:"public,omitempty"` + Generated bool `json:"generated,omitempty"` +} + +type ExternalServiceDTO struct { + Name string `json:"name"` + ID string `json:"clientId"` + Secret string `json:"clientSecret"` + RedirectURI string `json:"redirectUri,omitempty"` // Not used yet (code flow) + GrantTypes string `json:"grantTypes"` // CSV value + Audiences string `json:"audiences"` // CSV value + KeyResult *KeyResult `json:"key,omitempty"` +} + +type ExternalService struct { + ID int64 `xorm:"id pk autoincr"` + Name string `xorm:"name"` + ClientID string `xorm:"client_id"` + Secret string `xorm:"secret"` + RedirectURI string `xorm:"redirect_uri"` // Not used yet (code flow) + GrantTypes string `xorm:"grant_types"` // CSV value + Audiences string `xorm:"audiences"` // CSV value + PublicPem []byte `xorm:"public_pem"` + ServiceAccountID int64 `xorm:"service_account_id"` + // SelfPermissions are the registered service account permissions (registered and managed permissions) + SelfPermissions []ac.Permission + // ImpersonatePermissions is the restriction set of permissions while impersonating + ImpersonatePermissions []ac.Permission + + // SignedInUser refers to the current Service Account identity/user + SignedInUser *user.SignedInUser + Scopes []string + ImpersonateScopes []string +} + +func (c *ExternalService) ToDTO() *ExternalServiceDTO { + c2 := ExternalServiceDTO{ + Name: c.Name, + ID: c.ClientID, + Secret: c.Secret, + GrantTypes: c.GrantTypes, + Audiences: c.Audiences, + RedirectURI: c.RedirectURI, + } + if len(c.PublicPem) > 0 { + c2.KeyResult = &KeyResult{PublicPem: string(c.PublicPem)} + } + return &c2 +} + +func (c *ExternalService) LogID() string { + return "{name: " + c.Name + ", clientID: " + c.ClientID + "}" +} + +// GetID returns the client ID. +func (c *ExternalService) GetID() string { return c.ClientID } + +// GetHashedSecret returns the hashed secret as it is stored in the store. +func (c *ExternalService) GetHashedSecret() []byte { + // Hashed version is stored in the secret field + return []byte(c.Secret) +} + +// GetRedirectURIs returns the client's allowed redirect URIs. +func (c *ExternalService) GetRedirectURIs() []string { + return []string{c.RedirectURI} +} + +// GetGrantTypes returns the client's allowed grant types. +func (c *ExternalService) GetGrantTypes() fosite.Arguments { + return strings.Split(c.GrantTypes, ",") +} + +// GetResponseTypes returns the client's allowed response types. +// All allowed combinations of response types have to be listed, each combination having +// response types of the combination separated by a space. +func (c *ExternalService) GetResponseTypes() fosite.Arguments { + return fosite.Arguments{"code"} +} + +// GetScopes returns the scopes this client is allowed to request on its own behalf. +func (c *ExternalService) GetScopes() fosite.Arguments { + if c.Scopes != nil { + return c.Scopes + } + + ret := []string{"profile", "email", "groups", "entitlements"} + if c.SignedInUser != nil && c.SignedInUser.Permissions != nil { + perms := c.SignedInUser.Permissions[TmpOrgID] + for action := range perms { + // Add all actions that the plugin is allowed to request + ret = append(ret, action) + } + } + + c.Scopes = ret + return ret +} + +// GetScopes returns the scopes this client is allowed to request on a specific user. +func (c *ExternalService) GetScopesOnUser(ctx context.Context, accessControl ac.AccessControl, userID int64) []string { + ev := ac.EvalPermission(ac.ActionUsersImpersonate, ac.Scope("users", "id", strconv.FormatInt(userID, 10))) + hasAccess, errAccess := accessControl.Evaluate(ctx, c.SignedInUser, ev) + if errAccess != nil || !hasAccess { + return nil + } + + if c.ImpersonateScopes != nil { + return c.ImpersonateScopes + } + + ret := []string{} + if c.ImpersonatePermissions != nil { + perms := c.ImpersonatePermissions + for i := range perms { + if perms[i].Action == ac.ActionUsersRead && perms[i].Scope == ScopeGlobalUsersSelf { + ret = append(ret, "profile", "email", ac.ActionUsersRead) + continue + } + if perms[i].Action == ac.ActionUsersPermissionsRead && perms[i].Scope == ScopeUsersSelf { + ret = append(ret, "entitlements", ac.ActionUsersPermissionsRead) + continue + } + if perms[i].Action == ac.ActionTeamsRead && perms[i].Scope == ScopeTeamsSelf { + ret = append(ret, "groups", ac.ActionTeamsRead) + continue + } + // Add all actions that the plugin is allowed to request + ret = append(ret, perms[i].Action) + } + } + + c.ImpersonateScopes = ret + return ret +} + +// IsPublic returns true, if this client is marked as public. +func (c *ExternalService) IsPublic() bool { + return false +} + +// GetAudience returns the allowed audience(s) for this client. +func (c *ExternalService) GetAudience() fosite.Arguments { + return strings.Split(c.Audiences, ",") +} diff --git a/pkg/services/oauthserver/external_service_test.go b/pkg/services/oauthserver/external_service_test.go new file mode 100644 index 00000000000..ab5e562b59e --- /dev/null +++ b/pkg/services/oauthserver/external_service_test.go @@ -0,0 +1,210 @@ +package oauthserver + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + ac "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" + "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/setting" +) + +func setupTestEnv(t *testing.T) *ExternalService { + t.Helper() + + client := &ExternalService{ + Name: "my-ext-service", + ClientID: "RANDOMID", + Secret: "RANDOMSECRET", + GrantTypes: "client_credentials,urn:ietf:params:oauth:grant-type:jwt-bearer", + ServiceAccountID: 2, + SelfPermissions: []ac.Permission{ + {Action: ac.ActionUsersImpersonate, Scope: ac.ScopeUsersAll}, + }, + SignedInUser: &user.SignedInUser{ + UserID: 2, + OrgID: 1, + }, + } + return client +} + +func TestExternalService_GetScopesOnUser(t *testing.T) { + testCases := []struct { + name string + impersonatePermissions []ac.Permission + initTestEnv func(*ExternalService) + expectedScopes []string + }{ + { + name: "should return nil when the service account has no impersonate permissions", + expectedScopes: nil, + }, + { + name: "should return the 'profile', 'email' and associated RBAC action", + initTestEnv: func(c *ExternalService) { + c.SignedInUser.Permissions = map[int64]map[string][]string{ + 1: { + ac.ActionUsersImpersonate: {ac.ScopeUsersAll}, + }, + } + c.ImpersonatePermissions = []ac.Permission{ + {Action: ac.ActionUsersRead, Scope: ScopeGlobalUsersSelf}, + } + }, + expectedScopes: []string{"profile", "email", ac.ActionUsersRead}, + }, + { + name: "should return 'entitlements' and associated RBAC action scopes", + initTestEnv: func(c *ExternalService) { + c.SignedInUser.Permissions = map[int64]map[string][]string{ + 1: { + ac.ActionUsersImpersonate: {ac.ScopeUsersAll}, + }, + } + c.ImpersonatePermissions = []ac.Permission{ + {Action: ac.ActionUsersPermissionsRead, Scope: ScopeUsersSelf}, + } + }, + expectedScopes: []string{"entitlements", ac.ActionUsersPermissionsRead}, + }, + { + name: "should return 'groups' and associated RBAC action scopes", + initTestEnv: func(c *ExternalService) { + c.SignedInUser.Permissions = map[int64]map[string][]string{ + 1: { + ac.ActionUsersImpersonate: {ac.ScopeUsersAll}, + }, + } + c.ImpersonatePermissions = []ac.Permission{ + {Action: ac.ActionTeamsRead, Scope: ScopeTeamsSelf}, + } + }, + expectedScopes: []string{"groups", ac.ActionTeamsRead}, + }, + { + name: "should return all scopes", + initTestEnv: func(c *ExternalService) { + c.SignedInUser.Permissions = map[int64]map[string][]string{ + 1: { + ac.ActionUsersImpersonate: {ac.ScopeUsersAll}, + }, + } + c.ImpersonatePermissions = []ac.Permission{ + {Action: ac.ActionUsersRead, Scope: ScopeGlobalUsersSelf}, + {Action: ac.ActionUsersPermissionsRead, Scope: ScopeUsersSelf}, + {Action: ac.ActionTeamsRead, Scope: ScopeTeamsSelf}, + {Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeDashboardsAll}, + } + }, + expectedScopes: []string{"profile", "email", ac.ActionUsersRead, + "entitlements", ac.ActionUsersPermissionsRead, + "groups", ac.ActionTeamsRead, + "dashboards:read"}, + }, + { + name: "should return stored scopes when the client's impersonate scopes has already been set", + initTestEnv: func(c *ExternalService) { + c.SignedInUser.Permissions = map[int64]map[string][]string{ + 1: { + ac.ActionUsersImpersonate: {ac.ScopeUsersAll}, + }, + } + c.ImpersonateScopes = []string{"dashboard:create", "profile", "email", "entitlements", "groups"} + }, + expectedScopes: []string{"profile", "email", "entitlements", "groups", "dashboard:create"}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + c := setupTestEnv(t) + if tc.initTestEnv != nil { + tc.initTestEnv(c) + } + scopes := c.GetScopesOnUser(context.Background(), acimpl.ProvideAccessControl(setting.NewCfg()), 3) + require.ElementsMatch(t, tc.expectedScopes, scopes) + }) + } +} + +func TestExternalService_GetScopes(t *testing.T) { + testCases := []struct { + name string + impersonatePermissions []ac.Permission + initTestEnv func(*ExternalService) + expectedScopes []string + }{ + { + name: "should return default scopes when the signed in user is nil", + initTestEnv: func(c *ExternalService) { + c.SignedInUser = nil + }, + expectedScopes: []string{"profile", "email", "entitlements", "groups"}, + }, + { + name: "should return default scopes when the signed in user has no permissions", + initTestEnv: func(c *ExternalService) { + c.SignedInUser.Permissions = map[int64]map[string][]string{} + }, + expectedScopes: []string{"profile", "email", "entitlements", "groups"}, + }, + { + name: "should return additional scopes from signed in user's permissions", + initTestEnv: func(c *ExternalService) { + c.SignedInUser.Permissions = map[int64]map[string][]string{ + 1: { + dashboards.ActionDashboardsRead: {dashboards.ScopeDashboardsAll}, + }, + } + }, + expectedScopes: []string{"profile", "email", "entitlements", "groups", "dashboards:read"}, + }, + { + name: "should return stored scopes when the client's scopes has already been set", + initTestEnv: func(c *ExternalService) { + c.Scopes = []string{"profile", "email", "entitlements", "groups"} + }, + expectedScopes: []string{"profile", "email", "entitlements", "groups"}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + c := setupTestEnv(t) + if tc.initTestEnv != nil { + tc.initTestEnv(c) + } + scopes := c.GetScopes() + require.ElementsMatch(t, tc.expectedScopes, scopes) + }) + } +} + +func TestExternalService_ToDTO(t *testing.T) { + client := &ExternalService{ + ID: 1, + Name: "my-ext-service", + ClientID: "test", + Secret: "testsecret", + RedirectURI: "http://localhost:3000", + GrantTypes: "client_credentials,urn:ietf:params:oauth:grant-type:jwt-bearer", + Audiences: "https://example.org,https://second.example.org", + PublicPem: []byte("pem_encoded_public_key"), + } + + dto := client.ToDTO() + + require.Equal(t, client.ClientID, dto.ID) + require.Equal(t, client.Name, dto.Name) + require.Equal(t, client.RedirectURI, dto.RedirectURI) + require.Equal(t, client.GrantTypes, dto.GrantTypes) + require.Equal(t, client.Audiences, dto.Audiences) + require.Equal(t, client.PublicPem, []byte(dto.KeyResult.PublicPem)) + require.Empty(t, dto.KeyResult.PrivatePem) + require.Empty(t, dto.KeyResult.URL) + require.False(t, dto.KeyResult.Generated) + require.Equal(t, client.Secret, dto.Secret) +} diff --git a/pkg/services/oauthserver/models.go b/pkg/services/oauthserver/models.go new file mode 100644 index 00000000000..7eee4c57920 --- /dev/null +++ b/pkg/services/oauthserver/models.go @@ -0,0 +1,91 @@ +package oauthserver + +import ( + "context" + "net/http" + + "github.com/grafana/grafana/pkg/services/accesscontrol" + "gopkg.in/square/go-jose.v2" +) + +const ( + // TmpOrgID is the orgID we use while global service accounts are not supported. + TmpOrgID int64 = 1 + // NoServiceAccountID is the ID we use for client that have no service account associated. + NoServiceAccountID int64 = 0 + + // List of scopes used to identify the impersonated user. + ScopeUsersSelf = "users:self" + ScopeGlobalUsersSelf = "global.users:self" + ScopeTeamsSelf = "teams:self" + + // Supported encryptions + RS256 = "RS256" + ES256 = "ES256" +) + +// OAuth2Server represents a service in charge of managing OAuth2 clients +// and handling OAuth2 requests (token, introspection). +type OAuth2Server interface { + // SaveExternalService creates or updates an external service in the database, it generates client_id and secrets and + // it ensures that the associated service account has the correct permissions. + SaveExternalService(ctx context.Context, cmd *ExternalServiceRegistration) (*ExternalServiceDTO, error) + // GetExternalService retrieves an external service from store by client_id. It populates the SelfPermissions and + // SignedInUser from the associated service account. + GetExternalService(ctx context.Context, id string) (*ExternalService, error) + + // HandleTokenRequest handles the client's OAuth2 query to obtain an access_token by presenting its authorization + // grant (ex: client_credentials, jwtbearer). + HandleTokenRequest(rw http.ResponseWriter, req *http.Request) + // HandleIntrospectionRequest handles the OAuth2 query to determine the active state of an OAuth 2.0 token and + // to determine meta-information about this token. + HandleIntrospectionRequest(rw http.ResponseWriter, req *http.Request) +} + +//go:generate mockery --name Store --structname MockStore --outpkg oauthtest --filename store_mock.go --output ./oauthtest/ + +type Store interface { + RegisterExternalService(ctx context.Context, client *ExternalService) error + SaveExternalService(ctx context.Context, client *ExternalService) error + GetExternalService(ctx context.Context, id string) (*ExternalService, error) + GetExternalServiceByName(ctx context.Context, name string) (*ExternalService, error) + GetExternalServicePublicKey(ctx context.Context, clientID string) (*jose.JSONWebKey, error) +} + +type KeyOption struct { + // URL string `json:"url,omitempty"` // TODO allow specifying a URL (to a .jwks file) to fetch the key from + // PublicPEM contains the Base64 encoded public key in PEM format + PublicPEM string `json:"public_pem,omitempty"` + Generate bool `json:"generate,omitempty"` +} + +type SelfCfg struct { + // Enabled allows the service to request access tokens for itself using the client_credentials grant + Enabled bool `json:"enabled"` + // Permissions are the permissions that the external service needs its associated service account to have. + Permissions []accesscontrol.Permission `json:"permissions,omitempty"` +} +type ImpersonationCfg struct { + // Enabled allows the service to request access tokens to impersonate users using the jwtbearer grant + Enabled bool `json:"enabled"` + // Groups allows the service to list the impersonated user's teams + Groups bool `json:"groups"` + // Permissions are the permissions that the external service needs when impersonating a user. + // The intersection of this set with the impersonated user's permission guarantees that the client will not + // gain more privileges than the impersonated user has. + Permissions []accesscontrol.Permission `json:"permissions,omitempty"` +} + +// ExternalServiceRegistration represents the registration form to save new OAuth2 client. +type ExternalServiceRegistration struct { + Name string `json:"name"` + // RedirectURI is the URI that is used in the code flow. + // Note that this is not used yet. + RedirectURI *string `json:"redirectUri,omitempty"` + // Impersonation access configuration + Impersonation ImpersonationCfg `json:"impersonation"` + // Self access configuration + Self SelfCfg `json:"self"` + // Key is the option to specify a public key or ask the server to generate a crypto key pair. + Key *KeyOption `json:"key,omitempty"` +} diff --git a/pkg/services/oauthserver/oasimpl/aggregate_store.go b/pkg/services/oauthserver/oasimpl/aggregate_store.go new file mode 100644 index 00000000000..1c503fd4ece --- /dev/null +++ b/pkg/services/oauthserver/oasimpl/aggregate_store.go @@ -0,0 +1,162 @@ +package oasimpl + +import ( + "context" + "time" + + "github.com/ory/fosite" + "github.com/ory/fosite/handler/oauth2" + "github.com/ory/fosite/handler/rfc7523" + "gopkg.in/square/go-jose.v2" + + "github.com/grafana/grafana/pkg/services/oauthserver/utils" +) + +var _ fosite.ClientManager = &OAuth2ServiceImpl{} +var _ oauth2.AuthorizeCodeStorage = &OAuth2ServiceImpl{} +var _ oauth2.AccessTokenStorage = &OAuth2ServiceImpl{} +var _ oauth2.RefreshTokenStorage = &OAuth2ServiceImpl{} +var _ rfc7523.RFC7523KeyStorage = &OAuth2ServiceImpl{} +var _ oauth2.TokenRevocationStorage = &OAuth2ServiceImpl{} + +// GetClient loads the client by its ID or returns an error +// if the client does not exist or another error occurred. +func (s *OAuth2ServiceImpl) GetClient(ctx context.Context, id string) (fosite.Client, error) { + return s.GetExternalService(ctx, id) +} + +// ClientAssertionJWTValid returns an error if the JTI is +// known or the DB check failed and nil if the JTI is not known. +func (s *OAuth2ServiceImpl) ClientAssertionJWTValid(ctx context.Context, jti string) error { + return s.memstore.ClientAssertionJWTValid(ctx, jti) +} + +// SetClientAssertionJWT marks a JTI as known for the given +// expiry time. Before inserting the new JTI, it will clean +// up any existing JTIs that have expired as those tokens can +// not be replayed due to the expiry. +func (s *OAuth2ServiceImpl) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) error { + return s.memstore.SetClientAssertionJWT(ctx, jti, exp) +} + +// GetAuthorizeCodeSession stores the authorization request for a given authorization code. +func (s *OAuth2ServiceImpl) CreateAuthorizeCodeSession(ctx context.Context, code string, request fosite.Requester) (err error) { + return s.memstore.CreateAuthorizeCodeSession(ctx, code, request) +} + +// GetAuthorizeCodeSession hydrates the session based on the given code and returns the authorization request. +// If the authorization code has been invalidated with `InvalidateAuthorizeCodeSession`, this +// method should return the ErrInvalidatedAuthorizeCode error. +// +// Make sure to also return the fosite.Requester value when returning the fosite.ErrInvalidatedAuthorizeCode error! +func (s *OAuth2ServiceImpl) GetAuthorizeCodeSession(ctx context.Context, code string, session fosite.Session) (request fosite.Requester, err error) { + return s.memstore.GetAuthorizeCodeSession(ctx, code, session) +} + +// InvalidateAuthorizeCodeSession is called when an authorize code is being used. The state of the authorization +// code should be set to invalid and consecutive requests to GetAuthorizeCodeSession should return the +// ErrInvalidatedAuthorizeCode error. +func (s *OAuth2ServiceImpl) InvalidateAuthorizeCodeSession(ctx context.Context, code string) (err error) { + return s.memstore.InvalidateAuthorizeCodeSession(ctx, code) +} + +func (s *OAuth2ServiceImpl) CreateAccessTokenSession(ctx context.Context, signature string, request fosite.Requester) (err error) { + return s.memstore.CreateAccessTokenSession(ctx, signature, request) +} + +func (s *OAuth2ServiceImpl) GetAccessTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) { + return s.memstore.GetAccessTokenSession(ctx, signature, session) +} + +func (s *OAuth2ServiceImpl) DeleteAccessTokenSession(ctx context.Context, signature string) (err error) { + return s.memstore.DeleteAccessTokenSession(ctx, signature) +} + +func (s *OAuth2ServiceImpl) CreateRefreshTokenSession(ctx context.Context, signature string, request fosite.Requester) (err error) { + return s.memstore.CreateRefreshTokenSession(ctx, signature, request) +} + +func (s *OAuth2ServiceImpl) GetRefreshTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) { + return s.memstore.GetRefreshTokenSession(ctx, signature, session) +} + +func (s *OAuth2ServiceImpl) DeleteRefreshTokenSession(ctx context.Context, signature string) (err error) { + return s.memstore.DeleteRefreshTokenSession(ctx, signature) +} + +// RevokeRefreshToken revokes a refresh token as specified in: +// https://tools.ietf.org/html/rfc7009#section-2.1 +// If the particular +// token is a refresh token and the authorization server supports the +// revocation of access tokens, then the authorization server SHOULD +// also invalidate all access tokens based on the same authorization +// grant (see Implementation Note). +func (s *OAuth2ServiceImpl) RevokeRefreshToken(ctx context.Context, requestID string) error { + return s.memstore.RevokeRefreshToken(ctx, requestID) +} + +// RevokeRefreshTokenMaybeGracePeriod revokes a refresh token as specified in: +// https://tools.ietf.org/html/rfc7009#section-2.1 +// If the particular +// token is a refresh token and the authorization server supports the +// revocation of access tokens, then the authorization server SHOULD +// also invalidate all access tokens based on the same authorization +// grant (see Implementation Note). +// +// If the Refresh Token grace period is greater than zero in configuration the token +// will have its expiration time set as UTCNow + GracePeriod. +func (s *OAuth2ServiceImpl) RevokeRefreshTokenMaybeGracePeriod(ctx context.Context, requestID string, signature string) error { + return s.memstore.RevokeRefreshTokenMaybeGracePeriod(ctx, requestID, signature) +} + +// RevokeAccessToken revokes an access token as specified in: +// https://tools.ietf.org/html/rfc7009#section-2.1 +// If the token passed to the request +// is an access token, the server MAY revoke the respective refresh +// token as well. +func (s *OAuth2ServiceImpl) RevokeAccessToken(ctx context.Context, requestID string) error { + return s.memstore.RevokeAccessToken(ctx, requestID) +} + +// GetPublicKey returns public key, issued by 'issuer', and assigned for subject. Public key is used to check +// signature of jwt assertion in authorization grants. +func (s *OAuth2ServiceImpl) GetPublicKey(ctx context.Context, issuer string, subject string, kid string) (*jose.JSONWebKey, error) { + return s.sqlstore.GetExternalServicePublicKey(ctx, issuer) +} + +// GetPublicKeys returns public key, set issued by 'issuer', and assigned for subject. +func (s *OAuth2ServiceImpl) GetPublicKeys(ctx context.Context, issuer string, subject string) (*jose.JSONWebKeySet, error) { + jwk, err := s.sqlstore.GetExternalServicePublicKey(ctx, issuer) + if err != nil { + return nil, err + } + return &jose.JSONWebKeySet{ + Keys: []jose.JSONWebKey{*jwk}, + }, nil +} + +// GetPublicKeyScopes returns assigned scope for assertion, identified by public key, issued by 'issuer'. +func (s *OAuth2ServiceImpl) GetPublicKeyScopes(ctx context.Context, issuer string, subject string, kid string) ([]string, error) { + client, err := s.GetExternalService(ctx, issuer) + if err != nil { + return nil, err + } + userID, err := utils.ParseUserIDFromSubject(subject) + if err != nil { + return nil, err + } + return client.GetScopesOnUser(ctx, s.accessControl, userID), nil +} + +// IsJWTUsed returns true, if JWT is not known yet or it can not be considered valid, because it must be already +// expired. +func (s *OAuth2ServiceImpl) IsJWTUsed(ctx context.Context, jti string) (bool, error) { + return s.memstore.IsJWTUsed(ctx, jti) +} + +// MarkJWTUsedForTime marks JWT as used for a time passed in exp parameter. This helps ensure that JWTs are not +// replayed by maintaining the set of used "jti" values for the length of time for which the JWT would be +// considered valid based on the applicable "exp" instant. (https://tools.ietf.org/html/rfc7523#section-3) +func (s *OAuth2ServiceImpl) MarkJWTUsedForTime(ctx context.Context, jti string, exp time.Time) error { + return s.memstore.MarkJWTUsedForTime(ctx, jti, exp) +} diff --git a/pkg/services/oauthserver/oasimpl/aggregate_store_test.go b/pkg/services/oauthserver/oasimpl/aggregate_store_test.go new file mode 100644 index 00000000000..955efc402ce --- /dev/null +++ b/pkg/services/oauthserver/oasimpl/aggregate_store_test.go @@ -0,0 +1,119 @@ +package oasimpl + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + ac "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/oauthserver" + "github.com/grafana/grafana/pkg/services/user" +) + +var cachedExternalService = func() *oauthserver.ExternalService { + return &oauthserver.ExternalService{ + Name: "my-ext-service", + ClientID: "RANDOMID", + Secret: "RANDOMSECRET", + GrantTypes: "client_credentials", + PublicPem: []byte("-----BEGIN PUBLIC KEY-----"), + ServiceAccountID: 1, + SelfPermissions: []ac.Permission{{Action: "users:impersonate", Scope: "users:*"}}, + SignedInUser: &user.SignedInUser{ + UserID: 2, + OrgID: 1, + Permissions: map[int64]map[string][]string{ + 1: { + "users:impersonate": {"users:*"}, + }, + }, + }, + } +} + +func TestOAuth2ServiceImpl_GetPublicKeyScopes(t *testing.T) { + testCases := []struct { + name string + initTestEnv func(*TestEnv) + impersonatePermissions []ac.Permission + userID string + expectedScopes []string + wantErr bool + }{ + { + name: "should error out when GetExternalService returns error", + initTestEnv: func(env *TestEnv) { + env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(nil, oauthserver.ErrClientNotFound("my-ext-service")) + }, + wantErr: true, + }, + { + name: "should error out when the user id cannot be parsed", + initTestEnv: func(env *TestEnv) { + env.S.cache.Set("my-ext-service", *cachedExternalService(), time.Minute) + }, + userID: "user:3", + wantErr: true, + }, + { + name: "should return no scope when the external service is not allowed to impersonate the user", + initTestEnv: func(env *TestEnv) { + client := cachedExternalService() + client.SignedInUser.Permissions = map[int64]map[string][]string{} + env.S.cache.Set("my-ext-service", *client, time.Minute) + }, + userID: "user:id:3", + expectedScopes: nil, + wantErr: false, + }, + { + name: "should return no scope when the external service has an no impersonate permission", + initTestEnv: func(env *TestEnv) { + client := cachedExternalService() + client.ImpersonatePermissions = []ac.Permission{} + env.S.cache.Set("my-ext-service", *client, time.Minute) + }, + userID: "user:id:3", + expectedScopes: []string{}, + wantErr: false, + }, + { + name: "should return the scopes when the external service has impersonate permissions", + initTestEnv: func(env *TestEnv) { + env.S.cache.Set("my-ext-service", *cachedExternalService(), time.Minute) + client := cachedExternalService() + client.ImpersonatePermissions = []ac.Permission{ + {Action: ac.ActionUsersImpersonate, Scope: ac.ScopeUsersAll}, + {Action: ac.ActionUsersRead, Scope: oauthserver.ScopeGlobalUsersSelf}, + {Action: ac.ActionUsersPermissionsRead, Scope: oauthserver.ScopeUsersSelf}, + {Action: ac.ActionTeamsRead, Scope: oauthserver.ScopeTeamsSelf}} + env.S.cache.Set("my-ext-service", *client, time.Minute) + }, + userID: "user:id:3", + expectedScopes: []string{"users:impersonate", + "profile", "email", ac.ActionUsersRead, + "entitlements", ac.ActionUsersPermissionsRead, + "groups", ac.ActionTeamsRead}, + wantErr: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + env := setupTestEnv(t) + if tc.initTestEnv != nil { + tc.initTestEnv(env) + } + + scopes, err := env.S.GetPublicKeyScopes(context.Background(), "my-ext-service", tc.userID, "") + if tc.wantErr { + require.Error(t, err) + return + } + + require.EqualValues(t, tc.expectedScopes, scopes) + }) + } +} diff --git a/pkg/services/oauthserver/oasimpl/introspection.go b/pkg/services/oauthserver/oasimpl/introspection.go new file mode 100644 index 00000000000..3182efbf3ac --- /dev/null +++ b/pkg/services/oauthserver/oasimpl/introspection.go @@ -0,0 +1,21 @@ +package oasimpl + +import ( + "log" + "net/http" +) + +// HandleIntrospectionRequest handles the OAuth2 query to determine the active state of an OAuth 2.0 token and +// to determine meta-information about this token +func (s *OAuth2ServiceImpl) HandleIntrospectionRequest(rw http.ResponseWriter, req *http.Request) { + ctx := req.Context() + currentOAuthSessionData := NewAuthSession() + ir, err := s.oauthProvider.NewIntrospectionRequest(ctx, req, currentOAuthSessionData) + if err != nil { + log.Printf("Error occurred in NewIntrospectionRequest: %+v", err) + s.oauthProvider.WriteIntrospectionError(ctx, rw, err) + return + } + + s.oauthProvider.WriteIntrospectionResponse(ctx, rw, ir) +} diff --git a/pkg/services/oauthserver/oasimpl/service.go b/pkg/services/oauthserver/oasimpl/service.go new file mode 100644 index 00000000000..47feab26fc5 --- /dev/null +++ b/pkg/services/oauthserver/oasimpl/service.go @@ -0,0 +1,501 @@ +package oasimpl + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "strings" + "time" + + "github.com/ory/fosite" + "github.com/ory/fosite/compose" + "github.com/ory/fosite/storage" + "github.com/ory/fosite/token/jwt" + "golang.org/x/crypto/bcrypt" + + "github.com/grafana/grafana/pkg/api/routing" + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/localcache" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/slugify" + "github.com/grafana/grafana/pkg/models/roletype" + ac "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/oauthserver" + "github.com/grafana/grafana/pkg/services/oauthserver/api" + "github.com/grafana/grafana/pkg/services/oauthserver/store" + "github.com/grafana/grafana/pkg/services/oauthserver/utils" + "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/secrets/kvstore" + "github.com/grafana/grafana/pkg/services/serviceaccounts" + "github.com/grafana/grafana/pkg/services/signingkeys" + "github.com/grafana/grafana/pkg/services/team" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util/errutil" +) + +const ( + cacheExpirationTime = 5 * time.Minute + cacheCleanupInterval = 5 * time.Minute +) + +type OAuth2ServiceImpl struct { + cache *localcache.CacheService + memstore *storage.MemoryStore + cfg *setting.Cfg + sqlstore oauthserver.Store + oauthProvider fosite.OAuth2Provider + logger log.Logger + accessControl ac.AccessControl + acService ac.Service + saService serviceaccounts.Service + userService user.Service + teamService team.Service + publicKey interface{} +} + +func ProvideService(router routing.RouteRegister, db db.DB, cfg *setting.Cfg, skv kvstore.SecretsKVStore, + svcAccSvc serviceaccounts.Service, accessControl ac.AccessControl, acSvc ac.Service, userSvc user.Service, + teamSvc team.Service, keySvc signingkeys.Service, fmgmt *featuremgmt.FeatureManager) (*OAuth2ServiceImpl, error) { + if !fmgmt.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + return nil, nil + } + config := &fosite.Config{ + AccessTokenLifespan: cfg.OAuth2ServerAccessTokenLifespan, + TokenURL: fmt.Sprintf("%voauth2/token", cfg.AppURL), + AccessTokenIssuer: cfg.AppURL, + IDTokenIssuer: cfg.AppURL, + ScopeStrategy: fosite.WildcardScopeStrategy, + } + + privateKey := keySvc.GetServerPrivateKey() + + var publicKey interface{} + switch k := privateKey.(type) { + case *rsa.PrivateKey: + publicKey = &k.PublicKey + case *ecdsa.PrivateKey: + publicKey = &k.PublicKey + default: + return nil, fmt.Errorf("unknown private key type %T", k) + } + + s := &OAuth2ServiceImpl{ + cache: localcache.New(cacheExpirationTime, cacheCleanupInterval), + cfg: cfg, + accessControl: accessControl, + acService: acSvc, + memstore: storage.NewMemoryStore(), + sqlstore: store.NewStore(db), + logger: log.New("oauthserver"), + userService: userSvc, + saService: svcAccSvc, + teamService: teamSvc, + publicKey: publicKey, + } + + api := api.NewAPI(router, s) + api.RegisterAPIEndpoints() + + s.oauthProvider = newProvider(config, s, privateKey) + + return s, nil +} + +func newProvider(config *fosite.Config, storage interface{}, key interface{}) fosite.OAuth2Provider { + keyGetter := func(context.Context) (interface{}, error) { + return key, nil + } + return compose.Compose( + config, + storage, + &compose.CommonStrategy{ + CoreStrategy: compose.NewOAuth2JWTStrategy(keyGetter, compose.NewOAuth2HMACStrategy(config), config), + Signer: &jwt.DefaultSigner{GetPrivateKey: keyGetter}, + }, + compose.OAuth2ClientCredentialsGrantFactory, + compose.RFC7523AssertionGrantFactory, + + compose.OAuth2TokenIntrospectionFactory, + compose.OAuth2TokenRevocationFactory, + ) +} + +// GetExternalService retrieves an external service from store by client_id. It populates the SelfPermissions and +// SignedInUser from the associated service account. +// For performance reason, the service uses caching. +func (s *OAuth2ServiceImpl) GetExternalService(ctx context.Context, id string) (*oauthserver.ExternalService, error) { + entry, ok := s.cache.Get(id) + if ok { + client, ok := entry.(oauthserver.ExternalService) + if ok { + s.logger.Debug("GetExternalService: cache hit", "id", id) + return &client, nil + } + } + + client, err := s.sqlstore.GetExternalService(ctx, id) + if err != nil { + return nil, err + } + + // Handle the case where the external service has no service account + if client.ServiceAccountID == oauthserver.NoServiceAccountID { + s.logger.Debug("GetExternalService: service has no service account, hence no permission", "id", id, "name", client.Name) + // Create a signed in user with no role and no permissions + client.SignedInUser = &user.SignedInUser{ + UserID: oauthserver.NoServiceAccountID, + OrgID: oauthserver.TmpOrgID, + Name: client.Name, + Permissions: map[int64]map[string][]string{oauthserver.TmpOrgID: {}}, + } + s.cache.Set(id, *client, cacheExpirationTime) + return client, nil + } + + // Retrieve self permissions and generate a signed in user + s.logger.Debug("GetExternalService: fetch permissions", "client id", id) + sa, err := s.saService.RetrieveServiceAccount(ctx, oauthserver.TmpOrgID, client.ServiceAccountID) + if err != nil { + s.logger.Error("GetExternalService: error fetching service account", "id", id, "error", err) + return nil, err + } + client.SignedInUser = &user.SignedInUser{ + UserID: sa.Id, + OrgID: oauthserver.TmpOrgID, + OrgRole: org.RoleType(sa.Role), // Need this to compute the permissions in OSS + Login: sa.Login, + Name: sa.Name, + Permissions: map[int64]map[string][]string{}, + } + client.SelfPermissions, err = s.acService.GetUserPermissions(ctx, client.SignedInUser, ac.Options{}) + if err != nil { + s.logger.Error("GetExternalService: error fetching permissions", "id", id, "error", err) + return nil, err + } + client.SignedInUser.Permissions[oauthserver.TmpOrgID] = ac.GroupScopesByAction(client.SelfPermissions) + + s.cache.Set(id, *client, cacheExpirationTime) + return client, nil +} + +// SaveExternalService creates or updates an external service in the database, it generates client_id and secrets and +// it ensures that the associated service account has the correct permissions. +// Database consistency is not guaranteed, consider changing this in the future. +func (s *OAuth2ServiceImpl) SaveExternalService(ctx context.Context, registration *oauthserver.ExternalServiceRegistration) (*oauthserver.ExternalServiceDTO, error) { + if registration == nil { + s.logger.Warn("RegisterExternalService called without registration") + return nil, nil + } + s.logger.Info("Registering external service", "external service name", registration.Name) + + // Check if the client already exists in store + client, errFetchExtSvc := s.sqlstore.GetExternalServiceByName(ctx, registration.Name) + if errFetchExtSvc != nil { + var srcError errutil.Error + if errors.As(errFetchExtSvc, &srcError) { + if srcError.MessageID != oauthserver.ErrClientNotFoundMessageID { + s.logger.Error("Error fetching service", "external service", registration.Name, "error", errFetchExtSvc) + return nil, errFetchExtSvc + } + } + } + // Otherwise, create a new client + if client == nil { + s.logger.Debug("External service does not yet exist", "external service name", registration.Name) + client = &oauthserver.ExternalService{ + Name: registration.Name, + ServiceAccountID: oauthserver.NoServiceAccountID, + Audiences: s.cfg.AppURL, + } + } + + // Parse registration form to compute required permissions for the client + client.SelfPermissions, client.ImpersonatePermissions = s.handleRegistrationPermissions(registration) + + if registration.RedirectURI != nil { + client.RedirectURI = *registration.RedirectURI + } + + var errGenCred error + client.ClientID, client.Secret, errGenCred = s.genCredentials() + if errGenCred != nil { + s.logger.Error("Error generating credentials", "client", client.LogID(), "error", errGenCred) + return nil, errGenCred + } + + s.logger.Debug("Save service account") + saID, errSaveServiceAccount := s.saveServiceAccount(ctx, client.Name, client.ServiceAccountID, client.SelfPermissions) + if errSaveServiceAccount != nil { + return nil, errSaveServiceAccount + } + client.ServiceAccountID = saID + + grantTypes := s.computeGrantTypes(registration.Self.Enabled, registration.Impersonation.Enabled) + client.GrantTypes = strings.Join(grantTypes, ",") + + // Handle key options + s.logger.Debug("Handle key options") + keys, err := s.handleKeyOptions(ctx, registration.Key) + if err != nil { + s.logger.Error("Error handling key options", "client", client.LogID(), "error", err) + return nil, err + } + if keys != nil { + client.PublicPem = []byte(keys.PublicPem) + } + dto := client.ToDTO() + dto.KeyResult = keys + + hashedSecret, err := bcrypt.GenerateFromPassword([]byte(client.Secret), bcrypt.DefaultCost) + if err != nil { + s.logger.Error("Error hashing secret", "client", client.LogID(), "error", err) + return nil, err + } + client.Secret = string(hashedSecret) + + err = s.sqlstore.SaveExternalService(ctx, client) + if err != nil { + s.logger.Error("Error saving external service", "client", client.LogID(), "error", err) + return nil, err + } + s.logger.Debug("Registered", "client", client.LogID()) + return dto, nil +} + +// randString generates a a cryptographically secure random string of n bytes +func (s *OAuth2ServiceImpl) randString(n int) (string, error) { + res := make([]byte, n) + if _, err := rand.Read(res); err != nil { + return "", err + } + return base64.RawURLEncoding.EncodeToString(res), nil +} + +func (s *OAuth2ServiceImpl) genCredentials() (string, string, error) { + id, err := s.randString(20) + if err != nil { + return "", "", err + } + // client_secret must be at least 32 bytes long + secret, err := s.randString(32) + if err != nil { + return "", "", err + } + return id, secret, err +} + +func (s *OAuth2ServiceImpl) computeGrantTypes(selfAccessEnabled, impersonationEnabled bool) []string { + grantTypes := []string{} + + if selfAccessEnabled { + grantTypes = append(grantTypes, string(fosite.GrantTypeClientCredentials)) + } + + if impersonationEnabled { + grantTypes = append(grantTypes, string(fosite.GrantTypeJWTBearer)) + } + + return grantTypes +} + +func (s *OAuth2ServiceImpl) handleKeyOptions(ctx context.Context, keyOption *oauthserver.KeyOption) (*oauthserver.KeyResult, error) { + if keyOption == nil { + return nil, fmt.Errorf("keyOption is nil") + } + + var publicPem, privatePem string + + if keyOption.Generate { + switch s.cfg.OAuth2ServerGeneratedKeyTypeForClient { + case "RSA": + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + publicPem = string(pem.EncodeToMemory(&pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: x509.MarshalPKCS1PublicKey(&privateKey.PublicKey), + })) + privatePem = string(pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + })) + s.logger.Debug("RSA key has been generated") + default: // default to ECDSA + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + publicDer, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) + if err != nil { + return nil, err + } + + privateDer, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return nil, err + } + + publicPem = string(pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: publicDer, + })) + privatePem = string(pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: privateDer, + })) + s.logger.Debug("ECDSA key has been generated") + } + + return &oauthserver.KeyResult{ + PrivatePem: privatePem, + PublicPem: publicPem, + Generated: true, + }, nil + } + + // TODO MVP allow specifying a URL to get the public key + // if registration.Key.URL != "" { + // return &oauthserver.KeyResult{ + // URL: registration.Key.URL, + // }, nil + // } + + if keyOption.PublicPEM != "" { + pemEncoded, err := base64.StdEncoding.DecodeString(keyOption.PublicPEM) + if err != nil { + s.logger.Error("cannot decode base64 encoded PEM string", "error", err) + } + _, err = utils.ParsePublicKeyPem(pemEncoded) + if err != nil { + s.logger.Error("cannot parse PEM encoded string", "error", err) + return nil, err + } + return &oauthserver.KeyResult{ + PublicPem: string(pemEncoded), + }, nil + } + + return nil, fmt.Errorf("at least one key option must be specified") +} + +// saveServiceAccount creates a service account if the service account ID is NoServiceAccountID, otherwise it updates the service account's permissions +func (s *OAuth2ServiceImpl) saveServiceAccount(ctx context.Context, extSvcName string, saID int64, permissions []ac.Permission) (int64, error) { + if saID == oauthserver.NoServiceAccountID { + // Create a service account + s.logger.Debug("Create service account", "external service name", extSvcName) + return s.createServiceAccount(ctx, extSvcName, permissions) + } + + // check if the service account exists + s.logger.Debug("Update service account", "external service name", extSvcName) + sa, err := s.saService.RetrieveServiceAccount(ctx, oauthserver.TmpOrgID, saID) + if err != nil { + s.logger.Error("Error retrieving service account", "external service name", extSvcName, "error", err) + return oauthserver.NoServiceAccountID, err + } + + // update the service account's permissions + if len(permissions) > 0 { + s.logger.Debug("Update role permissions", "external service name", extSvcName, "saID", saID) + if err := s.acService.SaveExternalServiceRole(ctx, ac.SaveExternalServiceRoleCommand{ + OrgID: ac.GlobalOrgID, + Global: true, + ExternalServiceID: extSvcName, + ServiceAccountID: sa.Id, + Permissions: permissions, + }); err != nil { + return oauthserver.NoServiceAccountID, err + } + return saID, nil + } + + // remove the service account + errDelete := s.deleteServiceAccount(ctx, extSvcName, sa.Id) + return oauthserver.NoServiceAccountID, errDelete +} + +// deleteServiceAccount deletes a service account by ID and removes its associated role +func (s *OAuth2ServiceImpl) deleteServiceAccount(ctx context.Context, extSvcName string, saID int64) error { + s.logger.Debug("Delete service account", "external service name", extSvcName, "saID", saID) + if err := s.saService.DeleteServiceAccount(ctx, oauthserver.TmpOrgID, saID); err != nil { + return err + } + return s.acService.DeleteExternalServiceRole(ctx, extSvcName) +} + +// createServiceAccount creates a service account with the given permissions and returns the ID of the service account +// When no permission is given, the account isn't created and NoServiceAccountID is returned +// This first design does not use a single transaction for the whole service account creation process => database consistency is not guaranteed. +// Consider changing this in the future. +func (s *OAuth2ServiceImpl) createServiceAccount(ctx context.Context, extSvcName string, permissions []ac.Permission) (int64, error) { + if len(permissions) == 0 { + // No permission, no service account + s.logger.Debug("No permission, no service account", "external service name", extSvcName) + return oauthserver.NoServiceAccountID, nil + } + + newRole := func(r roletype.RoleType) *roletype.RoleType { + return &r + } + newBool := func(b bool) *bool { + return &b + } + + slug := slugify.Slugify(extSvcName) + + s.logger.Debug("Generate service account", "external service name", extSvcName, "orgID", oauthserver.TmpOrgID, "name", slug) + sa, err := s.saService.CreateServiceAccount(ctx, oauthserver.TmpOrgID, &serviceaccounts.CreateServiceAccountForm{ + Name: slug, + Role: newRole(roletype.RoleViewer), // FIXME: Use empty role + IsDisabled: newBool(false), + }) + if err != nil { + return oauthserver.NoServiceAccountID, err + } + + s.logger.Debug("create tailored role for service account", "external service name", extSvcName, "name", slug, "service_account_id", sa.Id, "permissions", permissions) + if err := s.acService.SaveExternalServiceRole(ctx, ac.SaveExternalServiceRoleCommand{ + OrgID: ac.GlobalOrgID, + Global: true, + ExternalServiceID: slug, + ServiceAccountID: sa.Id, + Permissions: permissions, + }); err != nil { + return oauthserver.NoServiceAccountID, err + } + + return sa.Id, nil +} + +// handleRegistrationPermissions parses the registration form to retrieve requested permissions and adds default +// permissions when impersonation is requested +func (*OAuth2ServiceImpl) handleRegistrationPermissions(registration *oauthserver.ExternalServiceRegistration) ([]ac.Permission, []ac.Permission) { + selfPermissions := []ac.Permission{} + impersonatePermissions := []ac.Permission{} + + if registration.Self.Enabled { + selfPermissions = append(selfPermissions, registration.Self.Permissions...) + } + if registration.Impersonation.Enabled { + requiredForToken := []ac.Permission{ + {Action: ac.ActionUsersRead, Scope: oauthserver.ScopeGlobalUsersSelf}, + {Action: ac.ActionUsersPermissionsRead, Scope: oauthserver.ScopeUsersSelf}, + } + if registration.Impersonation.Groups { + requiredForToken = append(requiredForToken, ac.Permission{Action: ac.ActionTeamsRead, Scope: oauthserver.ScopeTeamsSelf}) + } + impersonatePermissions = append(requiredForToken, registration.Impersonation.Permissions...) + selfPermissions = append(selfPermissions, ac.Permission{Action: ac.ActionUsersImpersonate, Scope: ac.ScopeUsersAll}) + } + return selfPermissions, impersonatePermissions +} diff --git a/pkg/services/oauthserver/oasimpl/service_test.go b/pkg/services/oauthserver/oasimpl/service_test.go new file mode 100644 index 00000000000..0fb441647da --- /dev/null +++ b/pkg/services/oauthserver/oasimpl/service_test.go @@ -0,0 +1,545 @@ +package oasimpl + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "encoding/base64" + "fmt" + "testing" + "time" + + "github.com/ory/fosite" + "github.com/ory/fosite/storage" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" + + "github.com/grafana/grafana/pkg/infra/localcache" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models/roletype" + ac "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/oauthserver" + "github.com/grafana/grafana/pkg/services/oauthserver/oastest" + sa "github.com/grafana/grafana/pkg/services/serviceaccounts" + satests "github.com/grafana/grafana/pkg/services/serviceaccounts/tests" + "github.com/grafana/grafana/pkg/services/team/teamtest" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/services/user/usertest" + "github.com/grafana/grafana/pkg/setting" +) + +const ( + AppURL = "https://oauth.test/" + TokenURL = AppURL + "oauth2/token" +) + +var ( + pk, _ = rsa.GenerateKey(rand.Reader, 4096) + Client1Key, _ = rsa.GenerateKey(rand.Reader, 4096) +) + +type TestEnv struct { + S *OAuth2ServiceImpl + Cfg *setting.Cfg + AcStore *actest.MockStore + OAuthStore *oastest.MockStore + UserService *usertest.FakeUserService + TeamService *teamtest.FakeService + SAService *satests.MockServiceAccountService +} + +func setupTestEnv(t *testing.T) *TestEnv { + t.Helper() + + cfg := setting.NewCfg() + cfg.AppURL = AppURL + + config := &fosite.Config{ + AccessTokenLifespan: time.Hour, + TokenURL: TokenURL, + AccessTokenIssuer: AppURL, + IDTokenIssuer: AppURL, + ScopeStrategy: fosite.WildcardScopeStrategy, + } + + fmgt := featuremgmt.WithFeatures(featuremgmt.FlagExternalServiceAuth) + + env := &TestEnv{ + Cfg: cfg, + AcStore: &actest.MockStore{}, + OAuthStore: &oastest.MockStore{}, + UserService: usertest.NewUserServiceFake(), + TeamService: teamtest.NewFakeService(), + SAService: &satests.MockServiceAccountService{}, + } + env.S = &OAuth2ServiceImpl{ + cache: localcache.New(cacheExpirationTime, cacheCleanupInterval), + cfg: cfg, + accessControl: acimpl.ProvideAccessControl(cfg), + acService: acimpl.ProvideOSSService(cfg, env.AcStore, localcache.New(0, 0), fmgt), + memstore: storage.NewMemoryStore(), + sqlstore: env.OAuthStore, + logger: log.New("oauthserver.test"), + userService: env.UserService, + saService: env.SAService, + teamService: env.TeamService, + publicKey: &pk.PublicKey, + } + env.S.oauthProvider = newProvider(config, env.S, pk) + + return env +} + +func TestOAuth2ServiceImpl_SaveExternalService(t *testing.T) { + const serviceName = "my-ext-service" + + sa1 := sa.ServiceAccountDTO{Id: 1, Name: serviceName, Login: serviceName, OrgId: oauthserver.TmpOrgID, IsDisabled: false, Role: "Viewer"} + sa1Profile := sa.ServiceAccountProfileDTO{Id: 1, Name: serviceName, Login: serviceName, OrgId: oauthserver.TmpOrgID, IsDisabled: false, Role: "Viewer"} + prevSaID := int64(3) + // Using a function to prevent modifying the same object in the tests + client1 := func() *oauthserver.ExternalService { + return &oauthserver.ExternalService{ + Name: serviceName, + ClientID: "RANDOMID", + Secret: "RANDOMSECRET", + GrantTypes: "client_credentials", + PublicPem: []byte("-----BEGIN PUBLIC KEY-----"), + ServiceAccountID: prevSaID, + SelfPermissions: []ac.Permission{{Action: "users:impersonate", Scope: "users:*"}}, + } + } + + tests := []struct { + name string + init func(*TestEnv) + cmd *oauthserver.ExternalServiceRegistration + mockChecks func(*testing.T, *TestEnv) + wantErr bool + }{ + { + name: "should create a new client without permissions", + init: func(env *TestEnv) { + // No client at the beginning + env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(nil, oauthserver.ErrClientNotFound(serviceName)) + env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil) + }, + cmd: &oauthserver.ExternalServiceRegistration{ + Name: serviceName, + Key: &oauthserver.KeyOption{Generate: true}, + }, + mockChecks: func(t *testing.T, env *TestEnv) { + env.OAuthStore.AssertCalled(t, "GetExternalServiceByName", mock.Anything, mock.MatchedBy(func(name string) bool { + return name == serviceName + })) + env.OAuthStore.AssertCalled(t, "SaveExternalService", mock.Anything, mock.MatchedBy(func(client *oauthserver.ExternalService) bool { + return client.Name == serviceName && client.ClientID != "" && client.Secret != "" && + len(client.GrantTypes) == 0 && len(client.PublicPem) > 0 && client.ServiceAccountID == 0 && + len(client.ImpersonatePermissions) == 0 + })) + }, + }, + { + name: "should create a service account", + init: func(env *TestEnv) { + // No client at the beginning + env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(nil, oauthserver.ErrClientNotFound(serviceName)) + env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil) + // Service account and permission creation + env.SAService.On("CreateServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(&sa1, nil) + env.AcStore.On("SaveExternalServiceRole", mock.Anything, mock.Anything).Return(nil) + }, + cmd: &oauthserver.ExternalServiceRegistration{ + Name: serviceName, + Key: &oauthserver.KeyOption{Generate: true}, + Self: oauthserver.SelfCfg{ + Enabled: true, + Permissions: []ac.Permission{{Action: "users:read", Scope: "users:*"}}, + }, + }, + mockChecks: func(t *testing.T, env *TestEnv) { + // Check that the client has a service account and the correct grant type + env.OAuthStore.AssertCalled(t, "SaveExternalService", mock.Anything, mock.MatchedBy(func(client *oauthserver.ExternalService) bool { + return client.Name == serviceName && + client.GrantTypes == "client_credentials" && client.ServiceAccountID == sa1.Id + })) + // Check that the service account is created in the correct org with the correct role + env.SAService.AssertCalled(t, "CreateServiceAccount", mock.Anything, + mock.MatchedBy(func(orgID int64) bool { return orgID == oauthserver.TmpOrgID }), + mock.MatchedBy(func(cmd *sa.CreateServiceAccountForm) bool { + return cmd.Name == serviceName && *cmd.Role == roletype.RoleViewer + }), + ) + }, + }, + { + name: "should delete the service account", + init: func(env *TestEnv) { + // Existing client (with a service account hence a role) + env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(client1(), nil) + env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil) + env.SAService.On("RetrieveServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(&sa1Profile, nil) + // No permission anymore will trigger deletion of the service account and its role + env.SAService.On("DeleteServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(nil) + env.AcStore.On("DeleteExternalServiceRole", mock.Anything, mock.Anything).Return(nil) + }, + cmd: &oauthserver.ExternalServiceRegistration{ + Name: serviceName, + Key: &oauthserver.KeyOption{Generate: true}, + Self: oauthserver.SelfCfg{ + Enabled: false, + }, + }, + mockChecks: func(t *testing.T, env *TestEnv) { + // Check that the service has no service account anymore + env.OAuthStore.AssertCalled(t, "SaveExternalService", mock.Anything, mock.MatchedBy(func(client *oauthserver.ExternalService) bool { + return client.Name == serviceName && client.ServiceAccountID == oauthserver.NoServiceAccountID + })) + // Check that the service account is retrieved with the correct ID + env.SAService.AssertCalled(t, "RetrieveServiceAccount", mock.Anything, + mock.MatchedBy(func(orgID int64) bool { return orgID == oauthserver.TmpOrgID }), + mock.MatchedBy(func(saID int64) bool { return saID == prevSaID })) + // Check that the service account is deleted in the correct org + env.SAService.AssertCalled(t, "DeleteServiceAccount", mock.Anything, + mock.MatchedBy(func(orgID int64) bool { return orgID == oauthserver.TmpOrgID }), + mock.MatchedBy(func(saID int64) bool { return saID == sa1.Id })) + // Check that the associated role is deleted + env.AcStore.AssertCalled(t, "DeleteExternalServiceRole", mock.Anything, + mock.MatchedBy(func(extSvcName string) bool { return extSvcName == serviceName })) + }, + }, + { + name: "should update the service account", + init: func(env *TestEnv) { + // Existing client (with a service account hence a role) + env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(client1(), nil) + env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil) + env.SAService.On("RetrieveServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(&sa1Profile, nil) + // Update the service account permissions + env.AcStore.On("SaveExternalServiceRole", mock.Anything, mock.Anything).Return(nil) + }, + cmd: &oauthserver.ExternalServiceRegistration{ + Name: serviceName, + Key: &oauthserver.KeyOption{Generate: true}, + Self: oauthserver.SelfCfg{ + Enabled: true, + Permissions: []ac.Permission{{Action: "dashboards:create", Scope: "folders:uid:general"}}, + }, + }, + mockChecks: func(t *testing.T, env *TestEnv) { + // Ensure new permissions are in place + env.AcStore.AssertCalled(t, "SaveExternalServiceRole", mock.Anything, + mock.MatchedBy(func(cmd ac.SaveExternalServiceRoleCommand) bool { + return cmd.ServiceAccountID == sa1.Id && cmd.ExternalServiceID == client1().Name && + cmd.OrgID == int64(ac.GlobalOrgID) && len(cmd.Permissions) == 1 && + cmd.Permissions[0] == ac.Permission{Action: "dashboards:create", Scope: "folders:uid:general"} + })) + }, + }, + { + name: "should allow jwt bearer grant and set default permissions", + init: func(env *TestEnv) { + // No client at the beginning + env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(nil, oauthserver.ErrClientNotFound(serviceName)) + env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil) + // The service account needs to be created with a permission to impersonate users + env.SAService.On("CreateServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(&sa1, nil) + env.AcStore.On("SaveExternalServiceRole", mock.Anything, mock.Anything).Return(nil) + }, + cmd: &oauthserver.ExternalServiceRegistration{ + Name: serviceName, + Key: &oauthserver.KeyOption{Generate: true}, + Impersonation: oauthserver.ImpersonationCfg{ + Enabled: true, + Groups: true, + Permissions: []ac.Permission{{Action: "dashboards:read", Scope: "dashboards:*"}}, + }, + }, + mockChecks: func(t *testing.T, env *TestEnv) { + // Check that the external service impersonate permissions contains the default permissions required to populate the access token + env.OAuthStore.AssertCalled(t, "SaveExternalService", mock.Anything, mock.MatchedBy(func(client *oauthserver.ExternalService) bool { + impPerm := client.ImpersonatePermissions + return slices.Contains(impPerm, ac.Permission{Action: "dashboards:read", Scope: "dashboards:*"}) && + slices.Contains(impPerm, ac.Permission{Action: ac.ActionUsersRead, Scope: oauthserver.ScopeGlobalUsersSelf}) && + slices.Contains(impPerm, ac.Permission{Action: ac.ActionUsersPermissionsRead, Scope: oauthserver.ScopeUsersSelf}) && + slices.Contains(impPerm, ac.Permission{Action: ac.ActionTeamsRead, Scope: oauthserver.ScopeTeamsSelf}) + })) + // Check that despite no credential_grants the service account still has a permission to impersonate users + env.AcStore.AssertCalled(t, "SaveExternalServiceRole", mock.Anything, + mock.MatchedBy(func(cmd ac.SaveExternalServiceRoleCommand) bool { + return len(cmd.Permissions) == 1 && cmd.Permissions[0] == ac.Permission{Action: ac.ActionUsersImpersonate, Scope: ac.ScopeUsersAll} + })) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + env := setupTestEnv(t) + if tt.init != nil { + tt.init(env) + } + + dto, err := env.S.SaveExternalService(context.Background(), tt.cmd) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + // Check that we generated client ID and secret + require.NotEmpty(t, dto.ID) + require.NotEmpty(t, dto.Secret) + + // Check that we have generated keys and that we correctly return them + if tt.cmd.Key != nil && tt.cmd.Key.Generate { + require.NotNil(t, dto.KeyResult) + require.True(t, dto.KeyResult.Generated) + require.NotEmpty(t, dto.KeyResult.PublicPem) + require.NotEmpty(t, dto.KeyResult.PrivatePem) + } + + // Check that we computed grant types and created or updated the service account + if tt.cmd.Self.Enabled { + require.NotNil(t, dto.GrantTypes) + require.Contains(t, dto.GrantTypes, fosite.GrantTypeClientCredentials, "grant types should contain client_credentials") + } else { + require.NotContains(t, dto.GrantTypes, fosite.GrantTypeClientCredentials, "grant types should not contain client_credentials") + } + // Check that we updated grant types + if tt.cmd.Impersonation.Enabled { + require.NotNil(t, dto.GrantTypes) + require.Contains(t, dto.GrantTypes, fosite.GrantTypeJWTBearer, "grant types should contain JWT Bearer grant") + } else { + require.NotContains(t, dto.GrantTypes, fosite.GrantTypeJWTBearer, "grant types should not contain JWT Bearer grant") + } + + // Check that mocks were called as expected + env.OAuthStore.AssertExpectations(t) + env.SAService.AssertExpectations(t) + env.AcStore.AssertExpectations(t) + + // Additional checks performed + if tt.mockChecks != nil { + tt.mockChecks(t, env) + } + }) + } +} + +func TestOAuth2ServiceImpl_GetExternalService(t *testing.T) { + const serviceName = "my-ext-service" + + dummyClient := func() *oauthserver.ExternalService { + return &oauthserver.ExternalService{ + Name: serviceName, + ClientID: "RANDOMID", + Secret: "RANDOMSECRET", + GrantTypes: "client_credentials", + PublicPem: []byte("-----BEGIN PUBLIC KEY-----"), + ServiceAccountID: 1, + } + } + cachedClient := &oauthserver.ExternalService{ + Name: serviceName, + ClientID: "RANDOMID", + Secret: "RANDOMSECRET", + GrantTypes: "client_credentials", + PublicPem: []byte("-----BEGIN PUBLIC KEY-----"), + ServiceAccountID: 1, + SelfPermissions: []ac.Permission{{Action: "users:impersonate", Scope: "users:*"}}, + SignedInUser: &user.SignedInUser{ + UserID: 1, + Permissions: map[int64]map[string][]string{ + 1: { + "users:impersonate": {"users:*"}, + }, + }, + }, + } + testCases := []struct { + name string + init func(*TestEnv) + mockChecks func(*testing.T, *TestEnv) + wantPerm []ac.Permission + wantErr bool + }{ + { + name: "should hit the cache", + init: func(env *TestEnv) { + env.S.cache.Set(serviceName, *cachedClient, time.Minute) + }, + mockChecks: func(t *testing.T, env *TestEnv) { + env.OAuthStore.AssertNotCalled(t, "GetExternalService", mock.Anything, mock.Anything) + }, + wantPerm: []ac.Permission{{Action: "users:impersonate", Scope: "users:*"}}, + }, + { + name: "should return error when the client was not found", + init: func(env *TestEnv) { + env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(nil, oauthserver.ErrClientNotFound(serviceName)) + }, + wantErr: true, + }, + { + name: "should return error when the service account was not found", + init: func(env *TestEnv) { + env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(dummyClient(), nil) + env.SAService.On("RetrieveServiceAccount", mock.Anything, int64(1), int64(1)).Return(&sa.ServiceAccountProfileDTO{}, sa.ErrServiceAccountNotFound) + }, + mockChecks: func(t *testing.T, env *TestEnv) { + env.OAuthStore.AssertCalled(t, "GetExternalService", mock.Anything, mock.Anything) + env.SAService.AssertCalled(t, "RetrieveServiceAccount", mock.Anything, 1, 1) + }, + wantErr: true, + }, + { + name: "should return error when the service account has no permissions", + init: func(env *TestEnv) { + env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(dummyClient(), nil) + env.SAService.On("RetrieveServiceAccount", mock.Anything, int64(1), int64(1)).Return(&sa.ServiceAccountProfileDTO{}, nil) + env.AcStore.On("GetUserPermissions", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("some error")) + }, + mockChecks: func(t *testing.T, env *TestEnv) { + env.OAuthStore.AssertCalled(t, "GetExternalService", mock.Anything, mock.Anything) + env.SAService.AssertCalled(t, "RetrieveServiceAccount", mock.Anything, 1, 1) + }, + wantErr: true, + }, + { + name: "should return correctly", + init: func(env *TestEnv) { + env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(dummyClient(), nil) + env.SAService.On("RetrieveServiceAccount", mock.Anything, int64(1), int64(1)).Return(&sa.ServiceAccountProfileDTO{Id: 1}, nil) + env.AcStore.On("GetUserPermissions", mock.Anything, mock.Anything).Return([]ac.Permission{{Action: ac.ActionUsersImpersonate, Scope: ac.ScopeUsersAll}}, nil) + }, + mockChecks: func(t *testing.T, env *TestEnv) { + env.OAuthStore.AssertCalled(t, "GetExternalService", mock.Anything, mock.Anything) + env.SAService.AssertCalled(t, "RetrieveServiceAccount", mock.Anything, int64(1), int64(1)) + }, + wantPerm: []ac.Permission{{Action: "users:impersonate", Scope: "users:*"}}, + }, + { + name: "should return correctly when the client has no service account", + init: func(env *TestEnv) { + client := &oauthserver.ExternalService{ + Name: serviceName, + ClientID: "RANDOMID", + Secret: "RANDOMSECRET", + GrantTypes: "client_credentials", + PublicPem: []byte("-----BEGIN PUBLIC KEY-----"), + ServiceAccountID: oauthserver.NoServiceAccountID, + } + env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(client, nil) + }, + mockChecks: func(t *testing.T, env *TestEnv) { + env.OAuthStore.AssertCalled(t, "GetExternalService", mock.Anything, mock.Anything) + }, + wantPerm: []ac.Permission{}, + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + env := setupTestEnv(t) + if tt.init != nil { + tt.init(env) + } + + client, err := env.S.GetExternalService(context.Background(), serviceName) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + if tt.mockChecks != nil { + tt.mockChecks(t, env) + } + + require.Equal(t, serviceName, client.Name) + require.ElementsMatch(t, client.SelfPermissions, tt.wantPerm) + assertArrayInMap(t, client.SignedInUser.Permissions[1], ac.GroupScopesByAction(tt.wantPerm)) + + env.OAuthStore.AssertExpectations(t) + env.SAService.AssertExpectations(t) + }) + } +} + +func assertArrayInMap[K comparable, V string](t *testing.T, m1 map[K][]V, m2 map[K][]V) { + for k, v := range m1 { + require.Contains(t, m2, k) + require.ElementsMatch(t, v, m2[k]) + } +} + +func TestTestOAuth2ServiceImpl_handleKeyOptions(t *testing.T) { + testCases := []struct { + name string + keyOption *oauthserver.KeyOption + expectedResult *oauthserver.KeyResult + wantErr bool + }{ + { + name: "should return error when the key option is nil", + wantErr: true, + }, + { + name: "should return error when the key option is empty", + keyOption: &oauthserver.KeyOption{}, + wantErr: true, + }, + { + name: "should return successfully when PublicPEM is specified", + keyOption: &oauthserver.KeyOption{ + PublicPEM: base64.StdEncoding.EncodeToString([]byte(`-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbsGtoGJTopAIbhqy49/vyCJuDot+ +mgGaC8vUIigFQVsVB+v/HZ4yG1Rcvysig+tyNk1dZQpozpFc2dGmzHlGhw== +-----END PUBLIC KEY-----`)), + }, + wantErr: false, + expectedResult: &oauthserver.KeyResult{ + PublicPem: `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbsGtoGJTopAIbhqy49/vyCJuDot+ +mgGaC8vUIigFQVsVB+v/HZ4yG1Rcvysig+tyNk1dZQpozpFc2dGmzHlGhw== +-----END PUBLIC KEY-----`, + Generated: false, + PrivatePem: "", + URL: "", + }, + }, + } + env := setupTestEnv(t) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := env.S.handleKeyOptions(context.Background(), tc.keyOption) + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.expectedResult, result) + }) + } + + t.Run("should generate an ECDSA key pair (default) when generate key option is specified", func(t *testing.T) { + result, err := env.S.handleKeyOptions(context.Background(), &oauthserver.KeyOption{Generate: true}) + + require.NoError(t, err) + require.NotNil(t, result.PrivatePem) + require.NotNil(t, result.PublicPem) + require.True(t, result.Generated) + }) + + t.Run("should generate an RSA key pair when generate key option is specified", func(t *testing.T) { + env.S.cfg.OAuth2ServerGeneratedKeyTypeForClient = "RSA" + result, err := env.S.handleKeyOptions(context.Background(), &oauthserver.KeyOption{Generate: true}) + + require.NoError(t, err) + require.NotNil(t, result.PrivatePem) + require.NotNil(t, result.PublicPem) + require.True(t, result.Generated) + }) +} diff --git a/pkg/services/oauthserver/oasimpl/session.go b/pkg/services/oauthserver/oasimpl/session.go new file mode 100644 index 00000000000..6d184c8c6d8 --- /dev/null +++ b/pkg/services/oauthserver/oasimpl/session.go @@ -0,0 +1,16 @@ +package oasimpl + +import ( + "github.com/ory/fosite/handler/oauth2" + "github.com/ory/fosite/token/jwt" +) + +func NewAuthSession() *oauth2.JWTSession { + sess := &oauth2.JWTSession{ + JWTClaims: new(jwt.JWTClaims), + JWTHeader: new(jwt.Headers), + } + // Our tokens will follow the RFC9068 + sess.JWTHeader.Add("typ", "at+jwt") + return sess +} diff --git a/pkg/services/oauthserver/oasimpl/token.go b/pkg/services/oauthserver/oasimpl/token.go new file mode 100644 index 00000000000..6f05f9ed188 --- /dev/null +++ b/pkg/services/oauthserver/oasimpl/token.go @@ -0,0 +1,351 @@ +package oasimpl + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + + "github.com/ory/fosite" + "github.com/ory/fosite/handler/oauth2" + + ac "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/oauthserver" + "github.com/grafana/grafana/pkg/services/oauthserver/utils" + "github.com/grafana/grafana/pkg/services/team" + "github.com/grafana/grafana/pkg/services/user" +) + +// HandleTokenRequest handles the client's OAuth2 query to obtain an access_token by presenting its authorization +// grant (ex: client_credentials, jwtbearer) +func (s *OAuth2ServiceImpl) HandleTokenRequest(rw http.ResponseWriter, req *http.Request) { + // This context will be passed to all methods. + ctx := req.Context() + + // Create an empty session object which will be passed to the request handlers + oauthSession := NewAuthSession() + + // This will create an access request object and iterate through the registered TokenEndpointHandlers to validate the request. + accessRequest, err := s.oauthProvider.NewAccessRequest(ctx, req, oauthSession) + if err != nil { + s.writeAccessError(ctx, rw, accessRequest, err) + return + } + + client, err := s.GetExternalService(ctx, accessRequest.GetClient().GetID()) + if err != nil || client == nil { + s.oauthProvider.WriteAccessError(ctx, rw, accessRequest, &fosite.RFC6749Error{ + DescriptionField: "Could not find the requested subject.", + ErrorField: "not_found", + CodeField: http.StatusBadRequest, + }) + return + } + oauthSession.JWTClaims.Add("client_id", client.ClientID) + + errClientCred := s.handleClientCredentials(ctx, accessRequest, oauthSession, client) + if errClientCred != nil { + s.writeAccessError(ctx, rw, accessRequest, errClientCred) + return + } + + errJWTBearer := s.handleJWTBearer(ctx, accessRequest, oauthSession, client) + if errJWTBearer != nil { + s.writeAccessError(ctx, rw, accessRequest, errJWTBearer) + return + } + + // All tokens we generate in this service should target Grafana's API. + accessRequest.GrantAudience(s.cfg.AppURL) + + // Prepare response, fosite handlers will populate the token. + response, err := s.oauthProvider.NewAccessResponse(ctx, accessRequest) + if err != nil { + s.writeAccessError(ctx, rw, accessRequest, err) + return + } + s.oauthProvider.WriteAccessResponse(ctx, rw, accessRequest, response) +} + +// writeAccessError logs the error then uses fosite to write the error back to the user. +func (s *OAuth2ServiceImpl) writeAccessError(ctx context.Context, rw http.ResponseWriter, accessRequest fosite.AccessRequester, err error) { + var fositeErr *fosite.RFC6749Error + if errors.As(err, &fositeErr) { + s.logger.Error("description", fositeErr.DescriptionField, "hint", fositeErr.HintField, "error", fositeErr.ErrorField) + } else { + s.logger.Error("error", err) + } + s.oauthProvider.WriteAccessError(ctx, rw, accessRequest, err) +} + +// splitOAuthScopes sort scopes that are generic (profile, email, groups, entitlements) from scopes +// that are RBAC actions (used to further restrict the entitlements embedded in the access_token) +func splitOAuthScopes(requestedScopes fosite.Arguments) (map[string]bool, map[string]bool) { + actionsFilter := map[string]bool{} + claimsFilter := map[string]bool{} + for _, scope := range requestedScopes { + switch scope { + case "profile", "email", "groups", "entitlements": + claimsFilter[scope] = true + default: + actionsFilter[scope] = true + } + } + return actionsFilter, claimsFilter +} + +// handleJWTBearer populates the "impersonation" access_token generated by fosite to match the rfc9068 specifications (entitlements, groups). +// It ensures that the user can be impersonated, that the generated token audiences only contain Grafana's AppURL (and token endpoint) +// and that entitlements solely contain the user's permissions that the client is allowed to have. +func (s *OAuth2ServiceImpl) handleJWTBearer(ctx context.Context, accessRequest fosite.AccessRequester, oauthSession *oauth2.JWTSession, client *oauthserver.ExternalService) error { + if !accessRequest.GetGrantTypes().ExactOne(string(fosite.GrantTypeJWTBearer)) { + return nil + } + + userID, err := utils.ParseUserIDFromSubject(oauthSession.Subject) + if err != nil { + return &fosite.RFC6749Error{ + DescriptionField: "Could not find the requested subject.", + ErrorField: "not_found", + CodeField: http.StatusBadRequest, + } + } + + // Check audiences list only contains the AppURL and the token endpoint + for _, aud := range accessRequest.GetGrantedAudience() { + if aud != fmt.Sprintf("%voauth2/token", s.cfg.AppURL) && aud != s.cfg.AppURL { + return &fosite.RFC6749Error{ + DescriptionField: "Client is not allowed to target this Audience.", + HintField: "The audience must be the AppURL or the token endpoint.", + ErrorField: "invalid_request", + CodeField: http.StatusForbidden, + } + } + } + + // If the client was not allowed to impersonate the user we would not have reached this point given allowed scopes would have been empty + // But just in case we check again + ev := ac.EvalPermission(ac.ActionUsersImpersonate, ac.Scope("users", "id", strconv.FormatInt(userID, 10))) + hasAccess, errAccess := s.accessControl.Evaluate(ctx, client.SignedInUser, ev) + if errAccess != nil || !hasAccess { + return &fosite.RFC6749Error{ + DescriptionField: "Client is not allowed to impersonate subject.", + ErrorField: "restricted_access", + CodeField: http.StatusForbidden, + } + } + + // Populate claims' suject from the session subject + oauthSession.JWTClaims.Subject = oauthSession.Subject + + // Get the user + query := user.GetUserByIDQuery{ID: userID} + dbUser, err := s.userService.GetByID(ctx, &query) + if err != nil { + if errors.Is(err, user.ErrUserNotFound) { + return &fosite.RFC6749Error{ + DescriptionField: "Could not find the requested subject.", + ErrorField: "not_found", + CodeField: http.StatusBadRequest, + } + } + return &fosite.RFC6749Error{ + DescriptionField: "The request subject could not be processed.", + ErrorField: "server_error", + CodeField: http.StatusInternalServerError, + } + } + oauthSession.Username = dbUser.Login + + // Split scopes into actions and claims + actionsFilter, claimsFilter := splitOAuthScopes(accessRequest.GetGrantedScopes()) + + teams := []*team.TeamDTO{} + // Fetch teams if the groups scope is requested or if we need to populate it in the entitlements + if claimsFilter["groups"] || + (claimsFilter["entitlements"] && (len(actionsFilter) == 0 || actionsFilter["teams:read"])) { + var errGetTeams error + teams, errGetTeams = s.teamService.GetTeamsByUser(ctx, &team.GetTeamsByUserQuery{ + OrgID: oauthserver.TmpOrgID, + UserID: dbUser.ID, + // Fetch teams without restriction on permissions + SignedInUser: &user.SignedInUser{ + OrgID: oauthserver.TmpOrgID, + Permissions: map[int64]map[string][]string{ + oauthserver.TmpOrgID: { + ac.ActionTeamsRead: {ac.ScopeTeamsAll}, + }, + }, + }, + }) + if errGetTeams != nil { + return &fosite.RFC6749Error{ + DescriptionField: "The teams scope could not be processed.", + ErrorField: "server_error", + CodeField: http.StatusInternalServerError, + } + } + } + if claimsFilter["profile"] { + oauthSession.JWTClaims.Add("name", dbUser.Name) + oauthSession.JWTClaims.Add("login", dbUser.Login) + oauthSession.JWTClaims.Add("updated_at", dbUser.Updated.Unix()) + } + if claimsFilter["email"] { + oauthSession.JWTClaims.Add("email", dbUser.Email) + } + if claimsFilter["groups"] { + teamNames := make([]string, 0, len(teams)) + for _, team := range teams { + teamNames = append(teamNames, team.Name) + } + oauthSession.JWTClaims.Add("groups", teamNames) + } + + if claimsFilter["entitlements"] { + // Get the user permissions (apply the actions filter) + permissions, errGetPermission := s.filteredUserPermissions(ctx, userID, actionsFilter) + if errGetPermission != nil { + return errGetPermission + } + + // Compute the impersonated permissions (apply the actions filter, replace the scope self with the user id) + impPerms := s.filteredImpersonatePermissions(client.ImpersonatePermissions, userID, teams, actionsFilter) + + // Intersect the permissions with the client permissions + intesect := ac.Intersect(permissions, impPerms) + + oauthSession.JWTClaims.Add("entitlements", intesect) + } + + return nil +} + +// filteredUserPermissions gets the user permissions and applies the actions filter +func (s *OAuth2ServiceImpl) filteredUserPermissions(ctx context.Context, userID int64, actionsFilter map[string]bool) ([]ac.Permission, error) { + permissions, err := s.acService.SearchUserPermissions(ctx, oauthserver.TmpOrgID, ac.SearchOptions{UserID: userID}) + if err != nil { + return nil, &fosite.RFC6749Error{ + DescriptionField: "The permissions scope could not be processed.", + ErrorField: "server_error", + CodeField: http.StatusInternalServerError, + } + } + + // Apply the actions filter + if len(actionsFilter) > 0 { + filtered := []ac.Permission{} + for i := range permissions { + if actionsFilter[permissions[i].Action] { + filtered = append(filtered, permissions[i]) + } + } + permissions = filtered + } + return permissions, nil +} + +// filteredImpersonatePermissions computes the impersonated permissions. +// It applies the actions filter and replaces the "self RBAC scopes" (~ scope templates) by the correct user id/team id. +func (*OAuth2ServiceImpl) filteredImpersonatePermissions(impersonatePermissions []ac.Permission, userID int64, teams []*team.TeamDTO, actionsFilter map[string]bool) []ac.Permission { + // Compute the impersonated permissions + impPerms := impersonatePermissions + // Apply the actions filter + if len(actionsFilter) > 0 { + filtered := []ac.Permission{} + for i := range impPerms { + if actionsFilter[impPerms[i].Action] { + filtered = append(filtered, impPerms[i]) + } + } + impPerms = filtered + } + + // Replace the scope self with the user id + correctScopes := []ac.Permission{} + for i := range impPerms { + switch impPerms[i].Scope { + case oauthserver.ScopeGlobalUsersSelf: + correctScopes = append(correctScopes, ac.Permission{ + Action: impPerms[i].Action, + Scope: ac.Scope("global.users", "id", strconv.FormatInt(userID, 10)), + }) + case oauthserver.ScopeUsersSelf: + correctScopes = append(correctScopes, ac.Permission{ + Action: impPerms[i].Action, + Scope: ac.Scope("users", "id", strconv.FormatInt(userID, 10)), + }) + case oauthserver.ScopeTeamsSelf: + for t := range teams { + correctScopes = append(correctScopes, ac.Permission{ + Action: impPerms[i].Action, + Scope: ac.Scope("teams", "id", strconv.FormatInt(teams[t].ID, 10)), + }) + } + default: + correctScopes = append(correctScopes, impPerms[i]) + } + continue + } + return correctScopes +} + +// handleClientCredentials populates the client's access_token generated by fosite to match the rfc9068 specifications (entitlements, groups) +func (s *OAuth2ServiceImpl) handleClientCredentials(ctx context.Context, accessRequest fosite.AccessRequester, oauthSession *oauth2.JWTSession, client *oauthserver.ExternalService) error { + if !accessRequest.GetGrantTypes().ExactOne("client_credentials") { + return nil + } + // Set the subject to the service account associated to the client + oauthSession.JWTClaims.Subject = fmt.Sprintf("user:id:%d", client.ServiceAccountID) + + sa := client.SignedInUser + if sa == nil { + return &fosite.RFC6749Error{ + DescriptionField: "Could not find the service account of the client", + ErrorField: "not_found", + CodeField: http.StatusNotFound, + } + } + oauthSession.Username = sa.Login + + // For client credentials, scopes are not marked as granted by fosite but the request would have been rejected + // already if the client was not allowed to request them + for _, scope := range accessRequest.GetRequestedScopes() { + accessRequest.GrantScope(scope) + } + + // Split scopes into actions and claims + actionsFilter, claimsFilter := splitOAuthScopes(accessRequest.GetGrantedScopes()) + + if claimsFilter["profile"] { + oauthSession.JWTClaims.Add("name", sa.Name) + oauthSession.JWTClaims.Add("login", sa.Login) + } + if claimsFilter["email"] { + s.logger.Debug("Service accounts have no emails") + } + if claimsFilter["groups"] { + s.logger.Debug("Service accounts have no groups") + } + if claimsFilter["entitlements"] { + s.logger.Debug("Processing client entitlements") + if sa.Permissions != nil && sa.Permissions[oauthserver.TmpOrgID] != nil { + perms := sa.Permissions[oauthserver.TmpOrgID] + if len(actionsFilter) > 0 { + filtered := map[string][]string{} + for action := range actionsFilter { + if _, ok := perms[action]; ok { + filtered[action] = perms[action] + } + } + perms = filtered + } + oauthSession.JWTClaims.Add("entitlements", perms) + } else { + s.logger.Debug("Client has no permissions") + } + } + + return nil +} diff --git a/pkg/services/oauthserver/oasimpl/token_test.go b/pkg/services/oauthserver/oasimpl/token_test.go new file mode 100644 index 00000000000..2e15bd602ec --- /dev/null +++ b/pkg/services/oauthserver/oasimpl/token_test.go @@ -0,0 +1,747 @@ +package oasimpl + +import ( + "context" + "crypto/rsa" + "encoding/json" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + "time" + + "github.com/google/uuid" + "github.com/ory/fosite" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/bcrypt" + "golang.org/x/exp/maps" + "gopkg.in/square/go-jose.v2" + "gopkg.in/square/go-jose.v2/jwt" + + "github.com/grafana/grafana/pkg/models/roletype" + ac "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/oauthserver" + "github.com/grafana/grafana/pkg/services/serviceaccounts" + "github.com/grafana/grafana/pkg/services/team" + "github.com/grafana/grafana/pkg/services/user" +) + +func TestOAuth2ServiceImpl_handleClientCredentials(t *testing.T) { + client1 := &oauthserver.ExternalService{ + Name: "testapp", + ClientID: "RANDOMID", + GrantTypes: string(fosite.GrantTypeClientCredentials), + ServiceAccountID: 2, + SignedInUser: &user.SignedInUser{ + UserID: 2, + Name: "Test App", + Login: "testapp", + OrgRole: roletype.RoleViewer, + Permissions: map[int64]map[string][]string{ + oauthserver.TmpOrgID: { + "dashboards:read": {"dashboards:*", "folders:*"}, + "dashboards:write": {"dashboards:uid:1"}, + }, + }, + }, + } + + tests := []struct { + name string + scopes []string + client *oauthserver.ExternalService + expectedClaims map[string]interface{} + wantErr bool + }{ + { + name: "no claim without client_credentials grant type", + client: &oauthserver.ExternalService{ + Name: "testapp", + ClientID: "RANDOMID", + GrantTypes: string(fosite.GrantTypeJWTBearer), + ServiceAccountID: 2, + SignedInUser: &user.SignedInUser{}, + }, + }, + { + name: "no claims without scopes", + client: client1, + }, + { + name: "profile claims", + client: client1, + scopes: []string{"profile"}, + expectedClaims: map[string]interface{}{"name": "Test App", "login": "testapp"}, + }, + { + name: "email claims should be empty", + client: client1, + scopes: []string{"email"}, + }, + { + name: "groups claims should be empty", + client: client1, + scopes: []string{"groups"}, + }, + { + name: "entitlements claims", + client: client1, + scopes: []string{"entitlements"}, + expectedClaims: map[string]interface{}{"entitlements": map[string][]string{ + "dashboards:read": {"dashboards:*", "folders:*"}, + "dashboards:write": {"dashboards:uid:1"}, + }}, + }, + { + name: "scoped entitlements claims", + client: client1, + scopes: []string{"entitlements", "dashboards:write"}, + expectedClaims: map[string]interface{}{"entitlements": map[string][]string{ + "dashboards:write": {"dashboards:uid:1"}, + }}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + env := setupTestEnv(t) + session := &fosite.DefaultSession{} + requester := fosite.NewAccessRequest(session) + requester.GrantTypes = fosite.Arguments(strings.Split(tt.client.GrantTypes, ",")) + requester.RequestedScope = fosite.Arguments(tt.scopes) + sessionData := NewAuthSession() + err := env.S.handleClientCredentials(ctx, requester, sessionData, tt.client) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + if tt.expectedClaims == nil { + require.Empty(t, sessionData.JWTClaims.Extra) + return + } + require.Len(t, sessionData.JWTClaims.Extra, len(tt.expectedClaims)) + for claimsKey, claimsValue := range tt.expectedClaims { + switch expected := claimsValue.(type) { + case []string: + require.ElementsMatch(t, claimsValue, sessionData.JWTClaims.Extra[claimsKey]) + case map[string][]string: + actual, ok := sessionData.JWTClaims.Extra[claimsKey].(map[string][]string) + require.True(t, ok, "expected map[string][]string") + + require.ElementsMatch(t, maps.Keys(expected), maps.Keys(actual)) + for expKey, expValue := range expected { + require.ElementsMatch(t, expValue, actual[expKey]) + } + default: + require.Equal(t, claimsValue, sessionData.JWTClaims.Extra[claimsKey]) + } + } + }) + } +} + +func TestOAuth2ServiceImpl_handleJWTBearer(t *testing.T) { + now := time.Now() + client1 := &oauthserver.ExternalService{ + Name: "testapp", + ClientID: "RANDOMID", + GrantTypes: string(fosite.GrantTypeJWTBearer), + ServiceAccountID: 2, + SignedInUser: &user.SignedInUser{ + UserID: 2, + OrgID: oauthserver.TmpOrgID, + Name: "Test App", + Login: "testapp", + OrgRole: roletype.RoleViewer, + Permissions: map[int64]map[string][]string{ + oauthserver.TmpOrgID: { + "users:impersonate": {"users:*"}, + }, + }, + }, + } + user56 := &user.User{ + ID: 56, + Email: "user56@example.org", + Login: "user56", + Name: "User 56", + Updated: now, + } + teams := []*team.TeamDTO{ + {ID: 1, Name: "Team 1", OrgID: 1}, + {ID: 2, Name: "Team 2", OrgID: 1}, + } + client1WithPerm := func(perms []ac.Permission) *oauthserver.ExternalService { + client := *client1 + client.ImpersonatePermissions = perms + return &client + } + + tests := []struct { + name string + initEnv func(*TestEnv) + scopes []string + client *oauthserver.ExternalService + subject string + expectedClaims map[string]interface{} + wantErr bool + }{ + { + name: "no claim without jwtbearer grant type", + client: &oauthserver.ExternalService{ + Name: "testapp", + ClientID: "RANDOMID", + GrantTypes: string(fosite.GrantTypeClientCredentials), + ServiceAccountID: 2, + }, + }, + { + name: "err invalid subject", + client: client1, + subject: "invalid_subject", + wantErr: true, + }, + { + name: "err client is not allowed to impersonate", + client: &oauthserver.ExternalService{ + Name: "testapp", + ClientID: "RANDOMID", + GrantTypes: string(fosite.GrantTypeJWTBearer), + ServiceAccountID: 2, + SignedInUser: &user.SignedInUser{ + UserID: 2, + Name: "Test App", + Login: "testapp", + OrgRole: roletype.RoleViewer, + Permissions: map[int64]map[string][]string{oauthserver.TmpOrgID: {}}, + }, + }, + subject: "user:id:56", + wantErr: true, + }, + { + name: "err subject not found", + initEnv: func(env *TestEnv) { + env.UserService.ExpectedError = user.ErrUserNotFound + }, + client: client1, + subject: "user:id:56", + wantErr: true, + }, + { + name: "no claim without scope", + initEnv: func(env *TestEnv) { + env.UserService.ExpectedUser = user56 + }, + client: client1, + subject: "user:id:56", + }, + { + name: "profile claims", + initEnv: func(env *TestEnv) { + env.UserService.ExpectedUser = user56 + }, + client: client1, + subject: "user:id:56", + scopes: []string{"profile"}, + expectedClaims: map[string]interface{}{ + "name": "User 56", + "login": "user56", + "updated_at": now.Unix(), + }, + }, + { + name: "email claim", + initEnv: func(env *TestEnv) { + env.UserService.ExpectedUser = user56 + }, + client: client1, + subject: "user:id:56", + scopes: []string{"email"}, + expectedClaims: map[string]interface{}{ + "email": "user56@example.org", + }, + }, + { + name: "groups claim", + initEnv: func(env *TestEnv) { + env.UserService.ExpectedUser = user56 + env.TeamService.ExpectedTeamsByUser = teams + }, + client: client1, + subject: "user:id:56", + scopes: []string{"groups"}, + expectedClaims: map[string]interface{}{ + "groups": []string{"Team 1", "Team 2"}, + }, + }, + { + name: "no entitlement without permission intersection", + initEnv: func(env *TestEnv) { + env.UserService.ExpectedUser = user56 + env.AcStore.On("GetUsersBasicRoles", mock.Anything, mock.Anything, mock.Anything).Return(map[int64][]string{ + 56: {"Viewer"}}, nil) + env.AcStore.On("SearchUsersPermissions", mock.Anything, mock.Anything, mock.Anything).Return(map[int64][]ac.Permission{ + 56: {{Action: "dashboards:read", Scope: "dashboards:uid:1"}}, + }, nil) + }, + client: client1WithPerm([]ac.Permission{ + {Action: "datasources:read", Scope: "datasources:*"}, + }), + subject: "user:id:56", + expectedClaims: map[string]interface{}{ + "entitlements": map[string][]string{}, + }, + scopes: []string{"entitlements"}, + }, + { + name: "entitlements contains only the intersection of permissions", + initEnv: func(env *TestEnv) { + env.UserService.ExpectedUser = user56 + env.AcStore.On("GetUsersBasicRoles", mock.Anything, mock.Anything, mock.Anything).Return(map[int64][]string{ + 56: {"Viewer"}}, nil) + env.AcStore.On("SearchUsersPermissions", mock.Anything, mock.Anything, mock.Anything).Return(map[int64][]ac.Permission{ + 56: { + {Action: "dashboards:read", Scope: "dashboards:uid:1"}, + {Action: "datasources:read", Scope: "datasources:uid:1"}, + }, + }, nil) + }, + client: client1WithPerm([]ac.Permission{ + {Action: "datasources:read", Scope: "datasources:*"}, + }), + subject: "user:id:56", + expectedClaims: map[string]interface{}{ + "entitlements": map[string][]string{ + "datasources:read": {"datasources:uid:1"}, + }, + }, + scopes: []string{"entitlements"}, + }, + { + name: "entitlements have correctly translated users:self permissions", + initEnv: func(env *TestEnv) { + env.UserService.ExpectedUser = user56 + env.AcStore.On("GetUsersBasicRoles", mock.Anything, mock.Anything, mock.Anything).Return(map[int64][]string{ + 56: {"Viewer"}}, nil) + env.AcStore.On("SearchUsersPermissions", mock.Anything, mock.Anything, mock.Anything).Return(map[int64][]ac.Permission{ + 56: { + {Action: "users:read", Scope: "global.users:id:*"}, + {Action: "users.permissions:read", Scope: "users:id:*"}, + }}, nil) + }, + client: client1WithPerm([]ac.Permission{ + {Action: "users:read", Scope: "global.users:self"}, + {Action: "users.permissions:read", Scope: "users:self"}, + }), + subject: "user:id:56", + expectedClaims: map[string]interface{}{ + "entitlements": map[string][]string{ + "users:read": {"global.users:id:56"}, + "users.permissions:read": {"users:id:56"}, + }, + }, + scopes: []string{"entitlements"}, + }, + { + name: "entitlements have correctly translated teams:self permissions", + initEnv: func(env *TestEnv) { + env.UserService.ExpectedUser = user56 + env.TeamService.ExpectedTeamsByUser = teams + env.AcStore.On("GetUsersBasicRoles", mock.Anything, mock.Anything, mock.Anything).Return(map[int64][]string{ + 56: {"Viewer"}}, nil) + env.AcStore.On("SearchUsersPermissions", mock.Anything, mock.Anything, mock.Anything).Return(map[int64][]ac.Permission{ + 56: {{Action: "teams:read", Scope: "teams:*"}}}, nil) + }, + client: client1WithPerm([]ac.Permission{ + {Action: "teams:read", Scope: "teams:self"}, + }), + subject: "user:id:56", + expectedClaims: map[string]interface{}{ + "entitlements": map[string][]string{"teams:read": {"teams:id:1", "teams:id:2"}}, + }, + scopes: []string{"entitlements"}, + }, + { + name: "entitlements are correctly filtered based on scopes", + initEnv: func(env *TestEnv) { + env.UserService.ExpectedUser = user56 + env.TeamService.ExpectedTeamsByUser = teams + env.AcStore.On("GetUsersBasicRoles", mock.Anything, mock.Anything, mock.Anything).Return(map[int64][]string{ + 56: {"Viewer"}}, nil) + env.AcStore.On("SearchUsersPermissions", mock.Anything, mock.Anything, mock.Anything).Return(map[int64][]ac.Permission{ + 56: { + {Action: "users:read", Scope: "global.users:id:*"}, + {Action: "datasources:read", Scope: "datasources:uid:1"}, + }}, nil) + }, + client: client1WithPerm([]ac.Permission{ + {Action: "users:read", Scope: "global.users:*"}, + {Action: "datasources:read", Scope: "datasources:*"}, + }), + subject: "user:id:56", + expectedClaims: map[string]interface{}{ + "entitlements": map[string][]string{"users:read": {"global.users:id:*"}}, + }, + scopes: []string{"entitlements", "users:read"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + env := setupTestEnv(t) + session := &fosite.DefaultSession{} + requester := fosite.NewAccessRequest(session) + requester.GrantTypes = fosite.Arguments(strings.Split(tt.client.GrantTypes, ",")) + requester.RequestedScope = fosite.Arguments(tt.scopes) + requester.GrantedScope = fosite.Arguments(tt.scopes) + sessionData := NewAuthSession() + sessionData.Subject = tt.subject + + if tt.initEnv != nil { + tt.initEnv(env) + } + err := env.S.handleJWTBearer(ctx, requester, sessionData, tt.client) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + if tt.expectedClaims == nil { + require.Empty(t, sessionData.JWTClaims.Extra) + return + } + require.Len(t, sessionData.JWTClaims.Extra, len(tt.expectedClaims)) + + for claimsKey, claimsValue := range tt.expectedClaims { + switch expected := claimsValue.(type) { + case []string: + require.ElementsMatch(t, claimsValue, sessionData.JWTClaims.Extra[claimsKey]) + case map[string][]string: + actual, ok := sessionData.JWTClaims.Extra[claimsKey].(map[string][]string) + require.True(t, ok, "expected map[string][]string") + + require.ElementsMatch(t, maps.Keys(expected), maps.Keys(actual)) + for expKey, expValue := range expected { + require.ElementsMatch(t, expValue, actual[expKey]) + } + default: + require.Equal(t, claimsValue, sessionData.JWTClaims.Extra[claimsKey]) + } + } + + env.AcStore.AssertExpectations(t) + }) + } +} + +type tokenResponse struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + Scope string `json:"scope"` + TokenType string `json:"token_type"` +} + +type claims struct { + jwt.Claims + ClientID string `json:"client_id"` + Groups []string `json:"groups"` + Email string `json:"email"` + Name string `json:"name"` + Login string `json:"login"` + Scopes []string `json:"scope"` + Entitlements map[string][]string `json:"entitlements"` +} + +func TestOAuth2ServiceImpl_HandleTokenRequest(t *testing.T) { + tests := []struct { + name string + tweakTestClient func(*oauthserver.ExternalService) + reqParams url.Values + wantCode int + wantScope []string + wantClaims *claims + }{ + { + name: "should allow client credentials grant", + reqParams: url.Values{ + "grant_type": {string(fosite.GrantTypeClientCredentials)}, + "client_id": {"CLIENT1ID"}, + "client_secret": {"CLIENT1SECRET"}, + "scope": {"profile email groups entitlements"}, + "audience": {AppURL}, + }, + wantCode: http.StatusOK, + wantScope: []string{"profile", "email", "groups", "entitlements"}, + wantClaims: &claims{ + Claims: jwt.Claims{ + Subject: "user:id:2", // From client1.ServiceAccountID + Issuer: AppURL, // From env.S.Config.Issuer + Audience: jwt.Audience{AppURL}, + }, + ClientID: "CLIENT1ID", + Name: "client-1", + Login: "client-1", + Entitlements: map[string][]string{ + "users:impersonate": {"users:*"}, + }, + }, + }, + { + name: "should allow jwt-bearer grant", + reqParams: url.Values{ + "grant_type": {string(fosite.GrantTypeJWTBearer)}, + "client_id": {"CLIENT1ID"}, + "client_secret": {"CLIENT1SECRET"}, + "assertion": { + genAssertion(t, Client1Key, "CLIENT1ID", "user:id:56", TokenURL, AppURL), + }, + "scope": {"profile email groups entitlements"}, + }, + wantCode: http.StatusOK, + wantScope: []string{"profile", "email", "groups", "entitlements"}, + wantClaims: &claims{ + Claims: jwt.Claims{ + Subject: "user:id:56", // To match the assertion + Issuer: AppURL, // From env.S.Config.Issuer + Audience: jwt.Audience{TokenURL, AppURL}, + }, + ClientID: "CLIENT1ID", + Email: "user56@example.org", + Name: "User 56", + Login: "user56", + Groups: []string{"Team 1", "Team 2"}, + Entitlements: map[string][]string{ + "dashboards:read": {"folders:uid:UID1"}, + "folders:read": {"folders:uid:UID1"}, + "users:read": {"global.users:id:56"}, + }, + }, + }, + { + name: "should deny jwt-bearer grant with wrong audience", + reqParams: url.Values{ + "grant_type": {string(fosite.GrantTypeJWTBearer)}, + "client_id": {"CLIENT1ID"}, + "client_secret": {"CLIENT1SECRET"}, + "assertion": { + genAssertion(t, Client1Key, "CLIENT1ID", "user:id:56", TokenURL, "invalid audience"), + }, + "scope": {"profile email groups entitlements"}, + }, + wantCode: http.StatusForbidden, + }, + { + name: "should deny jwt-bearer grant for clients without the grant", + reqParams: url.Values{ + "grant_type": {string(fosite.GrantTypeJWTBearer)}, + "client_id": {"CLIENT1ID"}, + "client_secret": {"CLIENT1SECRET"}, + "assertion": { + genAssertion(t, Client1Key, "CLIENT1ID", "user:id:56", TokenURL, AppURL), + }, + "scope": {"profile email groups entitlements"}, + }, + tweakTestClient: func(es *oauthserver.ExternalService) { + es.GrantTypes = string(fosite.GrantTypeClientCredentials) + }, + wantCode: http.StatusBadRequest, + }, + { + name: "should deny client_credentials grant for clients without the grant", + reqParams: url.Values{ + "grant_type": {string(fosite.GrantTypeClientCredentials)}, + "client_id": {"CLIENT1ID"}, + "client_secret": {"CLIENT1SECRET"}, + "scope": {"profile email groups entitlements"}, + "audience": {AppURL}, + }, + tweakTestClient: func(es *oauthserver.ExternalService) { + es.GrantTypes = string(fosite.GrantTypeJWTBearer) + }, + wantCode: http.StatusBadRequest, + }, + { + name: "should deny client_credentials grant with wrong secret", + reqParams: url.Values{ + "grant_type": {string(fosite.GrantTypeClientCredentials)}, + "client_id": {"CLIENT1ID"}, + "client_secret": {"WRONG_SECRET"}, + "scope": {"profile email groups entitlements"}, + "audience": {AppURL}, + }, + tweakTestClient: func(es *oauthserver.ExternalService) { + es.GrantTypes = string(fosite.GrantTypeClientCredentials) + }, + wantCode: http.StatusUnauthorized, + }, + { + name: "should deny jwt-bearer grant with wrong secret", + reqParams: url.Values{ + "grant_type": {string(fosite.GrantTypeJWTBearer)}, + "client_id": {"CLIENT1ID"}, + "client_secret": {"WRONG_SECRET"}, + "assertion": { + genAssertion(t, Client1Key, "CLIENT1ID", "user:id:56", TokenURL, AppURL), + }, + "scope": {"profile email groups entitlements"}, + }, + tweakTestClient: func(es *oauthserver.ExternalService) { + es.GrantTypes = string(fosite.GrantTypeJWTBearer) + }, + wantCode: http.StatusUnauthorized, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + env := setupTestEnv(t) + setupHandleTokenRequestEnv(t, env, tt.tweakTestClient) + + req := httptest.NewRequest("POST", "/oauth2/token", strings.NewReader(tt.reqParams.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp := httptest.NewRecorder() + + env.S.HandleTokenRequest(resp, req) + + require.Equal(t, tt.wantCode, resp.Code) + if tt.wantCode != http.StatusOK { + return + } + + body := resp.Body.Bytes() + require.NotEmpty(t, body) + + var tokenResp tokenResponse + require.NoError(t, json.Unmarshal(body, &tokenResp)) + + // Check token response + require.NotEmpty(t, tokenResp.Scope) + require.ElementsMatch(t, tt.wantScope, strings.Split(tokenResp.Scope, " ")) + require.Positive(t, tokenResp.ExpiresIn) + require.Equal(t, "bearer", tokenResp.TokenType) + require.NotEmpty(t, tokenResp.AccessToken) + + // Check access token + parsedToken, err := jwt.ParseSigned(tokenResp.AccessToken) + require.NoError(t, err) + require.Len(t, parsedToken.Headers, 1) + typeHeader := parsedToken.Headers[0].ExtraHeaders["typ"] + require.Equal(t, "at+jwt", strings.ToLower(typeHeader.(string))) + require.Equal(t, "RS256", parsedToken.Headers[0].Algorithm) + // Check access token claims + var claims claims + require.NoError(t, parsedToken.Claims(pk.Public(), &claims)) + // Check times and remove them + require.Positive(t, claims.IssuedAt.Time()) + require.Positive(t, claims.Expiry.Time()) + claims.IssuedAt = jwt.NewNumericDate(time.Time{}) + claims.Expiry = jwt.NewNumericDate(time.Time{}) + // Check the ID and remove it + require.NotEmpty(t, claims.ID) + claims.ID = "" + // Compare the rest + require.Equal(t, tt.wantClaims, &claims) + }) + } +} + +func genAssertion(t *testing.T, signKey *rsa.PrivateKey, clientID, sub string, audience ...string) string { + key := jose.SigningKey{Algorithm: jose.RS256, Key: signKey} + assertion := jwt.Claims{ + ID: uuid.New().String(), + Issuer: clientID, + Subject: sub, + Audience: audience, + Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour)), + IssuedAt: jwt.NewNumericDate(time.Now()), + } + + var signerOpts = jose.SignerOptions{} + signerOpts.WithType("JWT") + rsaSigner, errSigner := jose.NewSigner(key, &signerOpts) + require.NoError(t, errSigner) + builder := jwt.Signed(rsaSigner) + rawJWT, errSign := builder.Claims(assertion).CompactSerialize() + require.NoError(t, errSign) + return rawJWT +} + +// setupHandleTokenRequestEnv creates a client and a user and sets all Mocks call for the handleTokenRequest test cases +func setupHandleTokenRequestEnv(t *testing.T, env *TestEnv, opt func(*oauthserver.ExternalService)) { + now := time.Now() + hashedSecret, err := bcrypt.GenerateFromPassword([]byte("CLIENT1SECRET"), bcrypt.DefaultCost) + require.NoError(t, err) + client1 := &oauthserver.ExternalService{ + Name: "client-1", + ClientID: "CLIENT1ID", + Secret: string(hashedSecret), + GrantTypes: string(fosite.GrantTypeClientCredentials + "," + fosite.GrantTypeJWTBearer), + ServiceAccountID: 2, + ImpersonatePermissions: []ac.Permission{ + {Action: "users:read", Scope: oauthserver.ScopeGlobalUsersSelf}, + {Action: "users.permissions:read", Scope: oauthserver.ScopeUsersSelf}, + {Action: "teams:read", Scope: oauthserver.ScopeTeamsSelf}, + + {Action: "folders:read", Scope: "folders:*"}, + {Action: "dashboards:read", Scope: "folders:*"}, + {Action: "dashboards:read", Scope: "dashboards:*"}, + }, + SelfPermissions: []ac.Permission{ + {Action: "users:impersonate", Scope: "users:*"}, + }, + Audiences: AppURL, + } + + // Apply any option the test case might need + if opt != nil { + opt(client1) + } + + sa1 := &serviceaccounts.ServiceAccountProfileDTO{ + Id: client1.ServiceAccountID, + Name: client1.Name, + Login: client1.Name, + OrgId: oauthserver.TmpOrgID, + IsDisabled: false, + Created: now, + Updated: now, + Role: "Viewer", + } + + user56 := &user.User{ + ID: 56, + Email: "user56@example.org", + Login: "user56", + Name: "User 56", + Updated: now, + } + user56Permissions := []ac.Permission{ + {Action: "users:read", Scope: "global.users:id:56"}, + {Action: "folders:read", Scope: "folders:uid:UID1"}, + {Action: "dashboards:read", Scope: "folders:uid:UID1"}, + {Action: "datasources:read", Scope: "datasources:uid:DS_UID2"}, // This one should be ignored when impersonating + } + user56Teams := []*team.TeamDTO{ + {ID: 1, Name: "Team 1", OrgID: 1}, + {ID: 2, Name: "Team 2", OrgID: 1}, + } + + // To retrieve the Client, its publicKey and its permissions + env.OAuthStore.On("GetExternalService", mock.Anything, client1.ClientID).Return(client1, nil) + env.OAuthStore.On("GetExternalServicePublicKey", mock.Anything, client1.ClientID).Return(&jose.JSONWebKey{Key: Client1Key.Public(), Algorithm: "RS256"}, nil) + env.SAService.On("RetrieveServiceAccount", mock.Anything, oauthserver.TmpOrgID, client1.ServiceAccountID).Return(sa1, nil) + env.AcStore.On("GetUserPermissions", mock.Anything, mock.Anything).Return(client1.SelfPermissions, nil) + // To retrieve the user to impersonate, its permissions and its teams + env.AcStore.On("SearchUsersPermissions", mock.Anything, mock.Anything, mock.Anything).Return(map[int64][]ac.Permission{ + user56.ID: user56Permissions}, nil) + env.AcStore.On("GetUsersBasicRoles", mock.Anything, mock.Anything, mock.Anything).Return(map[int64][]string{ + user56.ID: {"Viewer"}}, nil) + env.TeamService.ExpectedTeamsByUser = user56Teams + env.UserService.ExpectedUser = user56 +} diff --git a/pkg/services/oauthserver/oastest/fakes.go b/pkg/services/oauthserver/oastest/fakes.go new file mode 100644 index 00000000000..95e639d02fb --- /dev/null +++ b/pkg/services/oauthserver/oastest/fakes.go @@ -0,0 +1,29 @@ +package oastest + +import ( + "context" + "net/http" + + "github.com/grafana/grafana/pkg/services/oauthserver" + "gopkg.in/square/go-jose.v2" +) + +type FakeService struct { + ExpectedClient *oauthserver.ExternalService + ExpectedKey *jose.JSONWebKey + ExpectedErr error +} + +var _ oauthserver.OAuth2Server = &FakeService{} + +func (s *FakeService) SaveExternalService(ctx context.Context, cmd *oauthserver.ExternalServiceRegistration) (*oauthserver.ExternalServiceDTO, error) { + return s.ExpectedClient.ToDTO(), s.ExpectedErr +} + +func (s *FakeService) GetExternalService(ctx context.Context, id string) (*oauthserver.ExternalService, error) { + return s.ExpectedClient, s.ExpectedErr +} + +func (s *FakeService) HandleTokenRequest(rw http.ResponseWriter, req *http.Request) {} + +func (s *FakeService) HandleIntrospectionRequest(rw http.ResponseWriter, req *http.Request) {} diff --git a/pkg/services/oauthserver/oastest/store_mock.go b/pkg/services/oauthserver/oastest/store_mock.go new file mode 100644 index 00000000000..b3107cbe4c5 --- /dev/null +++ b/pkg/services/oauthserver/oastest/store_mock.go @@ -0,0 +1,138 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package oastest + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + jose "gopkg.in/square/go-jose.v2" + + oauthserver "github.com/grafana/grafana/pkg/services/oauthserver" +) + +// MockStore is an autogenerated mock type for the Store type +type MockStore struct { + mock.Mock +} + +// GetExternalService provides a mock function with given fields: ctx, id +func (_m *MockStore) GetExternalService(ctx context.Context, id string) (*oauthserver.ExternalService, error) { + ret := _m.Called(ctx, id) + + var r0 *oauthserver.ExternalService + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*oauthserver.ExternalService, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *oauthserver.ExternalService); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*oauthserver.ExternalService) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetExternalServiceByName provides a mock function with given fields: ctx, name +func (_m *MockStore) GetExternalServiceByName(ctx context.Context, name string) (*oauthserver.ExternalService, error) { + ret := _m.Called(ctx, name) + + var r0 *oauthserver.ExternalService + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*oauthserver.ExternalService, error)); ok { + return rf(ctx, name) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *oauthserver.ExternalService); ok { + r0 = rf(ctx, name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*oauthserver.ExternalService) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetExternalServicePublicKey provides a mock function with given fields: ctx, clientID +func (_m *MockStore) GetExternalServicePublicKey(ctx context.Context, clientID string) (*jose.JSONWebKey, error) { + ret := _m.Called(ctx, clientID) + + var r0 *jose.JSONWebKey + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*jose.JSONWebKey, error)); ok { + return rf(ctx, clientID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *jose.JSONWebKey); ok { + r0 = rf(ctx, clientID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*jose.JSONWebKey) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, clientID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RegisterExternalService provides a mock function with given fields: ctx, client +func (_m *MockStore) RegisterExternalService(ctx context.Context, client *oauthserver.ExternalService) error { + ret := _m.Called(ctx, client) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *oauthserver.ExternalService) error); ok { + r0 = rf(ctx, client) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SaveExternalService provides a mock function with given fields: ctx, client +func (_m *MockStore) SaveExternalService(ctx context.Context, client *oauthserver.ExternalService) error { + ret := _m.Called(ctx, client) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *oauthserver.ExternalService) error); ok { + r0 = rf(ctx, client) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewMockStore interface { + mock.TestingT + Cleanup(func()) +} + +// NewMockStore creates a new instance of MockStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewMockStore(t mockConstructorTestingTNewMockStore) *MockStore { + mock := &MockStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/oauthserver/store/database.go b/pkg/services/oauthserver/store/database.go new file mode 100644 index 00000000000..a153701af7d --- /dev/null +++ b/pkg/services/oauthserver/store/database.go @@ -0,0 +1,219 @@ +package store + +import ( + "context" + "crypto/ecdsa" + "crypto/rsa" + "errors" + + "gopkg.in/square/go-jose.v2" + + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/services/oauthserver" + "github.com/grafana/grafana/pkg/services/oauthserver/utils" + "github.com/grafana/grafana/pkg/util/errutil" +) + +type store struct { + db db.DB +} + +func NewStore(db db.DB) oauthserver.Store { + return &store{db: db} +} + +func createImpersonatePermissions(sess *db.Session, client *oauthserver.ExternalService) error { + if len(client.ImpersonatePermissions) == 0 { + return nil + } + + insertPermQuery := make([]interface{}, 1, len(client.ImpersonatePermissions)*3+1) + insertPermStmt := `INSERT INTO oauth_impersonate_permission (client_id, action, scope) VALUES ` + for _, perm := range client.ImpersonatePermissions { + insertPermStmt += "(?, ?, ?)," + insertPermQuery = append(insertPermQuery, client.ClientID, perm.Action, perm.Scope) + } + insertPermQuery[0] = insertPermStmt[:len(insertPermStmt)-1] + _, err := sess.Exec(insertPermQuery...) + return err +} + +func registerExternalService(sess *db.Session, client *oauthserver.ExternalService) error { + insertQuery := []interface{}{ + `INSERT INTO oauth_client (name, client_id, secret, grant_types, audiences, service_account_id, public_pem, redirect_uri) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + client.Name, + client.ClientID, + client.Secret, + client.GrantTypes, + client.Audiences, + client.ServiceAccountID, + client.PublicPem, + client.RedirectURI, + } + if _, err := sess.Exec(insertQuery...); err != nil { + return err + } + + return createImpersonatePermissions(sess, client) +} + +func (s *store) RegisterExternalService(ctx context.Context, client *oauthserver.ExternalService) error { + return s.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { + return registerExternalService(sess, client) + }) +} + +func recreateImpersonatePermissions(sess *db.Session, client *oauthserver.ExternalService, prevClientID string) error { + deletePermQuery := `DELETE FROM oauth_impersonate_permission WHERE client_id = ?` + if _, errDelPerm := sess.Exec(deletePermQuery, prevClientID); errDelPerm != nil { + return errDelPerm + } + + if len(client.ImpersonatePermissions) == 0 { + return nil + } + + return createImpersonatePermissions(sess, client) +} + +func updateExternalService(sess *db.Session, client *oauthserver.ExternalService, prevClientID string) error { + updateQuery := []interface{}{ + `UPDATE oauth_client SET client_id = ?, secret = ?, grant_types = ?, audiences = ?, service_account_id = ?, public_pem = ?, redirect_uri = ? WHERE name = ?`, + client.ClientID, + client.Secret, + client.GrantTypes, + client.Audiences, + client.ServiceAccountID, + client.PublicPem, + client.RedirectURI, + client.Name, + } + if _, err := sess.Exec(updateQuery...); err != nil { + return err + } + + return recreateImpersonatePermissions(sess, client, prevClientID) +} + +func (s *store) SaveExternalService(ctx context.Context, client *oauthserver.ExternalService) error { + if client.Name == "" { + return oauthserver.ErrClientRequiredName + } + return s.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { + previous, errFetchExtSvc := getExternalServiceByName(sess, client.Name) + if errFetchExtSvc != nil { + var srcError errutil.Error + if errors.As(errFetchExtSvc, &srcError) { + if srcError.MessageID != oauthserver.ErrClientNotFoundMessageID { + return errFetchExtSvc + } + } + } + if previous == nil { + return registerExternalService(sess, client) + } + return updateExternalService(sess, client, previous.ClientID) + }) +} + +func (s *store) GetExternalService(ctx context.Context, id string) (*oauthserver.ExternalService, error) { + res := &oauthserver.ExternalService{} + if id == "" { + return nil, oauthserver.ErrClientRequiredID + } + + err := s.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { + getClientQuery := `SELECT + id, name, client_id, secret, grant_types, audiences, service_account_id, public_pem, redirect_uri + FROM oauth_client + WHERE client_id = ?` + found, err := sess.SQL(getClientQuery, id).Get(res) + if err != nil { + return err + } + if !found { + return oauthserver.ErrClientNotFound(id) + } + + impersonatePermQuery := `SELECT action, scope FROM oauth_impersonate_permission WHERE client_id = ?` + return sess.SQL(impersonatePermQuery, id).Find(&res.ImpersonatePermissions) + }) + + return res, err +} + +// GetPublicKey returns public key, issued by 'issuer', and assigned for subject. Public key is used to check +// signature of jwt assertion in authorization grants. +func (s *store) GetExternalServicePublicKey(ctx context.Context, clientID string) (*jose.JSONWebKey, error) { + res := &oauthserver.ExternalService{} + if clientID == "" { + return nil, oauthserver.ErrClientRequiredID + } + + if err := s.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { + getKeyQuery := `SELECT public_pem FROM oauth_client WHERE client_id = ?` + found, err := sess.SQL(getKeyQuery, clientID).Get(res) + if err != nil { + return err + } + if !found { + return oauthserver.ErrClientNotFound(clientID) + } + return nil + }); err != nil { + return nil, err + } + + key, errParseKey := utils.ParsePublicKeyPem(res.PublicPem) + if errParseKey != nil { + return nil, errParseKey + } + + var alg string + switch key.(type) { + case *rsa.PublicKey: + alg = oauthserver.RS256 + case *ecdsa.PublicKey: + alg = oauthserver.ES256 + } + + return &jose.JSONWebKey{ + Algorithm: alg, + Key: key, + }, nil +} + +func (s *store) GetExternalServiceByName(ctx context.Context, name string) (*oauthserver.ExternalService, error) { + res := &oauthserver.ExternalService{} + if name == "" { + return nil, oauthserver.ErrClientRequiredName + } + + err := s.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { + var errGetByName error + res, errGetByName = getExternalServiceByName(sess, name) + return errGetByName + }) + + return res, err +} + +func getExternalServiceByName(sess *db.Session, name string) (*oauthserver.ExternalService, error) { + res := &oauthserver.ExternalService{} + getClientQuery := `SELECT + id, name, client_id, secret, grant_types, audiences, service_account_id, public_pem, redirect_uri + FROM oauth_client + WHERE name = ?` + found, err := sess.SQL(getClientQuery, name).Get(res) + if err != nil { + return nil, err + } + if !found { + return nil, oauthserver.ErrClientNotFound(name) + } + + impersonatePermQuery := `SELECT action, scope FROM oauth_impersonate_permission WHERE client_id = ?` + errPerm := sess.SQL(impersonatePermQuery, res.ClientID).Find(&res.ImpersonatePermissions) + + return res, errPerm +} diff --git a/pkg/services/oauthserver/store/database_test.go b/pkg/services/oauthserver/store/database_test.go new file mode 100644 index 00000000000..0160c827831 --- /dev/null +++ b/pkg/services/oauthserver/store/database_test.go @@ -0,0 +1,376 @@ +package store + +import ( + "context" + "testing" + + "github.com/go-jose/go-jose/v3" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/oauthserver" +) + +func TestStore_RegisterAndGetClient(t *testing.T) { + s := &store{db: db.InitTestDB(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagExternalServiceAuth}})} + tests := []struct { + name string + client oauthserver.ExternalService + wantErr bool + }{ + { + name: "register and get", + client: oauthserver.ExternalService{ + Name: "The Worst App Ever", + ClientID: "ANonRandomClientID", + Secret: "ICouldKeepSecrets", + GrantTypes: "clients_credentials", + PublicPem: []byte(`------BEGIN FAKE PUBLIC KEY----- +VGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBO +b3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNB +IEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhp +cyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3Qg +QW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtl +eS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJ +cyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4g +UlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4g +VGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBO +b3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNB +IEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhp +cyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3Qg +QW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtl +eS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJ +cyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4g +UlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4g +VGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBO +b3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNB +IEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhp +cyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4uLi4gSXQgSXMgSnVz +dCBBIFJlZ3VsYXIgQmFzZTY0IEVuY29kZWQgU3RyaW5nLi4uCg== +------END FAKE PUBLIC KEY-----`), + ServiceAccountID: 2, + SelfPermissions: nil, + ImpersonatePermissions: nil, + RedirectURI: "/whereto", + }, + wantErr: false, + }, + { + name: "register with impersonate permissions and get", + client: oauthserver.ExternalService{ + Name: "The Best App Ever", + ClientID: "AnAlmostRandomClientID", + Secret: "ICannotKeepSecrets", + GrantTypes: "clients_credentials", + PublicPem: []byte(`test`), + ServiceAccountID: 2, + SelfPermissions: nil, + ImpersonatePermissions: []accesscontrol.Permission{ + {Action: "dashboards:create", Scope: "folders:*"}, + {Action: "dashboards:read", Scope: "folders:*"}, + {Action: "dashboards:read", Scope: "dashboards:*"}, + {Action: "dashboards:write", Scope: "folders:*"}, + {Action: "dashboards:write", Scope: "dashboards:*"}, + }, + RedirectURI: "/whereto", + }, + wantErr: false, + }, + { + name: "register with audiences and get", + client: oauthserver.ExternalService{ + Name: "The Most Normal App Ever", + ClientID: "AnAlmostRandomClientIDAgain", + Secret: "ICanKeepSecretsEventually", + GrantTypes: "clients_credentials", + PublicPem: []byte(`test`), + ServiceAccountID: 2, + SelfPermissions: nil, + Audiences: "https://oauth.test/,https://sub.oauth.test/", + RedirectURI: "/whereto", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + err := s.RegisterExternalService(ctx, &tt.client) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + // Compare results + compareClientToStored(t, s, &tt.client) + }) + } +} + +func TestStore_SaveExternalService(t *testing.T) { + client1 := oauthserver.ExternalService{ + Name: "my-external-service", + ClientID: "ClientID", + Secret: "Secret", + GrantTypes: "client_credentials", + PublicPem: []byte("test"), + ServiceAccountID: 2, + ImpersonatePermissions: []accesscontrol.Permission{}, + RedirectURI: "/whereto", + } + client1WithPerm := client1 + client1WithPerm.ImpersonatePermissions = []accesscontrol.Permission{ + {Action: "dashboards:read", Scope: "folders:*"}, + {Action: "dashboards:read", Scope: "dashboards:*"}, + } + client1WithNewSecrets := client1 + client1WithNewSecrets.ClientID = "NewClientID" + client1WithNewSecrets.Secret = "NewSecret" + client1WithNewSecrets.PublicPem = []byte("newtest") + + client1WithAud := client1 + client1WithAud.Audiences = "https://oauth.test/,https://sub.oauth.test/" + + tests := []struct { + name string + runs []oauthserver.ExternalService + wantErr bool + }{ + { + name: "error no name", + runs: []oauthserver.ExternalService{{}}, + wantErr: true, + }, + { + name: "simple register", + runs: []oauthserver.ExternalService{client1}, + wantErr: false, + }, + { + name: "no update", + runs: []oauthserver.ExternalService{client1, client1}, + wantErr: false, + }, + { + name: "add permissions", + runs: []oauthserver.ExternalService{client1, client1WithPerm}, + wantErr: false, + }, + { + name: "remove permissions", + runs: []oauthserver.ExternalService{client1WithPerm, client1}, + wantErr: false, + }, + { + name: "update id and secrets", + runs: []oauthserver.ExternalService{client1, client1WithNewSecrets}, + wantErr: false, + }, + { + name: "update audience", + runs: []oauthserver.ExternalService{client1, client1WithAud}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &store{db: db.InitTestDB(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagExternalServiceAuth}})} + for i := range tt.runs { + err := s.SaveExternalService(context.Background(), &tt.runs[i]) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + compareClientToStored(t, s, &tt.runs[i]) + } + }) + } +} + +func TestStore_GetExternalServiceByName(t *testing.T) { + client1 := oauthserver.ExternalService{ + Name: "my-external-service", + ClientID: "ClientID", + Secret: "Secret", + GrantTypes: "client_credentials", + PublicPem: []byte("test"), + ServiceAccountID: 2, + ImpersonatePermissions: []accesscontrol.Permission{}, + RedirectURI: "/whereto", + } + client2 := oauthserver.ExternalService{ + Name: "my-external-service-2", + ClientID: "ClientID2", + Secret: "Secret2", + GrantTypes: "client_credentials,urn:ietf:params:grant-type:jwt-bearer", + PublicPem: []byte("test2"), + ServiceAccountID: 3, + Audiences: "https://oauth.test/,https://sub.oauth.test/", + ImpersonatePermissions: []accesscontrol.Permission{ + {Action: "dashboards:read", Scope: "folders:*"}, + {Action: "dashboards:read", Scope: "dashboards:*"}, + }, + RedirectURI: "/whereto", + } + s := &store{db: db.InitTestDB(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagExternalServiceAuth}})} + require.NoError(t, s.SaveExternalService(context.Background(), &client1)) + require.NoError(t, s.SaveExternalService(context.Background(), &client2)) + + tests := []struct { + name string + search string + want *oauthserver.ExternalService + wantErr bool + }{ + { + name: "no name provided", + search: "", + want: nil, + wantErr: true, + }, + { + name: "not found", + search: "unknown-external-service", + want: nil, + wantErr: true, + }, + { + name: "search client 1 by name", + search: "my-external-service", + want: &client1, + wantErr: false, + }, + { + name: "search client 2 by name", + search: "my-external-service-2", + want: &client2, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stored, err := s.GetExternalServiceByName(context.Background(), tt.search) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + compareClients(t, stored, tt.want) + }) + } +} + +func TestStore_GetExternalServicePublicKey(t *testing.T) { + clientID := "ClientID" + createClient := func(clientID string, publicPem string) *oauthserver.ExternalService { + return &oauthserver.ExternalService{ + Name: "my-external-service", + ClientID: clientID, + Secret: "Secret", + GrantTypes: "client_credentials", + PublicPem: []byte(publicPem), + ServiceAccountID: 2, + ImpersonatePermissions: []accesscontrol.Permission{}, + RedirectURI: "/whereto", + } + } + + testCases := []struct { + name string + client *oauthserver.ExternalService + clientID string + want *jose.JSONWebKey + wantKeyType string + wantErr bool + }{ + { + name: "should return an error when clientID is empty", + clientID: "", + client: createClient(clientID, ""), + want: nil, + wantErr: true, + }, + { + name: "should return an error when the client was not found", + clientID: "random", + client: createClient(clientID, ""), + want: nil, + wantErr: true, + }, + { + name: "should return an error when PublicPem is not valid", + clientID: clientID, + client: createClient(clientID, ""), + want: nil, + wantErr: true, + }, + { + name: "should return the JSON Web Key ES256", + clientID: clientID, + client: createClient(clientID, `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbsGtoGJTopAIbhqy49/vyCJuDot+ +mgGaC8vUIigFQVsVB+v/HZ4yG1Rcvysig+tyNk1dZQpozpFc2dGmzHlGhw== +-----END PUBLIC KEY-----`), + wantKeyType: oauthserver.ES256, + wantErr: false, + }, + { + name: "should return the JSON Web Key RS256", + clientID: clientID, + client: createClient(clientID, `-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAxkly/cHvsxd6EcShGUlFAB5lIMlIbGRocCVWbIM26f6pnGr+gCNv +s365DQdQ/jUjF8bSEQM+EtjGlv2Y7Jm7dQROpPzX/1M+53Us/Gl138UtAEgL5ZKe +SKN5J/f9Nx4wkgb99v2Bt0nz6xv+kSJwgR0o8zi8shDR5n7a5mTdlQe2NOixzWlT +vnpp6Tm+IE+XyXXcrCr01I9Rf+dKuYOPSJ1K3PDgFmmGvsLcjRCCK9EftfY0keU+ +IP+sh8ewNxc6KcaLBXm3Tadb1c/HyuMi6FyYw7s9m8tyAvI1CMBAcXqLIEaRgNrc +vuO8AU0bVoUmYMKhozkcCYHudkeS08hEjQIDAQAB +-----END RSA PUBLIC KEY-----`), + wantKeyType: oauthserver.RS256, + wantErr: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + s := &store{db: db.InitTestDB(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagExternalServiceAuth}})} + require.NoError(t, s.SaveExternalService(context.Background(), tc.client)) + + webKey, err := s.GetExternalServicePublicKey(context.Background(), tc.clientID) + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + require.Equal(t, tc.wantKeyType, webKey.Algorithm) + }) + } +} + +func compareClientToStored(t *testing.T, s *store, wanted *oauthserver.ExternalService) { + ctx := context.Background() + stored, err := s.GetExternalService(ctx, wanted.ClientID) + require.NoError(t, err) + require.NotNil(t, stored) + + compareClients(t, stored, wanted) +} + +func compareClients(t *testing.T, stored *oauthserver.ExternalService, wanted *oauthserver.ExternalService) { + // Reset ID so we can compare + require.NotZero(t, stored.ID) + stored.ID = 0 + + // Compare permissions separately + wantedPerms := wanted.ImpersonatePermissions + storedPerms := stored.ImpersonatePermissions + wanted.ImpersonatePermissions = nil + stored.ImpersonatePermissions = nil + require.EqualValues(t, *wanted, *stored) + require.ElementsMatch(t, wantedPerms, storedPerms) +} diff --git a/pkg/services/oauthserver/utils/utils.go b/pkg/services/oauthserver/utils/utils.go new file mode 100644 index 00000000000..32dab615e70 --- /dev/null +++ b/pkg/services/oauthserver/utils/utils.go @@ -0,0 +1,35 @@ +package utils + +import ( + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "strconv" + "strings" + + "github.com/grafana/grafana/pkg/services/authn" +) + +// ParseUserIDFromSubject parses the user ID from format "user:id:". +func ParseUserIDFromSubject(subject string) (int64, error) { + trimmed := strings.TrimPrefix(subject, fmt.Sprintf("%s:id:", authn.NamespaceUser)) + return strconv.ParseInt(trimmed, 10, 64) +} + +// ParsePublicKeyPem parses the public key from the PEM encoded public key. +func ParsePublicKeyPem(publicPem []byte) (interface{}, error) { + block, _ := pem.Decode(publicPem) + if block == nil { + return nil, errors.New("could not decode PEM block") + } + + switch block.Type { + case "PUBLIC KEY": + return x509.ParsePKIXPublicKey(block.Bytes) + case "RSA PUBLIC KEY": + return x509.ParsePKCS1PublicKey(block.Bytes) + default: + return nil, fmt.Errorf("unknown key type %q", block.Type) + } +} diff --git a/pkg/services/oauthserver/utils/utils_test.go b/pkg/services/oauthserver/utils/utils_test.go new file mode 100644 index 00000000000..8da34460e6d --- /dev/null +++ b/pkg/services/oauthserver/utils/utils_test.go @@ -0,0 +1,82 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParsePublicKeyPem(t *testing.T) { + testCases := []struct { + name string + publicKeyPem string + wantErr bool + }{ + { + name: "should return error when the public key pem is empty", + publicKeyPem: "", + wantErr: true, + }, + { + name: "should return error when the public key pem is invalid", + publicKeyPem: `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxP72NEnQF3o3eFFMtFqyloW9oLhTydxXS2dA2NolMvXewO77 +UvJw54wkOdrJrJO2BIw+XBrrb+13+koRUnwa2DNsh+SWG0PEe/31mt0zJrCmNM37 +iJYIu3KZR2aRlierVY5gyrIniBIZ9blQspI6SRY9xmo6Wdh0VCRnsCV5sMlaqerI +snLpYOjGtMmL0rFuW2jKrAzpbq7L99IDgPbiH7tluaQkGIxoc29S4wjwg0NgQONT +GkfJEeXQIkxOHNM5WGb8mvjX4U0jMdXvC4WUWcS+KpcIV7ee8uEs2xDz++N4HYAS +ty37sY8wwW22QUW9I7XlSC4rsC88Ft5ar8yLsQIDAQABAoIBAAQ1yTv+mFmKGYGj +JiskFZVBNDdpPRQvNvfj8+c2iU08ozc3HEyuZQKT1InefsknCoCwIRyNkDrPBc2F +8/cR8y5r8e25EUqxoPM/7xXxVIinBZRTEyU9BKCB71vu6Z1eiWs9jNzEIDNopKCj +ZmG8nY2Gkckp58eYCEtskEE72c0RBPg8ZTBdc1cLqbNVUjkLvR5e98ruDz6b+wyH +FnztZ0k48zM047Ior69OwFRBg+S7d6cgMMmcq4X2pg3xgQMs0Se/4+pmvBf9JPSB +kl3qpVAkzM1IFdrmpFtBzeaqYNj3uU6Bm7NxEiqjAoeDxO231ziSdzIPtXIy5eRl +9WMZCqkCgYEA1ZOaT77aa54zgjAwjNB2Poo3yoUtYJz+yNCR0CPM4MzCas3PR4XI +WUXo+RNofWvRJF88aAVX7+J0UTnRr25rN12NDbo3aZhX2YNDGBe3hgB/FOAI5UAh +9SaU070PFeGzqlu/xWdx5GFk/kiNUNLX/X4xgUGPTiwY4LQeI9lffzkCgYEA7CA7 +VHaNPGVsaNKMJVjrZeYOxNBsrH99IEgaP76DC+EVR2JYVzrNxmN6ZlRxD4CRTcyd +oquTFoFFw26gJIJAYF8MtusOD3PArnpdCRSoELezYdtVhS0yx8TSHGVC9MWSSt7O +IdjzEFpA99HPkYFjXUiWXjfCTK7ofI0RXC6a+DkCgYEAoQb8nYuEGwfYRhwXPtQd +kuGbVvI6WFGGN9opVgjn+8Xl/6jU01QmzkhLcyAS9B1KPmYfoT4GIzNWB7fURLS3 +2bKLGwJ/rPnTooe5Gn0nPb06E38mtdI4yCEirNIqgZD+aT9rw2ZPFKXqA16oTXvq +pZFzucS4S3Qr/Z9P6i+GNOECgYBkvPuS9WEcO0kdD3arGFyVhKkYXrN+hIWlmB1a +xLS0BLtHUTXPQU85LIez0KLLslZLkthN5lVCbLSOxEueR9OfSe3qvC2ref7icWHv +1dg+CaGGRkUeJEJd6CKb6re+Jexb9OKMnjpU56yADgs4ULNLwQQl/jPu81BMkwKt +CVUkQQKBgFvbuUmYtP3aqV/Kt036Q6aB6Xwg29u2XFTe4BgW7c55teebtVmGA/zc +GMwRsF4rWCcScmHGcSKlW9L6S6OxmkYjDDRhimKyHgoiQ9tawWag2XCeOlyJ+hkc +/qwwKxScuFIi2xwT+aAmR70Xk11qXTft+DaEcHdxOOZD8gA0Gxr3 +-----END RSA PRIVATE KEY-----`, + wantErr: true, + }, + { + name: "should parse the public key if it is in PKCS1 format", + publicKeyPem: `-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAy06MeS06Ea7zGKfOM8kosxuUBMNhrWKWMvW4Jq1IXG+lyTfann2+ +kI1rKeWAQ9YbxNzLynahoKN47EQ6mqM1Yj5v9iKWtSvCMKHWBuqrG5ksaEQaAVsA +PDg8aOQrI1MSW9Hoc1CummcWX+HKNPVwIzG3sCboENFzEG8GrJgoNHZgmyOYEMMD +2WCdfY0I9Dm0/uuNMAcyMuVhRhOtT3j91zCXvDju2+M2EejApMkV9r7FqGmNH5Hw +8u43nWXnWc4UYXEItE8nPxuqsZia2mdB5MSIdKu8a7ytFcQ+tiK6vempnxHZytEL +6NDX8DLydHbEsLUn6hc76ODVkr/wRiuYdQIDAQAB +-----END RSA PUBLIC KEY-----`, + wantErr: false, + }, + { + name: "should parse the public key if it is in PKIX/X.509 format", + publicKeyPem: `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbsGtoGJTopAIbhqy49/vyCJuDot+ +mgGaC8vUIigFQVsVB+v/HZ4yG1Rcvysig+tyNk1dZQpozpFc2dGmzHlGhw== +-----END PUBLIC KEY-----`, + wantErr: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := ParsePublicKeyPem([]byte(tc.publicKeyPem)) + if tc.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/services/serviceaccounts/tests/mocks.go b/pkg/services/serviceaccounts/tests/mocks.go new file mode 100644 index 00000000000..0d06a560e2b --- /dev/null +++ b/pkg/services/serviceaccounts/tests/mocks.go @@ -0,0 +1,52 @@ +package tests + +import ( + "context" + + "github.com/stretchr/testify/mock" + + "github.com/grafana/grafana/pkg/services/apikey" + "github.com/grafana/grafana/pkg/services/serviceaccounts" +) + +var _ serviceaccounts.Service = &MockServiceAccountService{} + +type MockServiceAccountService struct { + mock.Mock +} + +// AddServiceAccountToken implements serviceaccounts.Service +func (s *MockServiceAccountService) AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *serviceaccounts.AddServiceAccountTokenCommand) (*apikey.APIKey, error) { + mockedArgs := s.Called(ctx, serviceAccountID, cmd) + return mockedArgs.Get(0).(*apikey.APIKey), mockedArgs.Error(1) +} + +// CreateServiceAccount implements serviceaccounts.Service +func (s *MockServiceAccountService) CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error) { + mockedArgs := s.Called(ctx, orgID, saForm) + return mockedArgs.Get(0).(*serviceaccounts.ServiceAccountDTO), mockedArgs.Error(1) +} + +// DeleteServiceAccount implements serviceaccounts.Service +func (s *MockServiceAccountService) DeleteServiceAccount(ctx context.Context, orgID int64, serviceAccountID int64) error { + mockedArgs := s.Called(ctx, orgID, serviceAccountID) + return mockedArgs.Error(0) +} + +// RetrieveServiceAccount implements serviceaccounts.Service +func (s *MockServiceAccountService) RetrieveServiceAccount(ctx context.Context, orgID int64, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) { + mockedArgs := s.Called(ctx, orgID, serviceAccountID) + return mockedArgs.Get(0).(*serviceaccounts.ServiceAccountProfileDTO), mockedArgs.Error(1) +} + +// RetrieveServiceAccountIdByName implements serviceaccounts.Service +func (s *MockServiceAccountService) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) { + mockedArgs := s.Called(ctx, orgID, name) + return mockedArgs.Get(0).(int64), mockedArgs.Error(1) +} + +// UpdateServiceAccount implements serviceaccounts.Service +func (s *MockServiceAccountService) UpdateServiceAccount(ctx context.Context, orgID int64, serviceAccountID int64, saForm *serviceaccounts.UpdateServiceAccountForm) (*serviceaccounts.ServiceAccountProfileDTO, error) { + mockedArgs := s.Called(ctx, orgID, serviceAccountID) + return mockedArgs.Get(0).(*serviceaccounts.ServiceAccountProfileDTO), mockedArgs.Error(1) +} diff --git a/pkg/services/signingkeys/signingkeysimpl/service.go b/pkg/services/signingkeys/signingkeysimpl/service.go index 52fb4b867e0..8dd51795c0d 100644 --- a/pkg/services/signingkeys/signingkeysimpl/service.go +++ b/pkg/services/signingkeys/signingkeysimpl/service.go @@ -2,13 +2,13 @@ package signingkeysimpl import ( "crypto" + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" - "crypto/rsa" "github.com/go-jose/go-jose/v3" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/signingkeys" ) @@ -18,13 +18,13 @@ const ( var _ signingkeys.Service = new(Service) -func ProvideEmbeddedSigningKeysService(features *featuremgmt.FeatureManager) (*Service, error) { +func ProvideEmbeddedSigningKeysService() (*Service, error) { s := &Service{ log: log.New("auth.key_service"), keys: map[string]crypto.Signer{}, } - privateKey, err := rsa.GenerateKey(rand.Reader, 4096) + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { s.log.Error("Error generating private key", "err", err) return nil, signingkeys.ErrKeyGenerationFailed.Errorf("Error generating private key: %v", err) diff --git a/pkg/services/signingkeys/signingkeysimpl/service_test.go b/pkg/services/signingkeys/signingkeysimpl/service_test.go index f4c891820b9..01baea39326 100644 --- a/pkg/services/signingkeys/signingkeysimpl/service_test.go +++ b/pkg/services/signingkeys/signingkeysimpl/service_test.go @@ -2,7 +2,7 @@ package signingkeysimpl import ( "crypto" - "crypto/rsa" + "crypto/ecdsa" "crypto/x509" "encoding/json" "encoding/pem" @@ -15,64 +15,17 @@ import ( ) const ( - privateKeyPem = `-----BEGIN RSA PRIVATE KEY----- -MIIJJgIBAAKCAgBixs4SiJylE8NwaR/AN2gr/XWgTfFqwg3m7rm018MSmMZxph77 -lZ96n/UqaAtEL9wCHjU0/76dhMtn6yGXmS9s3zTwOfuy5Hv4ai0PjEoRrxdtbKT8 -u0F0N7HJupBeUBZ86ELhlTw+OgOqxbWv/V6uN81UG/tadaR00k9yyfcT0noCE+3a -5l4OT7q2ILJL5nvyKgwcZJxGfoBwkGX42BZuIxZ4ANx3Mz/uQrkRMg+5bDDYgvlV -OsEhoDHmq4DsRODeVyCN0If0HL0fPIUoVv8C87igVnTq3ScxikypndK1uytKLTJP -ZsenbyfLyvR/jBAu2WZVYS0JSYAxN+4wJH8H1dLotYXpn/YSPBAsR/EHi4kpu5v+ -OBSGhMl21ZSeNNFUqX/YnRjYEYGgQuhYRnfzFaROUh3bWq25WC7bxTWwqtnA1FX2 -Vqr0tgNly0hCr+KP/kkUe7xiGzjBIC+A89b7y70l3m3j/kTj3TXVSzcwn7aGOO8X -OILw/x7vF08LYC26wLBOk2uPcraR5aKNy6KPhy8rMYLv8u4jNzGP8Y6ISMYyBv5N -tJ5BLHn80hbx/Vo5zADJ8WeMIUmtxLRD6oedX8za5Jpa3b71cx55zFhYiVThKeS2 -by9PKi2xurd5AYWVtJBr2azTMFY2FdGVbB02/21twepQXrRl17ucfaxapQIDAQAB -AoICAEO2QQHXgHpxR+LBTbC4ysKNJ5tSkxI6IMmUEN31opYXAMJbvJV+hirLiIcf -d8mwfUM+bf786jCVHdMJDqgbrLUXdfTP6slBc/Jg5q7n3sasnoS2m4tc2ovOuiOt -rtXYVPIfTenSIdAOeQESM3CHYeZP/oOQAwiJ6Mjkeu4XoTaHbHgMLVuH3CY3ZakA -VPlO8NybEl5MYgy5H1cKxbyGdSnfB8IP5RIZodO1DaTKCplznzBs6HsSod5pMIwO -OXy94uDIHVrZ/rjLEqJdHHMA4COn64KOgeuW2w1M3yzPMei+e/iHbxubO3Z97mv3 -nw/odheHlG0nBnZ9WlFjI/cArctWjqSfs7mEX6aV+Ity0+msMWWgrjg6l0y0rlqa -odYt2KIzyAcsFiZCUUgsmNRzB8kVycNwjDFpW24ZvwWtakvH/uZ/lK5jloXOF3Id -TTf4T+h6vtHjEMzfOKmrp2fycfgjavBEX/VMASHooB5H2lzB9poSC9k1V+HAnirq -s5PSehX2QnHvuFCG47iFN1xX747hESph1plzO17xMsKQnWPDQw8ega3fkW3MMQdx -wFOriHYZBk2o7pQ6aSErMMqlVM9PS2HXHTOV4ejAEYsFtnGqfZB3RSt3+4DIhyjo -+YS4At/nfWMyxTo5R/9EkuTCzZTfPVEq/7E8gPsK8c3GC/xpAoIBAQC51/DUpDtv -PsU02PO7m/+u5s1OJLE/PybUI0fNTfL+DAdzOfFlrJkyfhYNBOx2BrhYZsL4bZb/ -nvAj7L8nvUDTeNdhejrR3SFom8U9cxM28ZCBNNn9rCnkSNPdn5FsUr8QqOEJwWZG -6KXJ/c019LV+0ncn7fN5GYnPhlVgQCmAnSxudwRmH0uqXhV/p1F+veTe4TL/CHXf -ZrcW01pYlNtRB7D4bQ9YMPxgKaNNl8IcpZdKCocxImbTJeSn6nz7ZeMCVeUP/BuP -a2aBWe76xvxubm+NZbzcsj3b8tAYngAaL/yh9+uX3yqVA2Y5DR+0m5qgYehTlqET -jf1cXA9oA/JfAoIBAQCIEKYswfIRQUXoUx905KWT4leVaWRI37nQVjrVYG6ElN26 -mMMIKlN/BghteZB3Wrh9p4ZkHlLMMpXj6vRZRhpgjfiOxeiIkjDdQYQao/q7ZStr -H0G37lOiboxmMWpLI2XOrNAlTYmDCVVTSjoF0zxvMzIyvRV488X6tI8LAIf3QjDj -+6IrJH1RF1AGwLSeD07JWq4F3epg6BwEXlMePCwUr8cUYAIrPlGWT/ywP9ZKX5Wt -mNEZEgaWAohvdXGbkG4cQuIT2fvd2HvYDjbr9CvQDV5tHIE36jUrlbzVRHYxp0QQ -XbPTTN9On6fSueYoFy47CtXJOHrbZ+r74CU0yHl7AoIBACwQYl7YzerTlEiyhB/g -niAnQ1ia5JfdbmRwNQ8dw1avHXkZrP3xjaVmNe5CU5qsfzseqm3i9iGH2uJ5uN1A -R0Wc6lyHcbje2JQIEx090rl9T0kDcghusMQa7Hko438uo3TcxfbdL1XyxZR+JBD+ -A6adWnlSNx9oib9113pp3C1NlwJeH+Hi27r6cdiBoJYPilu6Q7AqnmAo55J27H4C -VXoB+9j7at77Rmu6k6jLKdBHBvccRe/Fe2HnIy8ZLycgglHEcfp3SUWZLoXPABXf -5mx8rOB21e/yJy6mhObBV77dz+XLdcXduSf51VwDm5fkKSaL8F0ZYvnS+dbTUSfV -f7sCggEAOQPw/jRPARf++TlLpyngkDV6Seud0EOfk0Nu59a+uOPAfd5ha1yBHGsk -wOr9tGXZhR3b3LwwKczQrm7X8UjE6MzU6M7Zf9DylORNPPSVrkzYgszYNwCxHxF/ -15rBVbcBhDc6CUeSZcxVas9hvOslGdu0HzrIcqSDw2hBwHR6hQvBfOcGr1ldAcvp -BstdZBY6B3nuDhtNiUn544K7BaJlPk3h+BG7Fu/INFpUIm69lvCywcmVZRH+nIF3 -Nm1aK7u7yC/mmDbxqaZ7Tq+2J+1rJoVTmhkltI55tUfLlvpXJLtYdBsvrU07DbEt -G8o2PXppLuh9aRI3uRS0jNMCBDo1XQKCAQAa2CsPi/ey9JzgUkBJvVusv7fF8hYB -4Nno4PXiRezIGbT9WitZU5lQhfw0g9XokyQqKwSS6iEtuSRE92P6XLB0jswQQ/Jc -5yWX9DqjKKE4dtpS4/VfkdfE6daIqtFCfE3gybnah/FWPAtYY4iC1207lZQjAp91 -OFOV2sfpk4ZIwnSJBvY0O5Brt/nbHkFUzxJRFgERD7zRrFOU9mZdEUfR9jvj4xlI -NcKeaYuoa4nWwuLEEzNTQqcS8ccOrpGTZQP2ffpyZdY42q4N8UggTdAcwOtQ6a6L -D3U+YcnG00aa3FnNN5EjOnY4FeIUJwpqzB8mDc0ztHdwOoJhDETWroDq ------END RSA PRIVATE KEY-----` + privateKeyPem = `-----BEGIN PRIVATE KEY----- +MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAv8mcYDAugBtzfGYP9 +ielIkb6/Ys51o7KjHxtANhPesw== +-----END PRIVATE KEY-----` ) -func getPrivateKey(t *testing.T) *rsa.PrivateKey { +func getPrivateKey(t *testing.T) *ecdsa.PrivateKey { pemBlock, _ := pem.Decode([]byte(privateKeyPem)) - privateKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes) + privateKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes) require.NoError(t, err) - return privateKey + return privateKey.(*ecdsa.PrivateKey) } func setupTestService(t *testing.T) *Service { @@ -271,6 +224,15 @@ func TestEmbeddedKeyService_AddPrivateKey(t *testing.T) { } } +func TestProvideEmbeddedSigningKeysService(t *testing.T) { + s, err := ProvideEmbeddedSigningKeysService() + require.NoError(t, err) + require.NotNil(t, s) + + // Verify that ProvideEmbeddedSigningKeysService generates an ECDSA private key by default + require.IsType(t, &ecdsa.PrivateKey{}, s.GetServerPrivateKey()) +} + type dummyPrivateKey struct { } diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index 58c8ed5ad12..75867ea2f1d 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -1,7 +1,9 @@ package migrations import ( + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore/migrations/accesscontrol" + "github.com/grafana/grafana/pkg/services/sqlstore/migrations/oauthserver" "github.com/grafana/grafana/pkg/services/sqlstore/migrations/ualert" . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" ) @@ -89,6 +91,11 @@ func (*OSSMigrations) AddMigration(mg *Migrator) { AddExternalAlertmanagerToDatasourceMigration(mg) addFolderMigrations(mg) + if mg.Cfg != nil && mg.Cfg.IsFeatureToggleEnabled != nil { + if mg.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagExternalServiceAuth) { + oauthserver.AddMigration(mg) + } + } } func addStarMigrations(mg *Migrator) { diff --git a/pkg/services/sqlstore/migrations/oauthserver/migrations.go b/pkg/services/sqlstore/migrations/oauthserver/migrations.go new file mode 100644 index 00000000000..b47590e7b25 --- /dev/null +++ b/pkg/services/sqlstore/migrations/oauthserver/migrations.go @@ -0,0 +1,52 @@ +package oauthserver + +import "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + +func AddMigration(mg *migrator.Migrator) { + impersonatePermissionsTable := migrator.Table{ + Name: "oauth_impersonate_permission", + Columns: []*migrator.Column{ + {Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, + {Name: "client_id", Type: migrator.DB_Varchar, Length: 190, Nullable: false}, + {Name: "action", Type: migrator.DB_Varchar, Length: 190, Nullable: false}, + {Name: "scope", Type: migrator.DB_Varchar, Length: 190, Nullable: true}, + }, + Indices: []*migrator.Index{ + {Cols: []string{"client_id", "action", "scope"}, Type: migrator.UniqueIndex}, + }, + } + + clientTable := migrator.Table{ + Name: "oauth_client", + Columns: []*migrator.Column{ + {Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, + {Name: "name", Type: migrator.DB_Varchar, Length: 190, Nullable: true}, + {Name: "client_id", Type: migrator.DB_Varchar, Length: 190, Nullable: false}, + {Name: "secret", Type: migrator.DB_Varchar, Length: 190, Nullable: false}, + {Name: "grant_types", Type: migrator.DB_Text, Nullable: true}, + {Name: "audiences", Type: migrator.DB_Varchar, Length: 190, Nullable: true}, + {Name: "service_account_id", Type: migrator.DB_BigInt, Nullable: true}, + {Name: "public_pem", Type: migrator.DB_Text, Nullable: true}, + {Name: "redirect_uri", Type: migrator.DB_Varchar, Length: 190, Nullable: true}, + }, + Indices: []*migrator.Index{ + {Cols: []string{"client_id"}, Type: migrator.UniqueIndex}, + {Cols: []string{"client_id", "service_account_id"}, Type: migrator.UniqueIndex}, + {Cols: []string{"name"}, Type: migrator.UniqueIndex}, + }, + } + + // Impersonate Permission + mg.AddMigration("create impersonate permissions table", migrator.NewAddTableMigration(impersonatePermissionsTable)) + + //------- indexes ------------------ + mg.AddMigration("add unique index client_id action scope", migrator.NewAddIndexMigration(impersonatePermissionsTable, impersonatePermissionsTable.Indices[0])) + + // Client + mg.AddMigration("create client table", migrator.NewAddTableMigration(clientTable)) + + //------- indexes ------------------ + mg.AddMigration("add unique index client_id", migrator.NewAddIndexMigration(clientTable, clientTable.Indices[0])) + mg.AddMigration("add unique index client_id service_account_id", migrator.NewAddIndexMigration(clientTable, clientTable.Indices[1])) + mg.AddMigration("add unique index name", migrator.NewAddIndexMigration(clientTable, clientTable.Indices[2])) +} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 9af3a1c2cf1..d6bd30ba4db 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -509,6 +509,13 @@ type Cfg struct { OktaAuthEnabled bool OktaSkipOrgRoleSync bool + // OAuth2 Server + OAuth2ServerEnabled bool + + // OAuth2Server supports the two recommended key types from the RFC https://www.rfc-editor.org/rfc/rfc7518#section-3.1: RS256 and ES256 + OAuth2ServerGeneratedKeyTypeForClient string + OAuth2ServerAccessTokenLifespan time.Duration + // Access Control RBACEnabled bool RBACPermissionCache bool @@ -1045,6 +1052,9 @@ func (cfg *Cfg) Load(args CommandLineArgs) error { if err := readAuthSettings(iniFile, cfg); err != nil { return err } + + readOAuth2ServerSettings(cfg) + readAccessControlSettings(iniFile, cfg) if err := cfg.readRenderingSettings(iniFile); err != nil { return err @@ -1595,6 +1605,13 @@ func readAccessControlSettings(iniFile *ini.File, cfg *Cfg) { cfg.RBACResetBasicRoles = rbac.Key("reset_basic_roles").MustBool(false) } +func readOAuth2ServerSettings(cfg *Cfg) { + oauth2Srv := cfg.SectionWithEnvOverrides("oauth2_server") + cfg.OAuth2ServerEnabled = oauth2Srv.Key("enabled").MustBool(false) + cfg.OAuth2ServerGeneratedKeyTypeForClient = strings.ToUpper(oauth2Srv.Key("generated_key_type_for_client").In("ECDSA", []string{"RSA", "ECDSA"})) + cfg.OAuth2ServerAccessTokenLifespan = oauth2Srv.Key("access_token_lifespan").MustDuration(time.Minute * 3) +} + func readUserSettings(iniFile *ini.File, cfg *Cfg) error { users := iniFile.Section("users") AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)