diff --git a/README.md b/README.md index 5ff92070..2c263ef5 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Here is what Authelia's portal looks like Here is the list of the main available features: -* Several kind of second factor: +* Several second factor methods: * **[Security Key (U2F)](https://www.authelia.com/docs/features/2fa/security-key)** with [Yubikey]. * **[Time-based One-Time password](https://www.authelia.com/docs/features/2fa/one-time-password)** with [Google Authenticator]. @@ -61,6 +61,7 @@ Here is the list of the main available features: * Access restriction after too many authentication attempts. * Fine-grained access control per subdomain, user, resource and network. * Support of basic authentication for endpoints protected by single factor. +* Beta support for [OpenID Connect](https://www.authelia.com/docs/configuration/identity-providers/oidc.html). * Highly available using a remote database and Redis as a highly available KV store. * Compatible with Kubernetes [ingress-nginx](https://github.com/kubernetes/ingress-nginx) controller out of the box. diff --git a/cmd/authelia-scripts/cmd_bootstrap.go b/cmd/authelia-scripts/cmd_bootstrap.go index 1c10a953..49b21af0 100644 --- a/cmd/authelia-scripts/cmd_bootstrap.go +++ b/cmd/authelia-scripts/cmd_bootstrap.go @@ -59,6 +59,9 @@ var hostEntries = []HostEntry{ // Kubernetes dashboard. {Domain: "kubernetes.example.com", IP: "192.168.240.110"}, + // OIDC tester app + {Domain: "oidc.example.com", IP: "192.168.240.100"}, + {Domain: "oidc-public.example.com", IP: "192.168.240.100"}, } func runCommand(cmd string, args ...string) { diff --git a/cmd/authelia-scripts/main.go b/cmd/authelia-scripts/main.go index ec1a29d0..c3c3d05d 100755 --- a/cmd/authelia-scripts/main.go +++ b/cmd/authelia-scripts/main.go @@ -59,7 +59,7 @@ var Commands = []AutheliaCommandDefinition{ }, { Name: "suites", - Short: "Compute hash of a password for creating a file-based users database", + Short: "Commands related to suites management", SubCommands: CobraCommands{ SuitesTestCmd, SuitesListCmd, @@ -135,7 +135,7 @@ func main() { cobraCommands = append(cobraCommands, command) } - cobraCommands = append(cobraCommands, commands.HashPasswordCmd) + cobraCommands = append(cobraCommands, commands.HashPasswordCmd, commands.CertificatesCmd, commands.RSACmd) rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Set the log level for the command") rootCmd.AddCommand(cobraCommands...) diff --git a/cmd/authelia-suites/main.go b/cmd/authelia-suites/main.go index 26515777..271df298 100644 --- a/cmd/authelia-suites/main.go +++ b/cmd/authelia-suites/main.go @@ -83,7 +83,7 @@ func setupSuite(cmd *cobra.Command, args []string) { suiteResourcePath := cwd + "/internal/suites/" + suiteName - exist, err := utils.FileExists(suiteResourcePath) + exist, err := utils.PathExists(suiteResourcePath) if err != nil { log.Fatal(err) diff --git a/cmd/authelia/main.go b/cmd/authelia/main.go index 58a92592..8e125420 100644 --- a/cmd/authelia/main.go +++ b/cmd/authelia/main.go @@ -14,6 +14,7 @@ import ( "github.com/authelia/authelia/internal/logging" "github.com/authelia/authelia/internal/middlewares" "github.com/authelia/authelia/internal/notification" + "github.com/authelia/authelia/internal/oidc" "github.com/authelia/authelia/internal/regulation" "github.com/authelia/authelia/internal/server" "github.com/authelia/authelia/internal/session" @@ -117,15 +118,22 @@ func startServer() { authorizer := authorization.NewAuthorizer(config.AccessControl) sessionProvider := session.NewProvider(config.Session, autheliaCertPool) regulator := regulation.NewRegulator(config.Regulation, storageProvider, clock) + oidcProvider, err := oidc.NewOpenIDConnectProvider(config.IdentityProviders.OIDC) + + if err != nil { + panic(err) + } providers := middlewares.Providers{ Authorizer: authorizer, UserProvider: userProvider, Regulator: regulator, + OpenIDConnect: oidcProvider, StorageProvider: storageProvider, Notifier: notifier, SessionProvider: sessionProvider, } + server.StartServer(*config, providers) } @@ -149,7 +157,8 @@ func main() { } rootCmd.AddCommand(versionCmd, commands.HashPasswordCmd, - commands.ValidateConfigCmd, commands.CertificatesCmd) + commands.ValidateConfigCmd, commands.CertificatesCmd, + commands.RSACmd) if err := rootCmd.Execute(); err != nil { logger.Fatal(err) diff --git a/config.template.yml b/config.template.yml index 154da0aa..b279bb11 100644 --- a/config.template.yml +++ b/config.template.yml @@ -553,4 +553,62 @@ notifier: # sender: admin@example.com # host: smtp.gmail.com # port: 587 + +## +## Identity Providers +## +# identity_providers: + + ## + ## OpenID Connect (Identity Provider) + ## + ## It's recommended you read the documentation before configuration of this section: + ## https://www.authelia.com/docs/configuration/identity-providers/oidc.html + # oidc: + ## The hmac_secret is used to sign OAuth2 tokens (authorization code, access tokens and refresh tokens). + ## HMAC Secret can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html + # hmac_secret: this_is_a_secret_abc123abc123abc + + ## The issuer_private_key is used to sign the JWT forged by OpenID Connect. + ## Issuer Private Key can also be set using a secret: https://docs.authelia.com/configuration/secrets.html + # issuer_private_key: | + # --- KEY START + # --- KEY END + + ## Clients is a list of known clients and their configuration. + # clients: + # - + ## The ID is the OpenID Connect ClientID which is used to link an application to a configuration. + # id: myapp + + ## The description to show to users when they end up on the consent screen. Defaults to the ID above. + # description: My Application + + ## The client secret is a shared secret between Authelia and the consumer of this client. + # secret: this_is_a_secret + + ## The policy to require for this client; one_factor or two_factor. + # authorization_policy: two_factor + + ## Redirect URI's specifies a list of valid case-sensitive callbacks for this client. + # redirect_uris: + # - https://oidc.example.com:8080/oauth2/callback + + ## Scopes defines the valid scopes this client can request + # scopes: + # - openid + # - groups + # - email + # - profile + + ## Grant Types configures which grants this client can obtain. + ## It's not recommended to define this unless you know what you're doing. + # grant_types: + # - refresh_token + # - "authorization_code + + ## Response Types configures which responses this client can be sent. + ## It's not recommended to define this unless you know what you're doing. + # response_types: + # - code ... diff --git a/docs/_sass/custom/custom.scss b/docs/_sass/custom/custom.scss index 0e9400ab..5a43d728 100644 --- a/docs/_sass/custom/custom.scss +++ b/docs/_sass/custom/custom.scss @@ -1,3 +1,10 @@ .label.label-config { text-transform: none; +} +.tbl-header { + font-weight: bold; + text-align: center; +} +.tbl-beta-stage { + border-bottom-width: 3px !important; } \ No newline at end of file diff --git a/docs/configuration/identity-providers/index.md b/docs/configuration/identity-providers/index.md new file mode 100644 index 00000000..b5a62d94 --- /dev/null +++ b/docs/configuration/identity-providers/index.md @@ -0,0 +1,12 @@ +--- +layout: default +title: Identity Providers +parent: Configuration +nav_order: 12 +has_children: true +--- + +# Identity Providers + +This section covers configuration of the identity server characteristics of Authelia. Currently the only identity server +supported is OpenID Connect. diff --git a/docs/configuration/identity-providers/oidc.md b/docs/configuration/identity-providers/oidc.md new file mode 100644 index 00000000..b4431a95 --- /dev/null +++ b/docs/configuration/identity-providers/oidc.md @@ -0,0 +1,225 @@ +--- +layout: default +title: OpenID Connect +parent: Identity Providers +grand_parent: Configuration +nav_order: 2 +--- + +# OpenID Connect + +**Authelia** currently supports the [OpenID Connect] OP role as a [beta](#beta) feature. The OP role is the +[OpenID Connect] Provider role, not the Relaying Party or RP role. This means other applications that implement the +[OpenID Connect] RP role can use Authelia as an authentication and authorization backend similar to how you may use +social media or development platforms for login. + +The Relaying Party role is the role which allows an application to use GitHub, Google, or other [OpenID Connect] +providers for authentication and authorization. We do not intend to support this functionality at this moment in time. + +## Beta + +We have decided to implement [OpenID Connect] as a beta feature, it's suggested you only utilize it for testing and +providing feedback, and should take caution in relying on it in production. [OpenID Connect] and it's related endpoints +are not enabled by default unless you specifically configure the [OpenID Connect] section. + +The beta will be broken up into stages. Each stage will bring additional features. The following table is a *rough* plan +for which stage will have each feature, and may evolve over time: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StageFeature Description
beta1User Consent
Authorization Code Flow
OpenID Connect Discovery
RS256 Signature Strategy
Per Client Scope/Grant Type/Response Type Restriction
Per Client Authorization Policy (1FA/2FA)
Per Client List of Valid Redirection URI's
beta2 1Token Storage
Audit Storage
beta3 1Back-Channel Logout
Deny Refresh on Session Expiration
Signing Key Rotation Policy
Client Secrets Hashed in Configuration
GA 1General Availability after previous stages are vetted for bug fixes
miscList of other features that may be implemented
Front-Channel Logout 2
+ +*1 this stage has not been implemented as of yet* + +*2 this individual feature has not been implemented as of yet* + +## Configuration + +```yaml +identity_providers: + oidc: + hmac_secret: this_is_a_secret_abc123abc123abc + issuer_private_key: | + --- KEY START + --- KEY END + clients: + - id: myapp + description: My Application + secret: this_is_a_secret + authorization_policy: two_factor + redirect_uris: + - https://oidc.example.com:8080/oauth2/callback + scopes: + - openid + - groups + - email + - profile + grant_types: + - refresh_token + - authorization_code + response_types: + - code +``` + +## Options + +### hmac_secret + +The HMAC secret used to sign the [OpenID Connect] JWT's. The provided string is hashed to a SHA256 byte string for +the purpose of meeting the required format. + +Can also be defined using a [secret](../secrets.md) which is the recommended for containerized deployments. + +### issuer_private_key + +The private key in DER base64 encoded PEM format used to encrypt the [OpenID Connect] JWT's. + +Can also be defined using a [secret](../secrets.md) which is the recommended for containerized deployments. + +### clients + +A list of clients to configure. The options for each client are described below. + +#### id + +The Client ID for this client. Must be configured in the application consuming this client. + +#### description + +A friendly description for this client shown in the UI. This defaults to the same as the ID. + +#### secret + +The shared secret between Authelia and the application consuming this client. Currently this is stored in plain text. + +#### authorization_policy + +The authorization policy for this client. Either `one_factor` or `two_factor`. + +#### redirect_uris + +A list of valid callback URL's this client will redirect to. All other callbacks will be considered unsafe. The URL's +are case-sensitive. + +#### scopes + +A list of scopes to allow this client to consume. See [scope definitions](#scope-definitions) for more information. + +#### grant_types + +A list of grant types this client can return. It is recommended that this isn't configured at this time unless you know +what you're doing. + +#### response_types + +A list of response types this client can return. It is recommended that this isn't configured at this time unless you +know what you're doing. + +## Scope Definitions + +### openid + +This is the default scope for openid. This field is forced on every client by the configuration +validation that Authelia does. + +|JWT Field|JWT Type |Authelia Attribute|Description | +|:-------:|:-----------:|:----------------:|:--------------------------------------:| +|sub |string |Username |The username the user used to login with| +|scope |string |scopes |Granted scopes (space delimited) | +|scp |array[string]|scopes |Granted scopes | +|iss |string |hostname |The issuer name, determined by URL | +|at_hash |string |_N/A_ |Access Token Hash | +|auth_time|number |_N/A_ |Authorize Time | +|aud |array[string]|_N/A_ |Audience | +|exp |number |_N/A_ |Expires | +|iat |number |_N/A_ |Issued At | +|rat |number |_N/A_ |Requested At | +|jti |string(uuid) |_N/A_ |JWT Identifier | + +### groups + +This scope includes the groups the authentication backend reports the user is a member of in the token. + +|JWT Field|JWT Type |Authelia Attribute|Description | +|:-------:|:-----------:|:----------------:|:--------------------:| +|groups |array[string]|Groups |The users display name| + +### email + +This scope includes the email information the authentication backend reports about the user in the token. + +|JWT Field |JWT Type|Authelia Attribute|Description | +|:------------:|:------:|:----------------:|:-------------------------------------------------------:| +|email |string |email[0] |The first email in the list of emails | +|email_verified|bool |_N/A_ |If the email is verified, assumed true for the time being| + +### profile + +This scope includes the profile information the authentication backend reports about the user in the token. + +|JWT Field|JWT Type|Authelia Attribute|Description | +|:-------:|:------:|:----------------:|:--------------------:| +|name |string | display_name |The users display name| + + +[OpenID Connect]: https://openid.net/connect/ \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index f8606080..3da01405 100644 --- a/docs/index.md +++ b/docs/index.md @@ -40,7 +40,7 @@ so that you can test it in minutes. Let's begin with the ## However, Authelia... -* is not an OAuth or OpenID Connect provider yet (planned in the [roadmap](./roadmap.md)) +* [OpenID Connect](./configuration/identity-providers/oidc.md) is still in preview. * is not a SAML provider yet. * does not support authentication against an OAuth or OpenID Connect provider yet. * does not support authentication against a SAML provider yet. diff --git a/docs/roadmap.md b/docs/roadmap.md index f34b97c5..5bf4c887 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -14,7 +14,9 @@ ideas and plans with you. Below are the prioritised roadmap items: -1. [Authelia acts as an OpenID Connect Provider](https://github.com/authelia/authelia/issues/189). This is a high +1. **[In Preview](./configuration/identity-providers/oidc.md)** *this roadmap item is in preview status, more + information can be found in the docs*. + [Authelia acts as an OpenID Connect Provider](https://github.com/authelia/authelia/issues/189). This is a high priority because currently the only way to pass authentication information back to the protected app is through the use of HTTP headers as described [here](https://www.authelia.com/docs/deployment/supported-proxies/#how-can-the-backend-be-aware-of-the-authenticated-users) diff --git a/go.mod b/go.mod index f3f250e9..f79d8b8a 100644 --- a/go.mod +++ b/go.mod @@ -17,9 +17,9 @@ require ( github.com/go-sql-driver/mysql v1.6.0 github.com/golang/mock v1.5.0 github.com/jackc/pgx/v4 v4.11.0 - github.com/mattn/go-sqlite3 v1.14.7 + github.com/mattn/go-sqlite3 v2.0.3+incompatible + github.com/ory/fosite v0.39.0 github.com/otiai10/copy v1.5.1 - github.com/pelletier/go-toml v1.4.0 // indirect github.com/pquerna/otp v1.3.0 github.com/simia-tech/crypt v0.5.0 github.com/sirupsen/logrus v1.8.1 @@ -30,5 +30,6 @@ require ( github.com/tstranex/u2f v1.0.0 github.com/valyala/fasthttp v1.24.0 golang.org/x/text v0.3.6 + gopkg.in/square/go-jose.v2 v2.5.1 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 0ddc121f..48f266fd 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -12,6 +13,7 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= @@ -20,13 +22,22 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOC github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgbutil v0.0.0-20160919175755-f7c97cef3b4e h1:4ZrkT/RzpnROylmoQL57iVUL57wGKTR5O6KpVnbm2tA= github.com/BurntSushi/xgbutil v0.0.0-20160919175755-f7c97cef3b4e/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/datadog-go v4.0.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4 h1:vdT7QwBhJJEVNFMBNhRSFDRCB6O16T28VhvqRgqFyn8= github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4/go.mod h1:SvXOG8ElV28oAiG9zv91SDe5+9PfIr7PPccpr8YyXNs= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= +github.com/Masterminds/semver v1.4.2/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/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= @@ -34,6 +45,8 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/ github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -43,16 +56,22 @@ github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +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-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aws/aws-xray-sdk-go v0.9.4/go.mod h1:XtMKdBQfpVut+tJEwI7+dJFRxxRdxHDyVNp2tHXRq04= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -60,24 +79,38 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +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.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +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/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= +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/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -85,26 +118,42 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA= 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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +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.3 h1:jh22xisGBjrEVnRZ1DVTpBVQm0Xndu8sMl0CWDzSIBI= +github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 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 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/duosecurity/duo_api_golang v0.0.0-20201112143038-0e07e9f869e3 h1:7/i/g2rlBeX1DHg5xTrR2hiFi87ZrqRWV3eLZUApjdI= github.com/duosecurity/duo_api_golang v0.0.0-20201112143038-0e07e9f869e3/go.mod h1:jdoEJUIrTIxN7nNTwwqA3TBNcSM+W1lrWM6OXVhjbG8= 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/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.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/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +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-20181003060214-f58a169a71a5/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -115,6 +164,11 @@ github.com/fasthttp/router v1.3.12/go.mod h1:u+cTxz7conAkhUqSYx0em5kfYwDaslNehUy github.com/fasthttp/session/v2 v2.3.2 h1:QtGPJGqcNriXN+dqz20z0yp3Kg9H28emODZ1ILcSQpE= github.com/fasthttp/session/v2 v2.3.2/go.mod h1:Fb8sNPFfYLBFHZWZbj1wjODHwe7/PNeuXsmTIkHAomQ= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +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/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -123,6 +177,7 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1/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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -135,15 +190,251 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-redis/redis/v8 v8.3.4 h1:ZF7juZS2wzxloqMKslTutWJ05IQrnchCSk1HD4d4Vbs= github.com/go-redis/redis/v8 v8.3.4/go.mod h1:jszGxBCez8QA1HWSmQxJO9Y82kNibbUmeYhKWrBejTU= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +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/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/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.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.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.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.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.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/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/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/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.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 v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +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 h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -152,8 +443,10 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er 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.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +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= @@ -162,6 +455,7 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -173,6 +467,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github/v27 v27.0.4/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= +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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -180,15 +475,28 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 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 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 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.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +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/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/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/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -228,9 +536,11 @@ github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9 github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= 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.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= @@ -262,9 +572,13 @@ github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkAL github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= github.com/jackc/pgtype v1.7.0 h1:6f4kVsW01QftE38ufBYxKciO6gyioXSC0ABIRLcZrGs= github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE= +github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= +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.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= @@ -275,28 +589,50 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0f github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +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/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/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +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.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.5/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.11.8 h1:difgzQsp5mdAz9v8lm3P/I+EpDKMU/6uTMw1y1FObuo= github.com/klauspost/compress v1.11.8/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +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= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 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.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -305,12 +641,37 @@ github.com/lib/pq v1.10.1 h1:6VXZrLU0jHBYyAqrSPa+MgPfnSvTPuMgK+k0o5kVFWo= github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +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/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +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/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/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -318,11 +679,21 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA= +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.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +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/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/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -331,12 +702,19 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI 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.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +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 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +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/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= @@ -345,31 +723,74 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/oleiade/reflections v1.0.0 h1:0ir4pc6v8/PJ0yw5AEtMddfXpWBXg9cnG7SgSoJuCgY= +github.com/oleiade/reflections v1.0.0/go.mod h1:RbATFBbKYkVdqmSFtx13Bb/tVhR0lgOBXunWTZKeL4w= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 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.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +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.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1/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-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= 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/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/fosite v0.29.0/go.mod h1:0atSZmXO7CAcs6NPMI/Qtot8tmZYj04Nddoold4S2h0= +github.com/ory/fosite v0.39.0 h1:u1Ct/ME7XYzREvufr7ehBIdq/KatjVLIYg/ABqWzprw= +github.com/ory/fosite v0.39.0/go.mod h1:37r59qkOSPueYKmaA7EHiXrDMF1B+XPN+MgkZgTRg3Y= +github.com/ory/go-acc v0.0.0-20181118080137-ddc355013f90/go.mod h1:sxnvPCxChFuSmTJGj8FdMupeq1BezCiEpDjTUXQ4hf4= +github.com/ory/go-acc v0.2.5 h1:31irXHzG2vnKQSE4weJm7AdfrnpaVjVCq3nD7viXCJE= +github.com/ory/go-acc v0.2.5/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/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.162 h1:xE/UBmmMlInTvlgGXUyo+VeZAcWU5gyWb/xh6jmBWsI= +github.com/ory/x v0.0.162/go.mod h1:sj3z/MeCrAyNFFTfN6yK1nTmHXGSFnw+QwIIQ/Rowec= github.com/otiai10/copy v1.5.1 h1:a/cs2E1/1V0az8K5nblbl+ymEa4E11AfaOLMar8V34w= github.com/otiai10/copy v1.5.1/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= @@ -378,20 +799,28 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E= github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= 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/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 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= 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.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +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= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -414,59 +843,117 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/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/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +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/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +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/savsgio/dictpool v0.0.0-20210404150759-6de1ea7c0e13 h1:GHosyqopD9q+RnHRd4HsqhT1873lF5necC/vB2oMcDI= github.com/savsgio/dictpool v0.0.0-20210404150759-6de1ea7c0e13/go.mod h1:jYioskm8OBnvGMa37NVB/Ndy/ZiyCZouiEZlRl3T+wY= github.com/savsgio/gotils v0.0.0-20210316171653-c54912823645 h1:ug9pfpEqVhvazyl1GezkQ9M/XdWsQn3VSx0s4qfH82I= github.com/savsgio/gotils v0.0.0-20210316171653-c54912823645/go.mod h1:CN0/b5o0sSBi9K8fZTUamBG5NZXO0I64vTh9L3Mzhn0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +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/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/go-snakecase v1.1.0/go.mod h1:jk1miR5MS7Na32PZUykG89Arm+1BUSYhuGR6b7+hJto= +github.com/segmentio/objconv v1.0.1/go.mod h1:auayaH5k3137Cl4SoXTgrzQcuQDmvuVtZgS0fb1Ahys= +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/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/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/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/simia-tech/crypt v0.5.0 h1:Y8xfAGqgd2wW2o4E63WIy9xr9w4jC1tDsOBHGKiqP0s= github.com/simia-tech/crypt v0.5.0/go.mod h1:DMwvjPTzsiHrjqHVW5HvIbF4vUUzMCYDKVLsPWmLdTo= +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.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.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/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/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 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +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.2 h1:GDarE4TJQI52kYSbSAmLiId1Elfj+xgSDqrUZxFhxlU= +github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +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 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +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.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 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.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A= 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= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -481,10 +968,16 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tebeka/selenium v0.9.9 h1:cNziB+etNgyH/7KlNI7RMC1ua5aH1+5wUlFQyzeMh+w= github.com/tebeka/selenium v0.9.9/go.mod h1:5Fr8+pUvU6B1OiPfkdCKdXZyr5znvVkxuPd0NOdZCQc= +github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.0.4/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y= +github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.5 h1:2gXmtWueD2HefZHQe1QOy9HVzmFrLOVvsXwXBQ0ayy0= github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -492,16 +985,36 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ= github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= +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-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/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +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/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.24.0 h1:AAiG4oLDUArTb7rYf9oO2bkGooOqCaUF6a2u8asBP3I= github.com/valyala/fasthttp v1.24.0/go.mod h1:0mw2RjXGOzxf4NL2jni3gUQ7LfjjUSiG5sskOUUSEpU= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +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= @@ -509,12 +1022,15 @@ 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.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.13.0/go.mod h1:TwTkyRaTam1pOIb2wxcAiC2hkMVbokXkt6DEt5nDkD8= go.opentelemetry.io/otel v0.13.0 h1:2isEnyzjjJZq6r2EKMsFj4TxiQiexsM04AVhwbR/oBA= go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= @@ -523,28 +1039,54 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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-20200117160349-530e935923ad/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-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -554,18 +1096,30 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-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= @@ -574,35 +1128,66 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/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-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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226101413-39120d07d75e/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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-20181116161606-93218def8b18/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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -611,6 +1196,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/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-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -618,13 +1204,22 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/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-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/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-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 h1:8qxJSnu+7dRq6upnbntrmriWByIakBuct5OM/MdQC1M= @@ -643,9 +1238,33 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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-20180828015842-6cd1fcedba52/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-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-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-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-20190114222345-bf090417da8b/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-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -656,18 +1275,28 @@ golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 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-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-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-20190911174233-4f2ddba30aff/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= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191224055732-dd894d0a8a40/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-20200117220505-0cba7a3a9ee9/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-20200203215610-ab391d50b528/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200721223218-6123e77877b2/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9 h1:sEvmEcJVKBNUvgCUClbUQeHOAa9U0I2Ce1BooMvVCY4= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -676,6 +1305,12 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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/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= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -687,6 +1322,8 @@ google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -694,6 +1331,7 @@ google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -704,6 +1342,7 @@ google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLD google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -713,37 +1352,65 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +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/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 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/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/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= 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.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 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/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/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.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= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +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= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/internal/authorization/util.go b/internal/authorization/util.go index f2997b84..c258e441 100644 --- a/internal/authorization/util.go +++ b/internal/authorization/util.go @@ -5,6 +5,7 @@ import ( "regexp" "strings" + "github.com/authelia/authelia/internal/authentication" "github.com/authelia/authelia/internal/configuration/schema" ) @@ -175,3 +176,17 @@ func domainToPrefixSuffix(domain string) (prefix, suffix string) { return parts[0], strings.Join(parts[1:], ".") } + +// IsAuthLevelSufficient returns true if the current authenticationLevel is above the authorizationLevel. +func IsAuthLevelSufficient(authenticationLevel authentication.Level, authorizationLevel Level) bool { + switch authorizationLevel { + case Denied: + return false + case OneFactor: + return authenticationLevel >= authentication.OneFactor + case TwoFactor: + return authenticationLevel >= authentication.TwoFactor + } + + return true +} diff --git a/internal/authorization/util_test.go b/internal/authorization/util_test.go index db72d682..f06f6adb 100644 --- a/internal/authorization/util_test.go +++ b/internal/authorization/util_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/authelia/authelia/internal/authentication" "github.com/authelia/authelia/internal/configuration/schema" ) @@ -182,3 +183,15 @@ func TestShouldParseACLNetworks(t *testing.T) { assert.Equal(t, fourthNetwork, networksCacheMap["fec0::1"]) assert.Equal(t, fourthNetwork, networksCacheMap["fec0::1/128"]) } + +func TestShouldReturnCorrectValidationLevel(t *testing.T) { + assert.True(t, IsAuthLevelSufficient(authentication.NotAuthenticated, Bypass)) + assert.True(t, IsAuthLevelSufficient(authentication.OneFactor, Bypass)) + assert.True(t, IsAuthLevelSufficient(authentication.TwoFactor, Bypass)) + assert.False(t, IsAuthLevelSufficient(authentication.NotAuthenticated, OneFactor)) + assert.True(t, IsAuthLevelSufficient(authentication.OneFactor, OneFactor)) + assert.True(t, IsAuthLevelSufficient(authentication.TwoFactor, OneFactor)) + assert.False(t, IsAuthLevelSufficient(authentication.NotAuthenticated, TwoFactor)) + assert.False(t, IsAuthLevelSufficient(authentication.OneFactor, TwoFactor)) + assert.True(t, IsAuthLevelSufficient(authentication.TwoFactor, TwoFactor)) +} diff --git a/internal/commands/certificates.go b/internal/commands/certificates.go index d1d382b1..123df284 100644 --- a/internal/commands/certificates.go +++ b/internal/commands/certificates.go @@ -21,14 +21,14 @@ import ( ) var ( - host string - validFrom string - validFor time.Duration - isCA bool - rsaBits int - ecdsaCurve string - ed25519Key bool - targetDirectory string + host string + validFrom string + validFor time.Duration + isCA bool + rsaBits int + ecdsaCurve string + ed25519Key bool + certificateTargetDirectory string ) func init() { @@ -45,7 +45,7 @@ func init() { CertificatesGenerateCmd.PersistentFlags().IntVar(&rsaBits, "rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set") CertificatesGenerateCmd.PersistentFlags().StringVar(&ecdsaCurve, "ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256 (recommended), P384, P521") CertificatesGenerateCmd.PersistentFlags().BoolVar(&ed25519Key, "ed25519", false, "Generate an Ed25519 key") - CertificatesGenerateCmd.PersistentFlags().StringVar(&targetDirectory, "dir", "", "Target directory where the certificate and keys will be stored") + CertificatesGenerateCmd.PersistentFlags().StringVar(&certificateTargetDirectory, "dir", "", "Target directory where the certificate and keys will be stored") CertificatesCmd.AddCommand(CertificatesGenerateCmd) } @@ -144,7 +144,7 @@ func generateSelfSignedCertificate(cmd *cobra.Command, args []string) { log.Fatalf("Failed to create certificate: %v", err) } - certPath := path.Join(targetDirectory, "cert.pem") + certPath := path.Join(certificateTargetDirectory, "cert.pem") certOut, err := os.Create(certPath) if err != nil { @@ -161,7 +161,7 @@ func generateSelfSignedCertificate(cmd *cobra.Command, args []string) { log.Printf("wrote %s\n", certPath) - keyPath := path.Join(targetDirectory, "key.pem") + keyPath := path.Join(certificateTargetDirectory, "key.pem") keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { diff --git a/internal/commands/rsa.go b/internal/commands/rsa.go new file mode 100644 index 00000000..a0d5d68f --- /dev/null +++ b/internal/commands/rsa.go @@ -0,0 +1,79 @@ +package commands + +import ( + "log" + "os" + "path" + + "github.com/spf13/cobra" + + "github.com/authelia/authelia/internal/utils" +) + +var rsaTargetDirectory string + +func init() { + RSAGenerateCmd.PersistentFlags().StringVar(&rsaTargetDirectory, "dir", "", "Target directory where the keypair will be stored") + + RSACmd.AddCommand(RSAGenerateCmd) +} + +func generateRSAKeypair(cmd *cobra.Command, args []string) { + privateKey, publicKey := utils.GenerateRsaKeyPair(2048) + + keyPath := path.Join(rsaTargetDirectory, "key.pem") + keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + + if err != nil { + log.Fatalf("Failed to open %s for writing: %v", keyPath, err) + return + } + + _, err = keyOut.WriteString(utils.ExportRsaPrivateKeyAsPemStr(privateKey)) + if err != nil { + log.Fatalf("Unable to write private key: %v", err) + return + } + + if err := keyOut.Close(); err != nil { + log.Fatalf("Unable to close private key file: %v", err) + return + } + + keyPath = path.Join(rsaTargetDirectory, "key.pub") + keyOut, err = os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + + if err != nil { + log.Fatalf("Failed to open %s for writing: %v", keyPath, err) + return + } + + publicPem, err := utils.ExportRsaPublicKeyAsPemStr(publicKey) + if err != nil { + log.Fatalf("Unable to marshal public key: %v", err) + } + + _, err = keyOut.WriteString(publicPem) + if err != nil { + log.Fatalf("Unable to write private key: %v", err) + return + } + + if err := keyOut.Close(); err != nil { + log.Fatalf("Unable to close public key file: %v", err) + return + } +} + +// RSACmd RSA helper command. +var RSACmd = &cobra.Command{ + Use: "rsa", + Short: "Commands related to rsa keypair generation", +} + +// RSAGenerateCmd certificate generation command. +var RSAGenerateCmd = &cobra.Command{ + Use: "generate", + Short: "Generate a RSA keypair", + Run: generateRSAKeypair, +} diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml index 154da0aa..b279bb11 100644 --- a/internal/configuration/config.template.yml +++ b/internal/configuration/config.template.yml @@ -553,4 +553,62 @@ notifier: # sender: admin@example.com # host: smtp.gmail.com # port: 587 + +## +## Identity Providers +## +# identity_providers: + + ## + ## OpenID Connect (Identity Provider) + ## + ## It's recommended you read the documentation before configuration of this section: + ## https://www.authelia.com/docs/configuration/identity-providers/oidc.html + # oidc: + ## The hmac_secret is used to sign OAuth2 tokens (authorization code, access tokens and refresh tokens). + ## HMAC Secret can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html + # hmac_secret: this_is_a_secret_abc123abc123abc + + ## The issuer_private_key is used to sign the JWT forged by OpenID Connect. + ## Issuer Private Key can also be set using a secret: https://docs.authelia.com/configuration/secrets.html + # issuer_private_key: | + # --- KEY START + # --- KEY END + + ## Clients is a list of known clients and their configuration. + # clients: + # - + ## The ID is the OpenID Connect ClientID which is used to link an application to a configuration. + # id: myapp + + ## The description to show to users when they end up on the consent screen. Defaults to the ID above. + # description: My Application + + ## The client secret is a shared secret between Authelia and the consumer of this client. + # secret: this_is_a_secret + + ## The policy to require for this client; one_factor or two_factor. + # authorization_policy: two_factor + + ## Redirect URI's specifies a list of valid case-sensitive callbacks for this client. + # redirect_uris: + # - https://oidc.example.com:8080/oauth2/callback + + ## Scopes defines the valid scopes this client can request + # scopes: + # - openid + # - groups + # - email + # - profile + + ## Grant Types configures which grants this client can obtain. + ## It's not recommended to define this unless you know what you're doing. + # grant_types: + # - refresh_token + # - "authorization_code + + ## Response Types configures which responses this client can be sent. + ## It's not recommended to define this unless you know what you're doing. + # response_types: + # - code ... diff --git a/internal/configuration/schema/configuration.go b/internal/configuration/schema/configuration.go index 040b64a7..89434d35 100644 --- a/internal/configuration/schema/configuration.go +++ b/internal/configuration/schema/configuration.go @@ -14,6 +14,7 @@ type Configuration struct { JWTSecret string `mapstructure:"jwt_secret"` DefaultRedirectionURL string `mapstructure:"default_redirection_url"` + IdentityProviders IdentityProvidersConfiguration `mapstructure:"identity_providers"` AuthenticationBackend AuthenticationBackendConfiguration `mapstructure:"authentication_backend"` Session SessionConfiguration `mapstructure:"session"` TOTP *TOTPConfiguration `mapstructure:"totp"` diff --git a/internal/configuration/schema/identity_providers.go b/internal/configuration/schema/identity_providers.go new file mode 100644 index 00000000..dd2d7519 --- /dev/null +++ b/internal/configuration/schema/identity_providers.go @@ -0,0 +1,35 @@ +package schema + +// IdentityProvidersConfiguration represents the IdentityProviders 2.0 configuration for Authelia. +type IdentityProvidersConfiguration struct { + OIDC *OpenIDConnectConfiguration `mapstructure:"oidc"` +} + +// OpenIDConnectConfiguration configuration for OpenID Connect. +type OpenIDConnectConfiguration struct { + // This secret must be 32 bytes long + HMACSecret string `mapstructure:"hmac_secret"` + IssuerPrivateKey string `mapstructure:"issuer_private_key"` + + Clients []OpenIDConnectClientConfiguration `mapstructure:"clients"` +} + +// OpenIDConnectClientConfiguration configuration for an OpenID Connect client. +type OpenIDConnectClientConfiguration struct { + ID string `mapstructure:"id"` + Description string `mapstructure:"description"` + Secret string `mapstructure:"secret"` + RedirectURIs []string `mapstructure:"redirect_uris"` + Policy string `mapstructure:"authorization_policy"` + Scopes []string `mapstructure:"scopes"` + GrantTypes []string `mapstructure:"grant_types"` + ResponseTypes []string `mapstructure:"response_types"` +} + +// DefaultOpenIDConnectClientConfiguration contains defaults for OIDC AutheliaClients. +var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClientConfiguration{ + Scopes: []string{"openid", "groups", "profile", "email"}, + ResponseTypes: []string{"code"}, + GrantTypes: []string{"refresh_token", "authorization_code"}, + Policy: "two_factor", +} diff --git a/internal/configuration/validator/configuration.go b/internal/configuration/validator/configuration.go index 3f6c6335..0bd640d2 100644 --- a/internal/configuration/validator/configuration.go +++ b/internal/configuration/validator/configuration.go @@ -91,4 +91,6 @@ func ValidateConfiguration(configuration *schema.Configuration, validator *schem } else { ValidateNotifier(configuration.Notifier, validator) } + + ValidateIdentityProviders(&configuration.IdentityProviders, validator) } diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go index 1014c5f7..fca945dd 100644 --- a/internal/configuration/validator/const.go +++ b/internal/configuration/validator/const.go @@ -7,6 +7,11 @@ const ( errFmtSessionRedisHostOrNodesRequired = "Either the host or a node must be provided when using the %s session provider" errFmtReplacedConfigurationKey = "invalid configuration key '%s' was replaced by '%s'" + errOAuthOIDCServerClientRedirectURIFmt = "OIDC Server Client redirect URI %s has an invalid scheme %s, should be http or https" + errOAuthOIDCServerClientRedirectURICantBeParsedFmt = "OIDC Client with ID '%s' has an invalid redirect URI '%s' could not be parsed: %v" + errIdentityProvidersOIDCServerClientInvalidPolicyFmt = "OIDC Client with ID '%s' has an invalid policy '%s', should be either 'one_factor' or 'two_factor'" + errIdentityProvidersOIDCServerClientInvalidSecFmt = "OIDC Client with ID '%s' has an empty secret" + errFileHashing = "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password" errFilePHashing = "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password" errFilePOptions = "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password" @@ -42,15 +47,17 @@ var validRequestMethods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELET // SecretNames contains a map of secret names. var SecretNames = map[string]string{ - "JWTSecret": "jwt_secret", - "SessionSecret": "session.secret", - "DUOSecretKey": "duo_api.secret_key", - "RedisPassword": "session.redis.password", - "RedisSentinelPassword": "session.redis.high_availability.sentinel_password", - "LDAPPassword": "authentication_backend.ldap.password", - "SMTPPassword": "notifier.smtp.password", - "MySQLPassword": "storage.mysql.password", - "PostgreSQLPassword": "storage.postgres.password", + "JWTSecret": "jwt_secret", + "SessionSecret": "session.secret", + "DUOSecretKey": "duo_api.secret_key", + "RedisPassword": "session.redis.password", + "RedisSentinelPassword": "session.redis.high_availability.sentinel_password", + "LDAPPassword": "authentication_backend.ldap.password", + "SMTPPassword": "notifier.smtp.password", + "MySQLPassword": "storage.mysql.password", + "PostgreSQLPassword": "storage.postgres.password", + "OpenIDConnectHMACSecret": "identity_providers.oidc.hmac_secret", + "OpenIDConnectIssuerPrivateKey": "identity_providers.oidc.issuer_private_key", } // validKeys is a list of valid keys that are not secret names. For the sake of consistency please place any secret in @@ -184,6 +191,9 @@ var validKeys = []string{ "authentication_backend.file.password.salt_length", "authentication_backend.file.password.memory", "authentication_backend.file.password.parallelism", + + // Identity Provider Keys. + "identity_providers.oidc.clients", } var replacedKeys = map[string]string{ diff --git a/internal/configuration/validator/identity_providers.go b/internal/configuration/validator/identity_providers.go new file mode 100644 index 00000000..792621bb --- /dev/null +++ b/internal/configuration/validator/identity_providers.go @@ -0,0 +1,98 @@ +package validator + +import ( + "fmt" + "net/url" + + "github.com/authelia/authelia/internal/configuration/schema" + "github.com/authelia/authelia/internal/utils" +) + +// ValidateIdentityProviders validates and update IdentityProviders configuration. +func ValidateIdentityProviders(configuration *schema.IdentityProvidersConfiguration, validator *schema.StructValidator) { + validateOIDC(configuration.OIDC, validator) +} + +func validateOIDC(configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { + if configuration != nil { + if configuration.IssuerPrivateKey == "" { + validator.Push(fmt.Errorf("OIDC Server issuer private key must be provided")) + } + + validateOIDCClients(configuration, validator) + + if len(configuration.Clients) == 0 { + validator.Push(fmt.Errorf("OIDC Server has no clients defined")) + } + } +} + +func validateOIDCClients(configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { + invalidID, duplicateIDs := false, false + + var ids []string + + for c, client := range configuration.Clients { + if client.ID == "" { + invalidID = true + } else { + if client.Description == "" { + configuration.Clients[c].Description = client.ID + } + + if utils.IsStringInSliceFold(client.ID, ids) { + duplicateIDs = true + } + ids = append(ids, client.ID) + } + + if client.Secret == "" { + validator.Push(fmt.Errorf(errIdentityProvidersOIDCServerClientInvalidSecFmt, client.ID)) + } + + if client.Policy == "" { + configuration.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy + } else if client.Policy != oneFactorPolicy && client.Policy != twoFactorPolicy { + validator.Push(fmt.Errorf(errIdentityProvidersOIDCServerClientInvalidPolicyFmt, client.ID, client.Policy)) + } + + if len(client.Scopes) == 0 { + configuration.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes + } else if !utils.IsStringInSlice("openid", client.Scopes) { + configuration.Clients[c].Scopes = append(configuration.Clients[c].Scopes, "openid") + } + + if len(client.GrantTypes) == 0 { + configuration.Clients[c].GrantTypes = schema.DefaultOpenIDConnectClientConfiguration.GrantTypes + } + + if len(client.ResponseTypes) == 0 { + configuration.Clients[c].ResponseTypes = schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes + } + + validateOIDCClientRedirectURIs(client, validator) + } + + if invalidID { + validator.Push(fmt.Errorf("OIDC Server has one or more clients with an empty ID")) + } + + if duplicateIDs { + validator.Push(fmt.Errorf("OIDC Server has clients with duplicate ID's")) + } +} + +func validateOIDCClientRedirectURIs(client schema.OpenIDConnectClientConfiguration, validator *schema.StructValidator) { + for _, redirectURI := range client.RedirectURIs { + parsedURI, err := url.Parse(redirectURI) + + if err != nil { + validator.Push(fmt.Errorf(errOAuthOIDCServerClientRedirectURICantBeParsedFmt, client.ID, redirectURI, err)) + break + } + + if parsedURI.Scheme != "https" && parsedURI.Scheme != "http" { + validator.Push(fmt.Errorf(errOAuthOIDCServerClientRedirectURIFmt, redirectURI, parsedURI.Scheme)) + } + } +} diff --git a/internal/configuration/validator/identity_providers_test.go b/internal/configuration/validator/identity_providers_test.go new file mode 100644 index 00000000..00e50272 --- /dev/null +++ b/internal/configuration/validator/identity_providers_test.go @@ -0,0 +1,172 @@ +package validator + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/authelia/authelia/internal/configuration/schema" +) + +func TestShouldRaiseErrorWhenInvalidOIDCServerConfiguration(t *testing.T) { + validator := schema.NewStructValidator() + config := &schema.IdentityProvidersConfiguration{ + OIDC: &schema.OpenIDConnectConfiguration{ + HMACSecret: "abc", + IssuerPrivateKey: "", + }, + } + + ValidateIdentityProviders(config, validator) + + require.Len(t, validator.Errors(), 2) + + assert.EqualError(t, validator.Errors()[0], "OIDC Server issuer private key must be provided") + assert.EqualError(t, validator.Errors()[1], "OIDC Server has no clients defined") +} + +func TestShouldRaiseErrorWhenOIDCServerIssuerPrivateKeyPathInvalid(t *testing.T) { + validator := schema.NewStructValidator() + config := &schema.IdentityProvidersConfiguration{ + OIDC: &schema.OpenIDConnectConfiguration{ + HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", + IssuerPrivateKey: "key-material", + }, + } + + ValidateIdentityProviders(config, validator) + + require.Len(t, validator.Errors(), 1) + + assert.EqualError(t, validator.Errors()[0], "OIDC Server has no clients defined") +} + +func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { + validator := schema.NewStructValidator() + config := &schema.IdentityProvidersConfiguration{ + OIDC: &schema.OpenIDConnectConfiguration{ + HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", + IssuerPrivateKey: "key-material", + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "", + Secret: "", + Policy: "", + RedirectURIs: []string{ + "tcp://google.com", + }, + }, + { + ID: "a-client", + Secret: "a-secret", + Policy: "a-policy", + RedirectURIs: []string{ + "https://google.com", + }, + }, + { + ID: "a-client", + Secret: "a-secret", + Policy: "a-policy", + RedirectURIs: []string{ + "https://google.com", + }, + }, + { + ID: "client-check-uri-parse", + Secret: "a-secret", + Policy: twoFactorPolicy, + RedirectURIs: []string{ + "http://abc@%two", + }, + }, + }, + }, + } + + ValidateIdentityProviders(config, validator) + + require.Len(t, validator.Errors(), 7) + + assert.Equal(t, schema.DefaultOpenIDConnectClientConfiguration.Policy, config.OIDC.Clients[0].Policy) + assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errIdentityProvidersOIDCServerClientInvalidSecFmt, "")) + assert.EqualError(t, validator.Errors()[1], fmt.Sprintf(errOAuthOIDCServerClientRedirectURIFmt, "tcp://google.com", "tcp")) + assert.EqualError(t, validator.Errors()[2], fmt.Sprintf(errIdentityProvidersOIDCServerClientInvalidPolicyFmt, "a-client", "a-policy")) + assert.EqualError(t, validator.Errors()[3], fmt.Sprintf(errIdentityProvidersOIDCServerClientInvalidPolicyFmt, "a-client", "a-policy")) + assert.EqualError(t, validator.Errors()[4], fmt.Sprintf(errOAuthOIDCServerClientRedirectURICantBeParsedFmt, "client-check-uri-parse", "http://abc@%two", errors.New("parse \"http://abc@%two\": invalid URL escape \"%tw\""))) + assert.EqualError(t, validator.Errors()[5], "OIDC Server has one or more clients with an empty ID") + assert.EqualError(t, validator.Errors()[6], "OIDC Server has clients with duplicate ID's") +} + +func TestShouldNotRaiseErrorWhenOIDCServerConfiguredCorrectly(t *testing.T) { + validator := schema.NewStructValidator() + config := &schema.IdentityProvidersConfiguration{ + OIDC: &schema.OpenIDConnectConfiguration{ + HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", + IssuerPrivateKey: "../../../README.md", + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "a-client", + Secret: "a-client-secret", + Policy: oneFactorPolicy, + RedirectURIs: []string{ + "https://google.com", + }, + }, + { + ID: "b-client", + Description: "Normal Description", + Secret: "b-client-secret", + Policy: oneFactorPolicy, + RedirectURIs: []string{ + "https://google.com", + }, + Scopes: []string{ + "groups", + }, + GrantTypes: []string{ + "refresh_token", + }, + ResponseTypes: []string{ + "token", + "code", + }, + }, + }, + }, + } + + ValidateIdentityProviders(config, validator) + + assert.Len(t, validator.Errors(), 0) + + assert.Equal(t, config.OIDC.Clients[0].ID, config.OIDC.Clients[0].Description) + assert.Equal(t, "Normal Description", config.OIDC.Clients[1].Description) + + require.Len(t, config.OIDC.Clients[0].Scopes, 4) + assert.Equal(t, "openid", config.OIDC.Clients[0].Scopes[0]) + assert.Equal(t, "groups", config.OIDC.Clients[0].Scopes[1]) + assert.Equal(t, "profile", config.OIDC.Clients[0].Scopes[2]) + assert.Equal(t, "email", config.OIDC.Clients[0].Scopes[3]) + + require.Len(t, config.OIDC.Clients[0].GrantTypes, 2) + assert.Equal(t, "refresh_token", config.OIDC.Clients[0].GrantTypes[0]) + assert.Equal(t, "authorization_code", config.OIDC.Clients[0].GrantTypes[1]) + + require.Len(t, config.OIDC.Clients[0].ResponseTypes, 1) + assert.Equal(t, "code", config.OIDC.Clients[0].ResponseTypes[0]) + + require.Len(t, config.OIDC.Clients[1].Scopes, 2) + assert.Equal(t, "groups", config.OIDC.Clients[1].Scopes[0]) + assert.Equal(t, "openid", config.OIDC.Clients[1].Scopes[1]) + + require.Len(t, config.OIDC.Clients[1].GrantTypes, 1) + assert.Equal(t, "refresh_token", config.OIDC.Clients[1].GrantTypes[0]) + + require.Len(t, config.OIDC.Clients[1].ResponseTypes, 2) + assert.Equal(t, "token", config.OIDC.Clients[1].ResponseTypes[0]) + assert.Equal(t, "code", config.OIDC.Clients[1].ResponseTypes[1]) +} diff --git a/internal/configuration/validator/secrets.go b/internal/configuration/validator/secrets.go index cb46513d..285c413a 100644 --- a/internal/configuration/validator/secrets.go +++ b/internal/configuration/validator/secrets.go @@ -58,6 +58,11 @@ func ValidateSecrets(configuration *schema.Configuration, validator *schema.Stru if configuration.Storage.PostgreSQL != nil { configuration.Storage.PostgreSQL.Password = getSecretValue(SecretNames["PostgreSQLPassword"], validator, viper) } + + if configuration.IdentityProviders.OIDC != nil { + configuration.IdentityProviders.OIDC.HMACSecret = getSecretValue(SecretNames["OpenIDConnectHMACSecret"], validator, viper) + configuration.IdentityProviders.OIDC.IssuerPrivateKey = getSecretValue(SecretNames["OpenIDConnectIssuerPrivateKey"], validator, viper) + } } func getSecretValue(name string, validator *schema.StructValidator, viper *viper.Viper) string { @@ -75,7 +80,8 @@ func getSecretValue(name string, validator *schema.StructValidator, viper *viper if err != nil { validator.Push(fmt.Errorf("error loading secret file (%s): %s", name, err)) } else { - return strings.ReplaceAll(string(content), "\n", "") + // TODO: Test this functionality. + return strings.TrimRight(string(content), "\n") } } diff --git a/internal/handlers/const.go b/internal/handlers/const.go index c3f3701e..5a076b1b 100644 --- a/internal/handlers/const.go +++ b/internal/handlers/const.go @@ -25,8 +25,6 @@ const remoteNameHeader = "Remote-Name" const remoteEmailHeader = "Remote-Email" const remoteGroupsHeader = "Remote-Groups" -var protoHostSeparator = []byte("://") - const ( // Forbidden means the user is forbidden the access to a resource. Forbidden authorizationMatching = iota @@ -57,3 +55,30 @@ const testUsername = "john" const movingAverageWindow = 10 const msMinimumDelay1FA = float64(250) const msMaximumRandomDelay = int64(85) + +// OIDC constants. +const ( + oidcWellKnownPath = "/.well-known/openid-configuration" + oidcJWKsPath = "/api/oidc/jwks" + oidcAuthorizePath = "/api/oidc/authorize" + oidcTokenPath = "/api/oidc/token" //nolint:gosec // This is not a hard coded credential, it's a path. + oidcIntrospectPath = "/api/oidc/introspect" + oidcRevokePath = "/api/oidc/revoke" + + // Note: If you change this const you must also do so in the frontend at web/src/services/Api.ts. + oidcConsentPath = "/api/oidc/consent" +) + +const ( + accept = "accept" + reject = "reject" +) + +var scopeDescriptions = map[string]string{ + "openid": "Use OpenID to verify your identity", + "email": "Access your email addresses", + "profile": "Access your username", + "groups": "Access your group membership", +} + +var audienceDescriptions = map[string]string{} diff --git a/internal/handlers/handler_firstfactor.go b/internal/handlers/handler_firstfactor.go index 56201a8f..39b02cc4 100644 --- a/internal/handlers/handler_firstfactor.go +++ b/internal/handlers/handler_firstfactor.go @@ -127,8 +127,12 @@ func FirstFactorPost(msInitialDelay time.Duration, delayEnabled bool) middleware ctx.Logger.Debugf("Credentials validation of user %s is ok", bodyJSON.Username) - // Reset all values from previous session before regenerating the cookie. - err = ctx.SaveSession(session.NewDefaultUserSession()) + userSession := ctx.GetSession() + newSession := session.NewDefaultUserSession() + newSession.OIDCWorkflowSession = userSession.OIDCWorkflowSession + + // Reset all values from previous session except OIDC workflow before regenerating the cookie. + err = ctx.SaveSession(newSession) if err != nil { handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to reset the session for user %s: %s", bodyJSON.Username, err.Error()), authenticationFailedMessage) @@ -165,7 +169,6 @@ func FirstFactorPost(msInitialDelay time.Duration, delayEnabled bool) middleware ctx.Logger.Tracef("Details for user %s => groups: %s, emails %s", bodyJSON.Username, userDetails.Groups, userDetails.Emails) // And set those information in the new session. - userSession := ctx.GetSession() userSession.Username = userDetails.Username userSession.DisplayName = userDetails.DisplayName userSession.Groups = userDetails.Groups @@ -188,6 +191,10 @@ func FirstFactorPost(msInitialDelay time.Duration, delayEnabled bool) middleware successful = true - Handle1FAResponse(ctx, bodyJSON.TargetURL, bodyJSON.RequestMethod, userSession.Username, userSession.Groups) + if userSession.OIDCWorkflowSession != nil { + HandleOIDCWorkflowResponse(ctx) + } else { + Handle1FAResponse(ctx, bodyJSON.TargetURL, bodyJSON.RequestMethod, userSession.Username, userSession.Groups) + } } } diff --git a/internal/handlers/handler_oidc_authorize.go b/internal/handlers/handler_oidc_authorize.go new file mode 100644 index 00000000..3220949b --- /dev/null +++ b/internal/handlers/handler_oidc_authorize.go @@ -0,0 +1,129 @@ +package handlers + +import ( + "fmt" + "net/http" + "strings" + + "github.com/ory/fosite" + + "github.com/authelia/authelia/internal/logging" + "github.com/authelia/authelia/internal/middlewares" + "github.com/authelia/authelia/internal/oidc" + "github.com/authelia/authelia/internal/session" +) + +func oidcAuthorize(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, r *http.Request) { + ar, err := ctx.Providers.OpenIDConnect.Fosite.NewAuthorizeRequest(ctx, r) + if err != nil { + logging.Logger().Errorf("Error occurred in NewAuthorizeRequest: %+v", err) + ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err) + + return + } + + clientID := ar.GetClient().GetID() + client, err := ctx.Providers.OpenIDConnect.Store.GetInternalClient(clientID) + + if err != nil { + err := fmt.Errorf("Unable to find related client configuration with name '%s': %v", ar.GetID(), err) + ctx.Logger.Error(err) + ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err) + + return + } + + userSession := ctx.GetSession() + + requestedScopes := ar.GetRequestedScopes() + requestedAudience := ar.GetRequestedAudience() + + isAuthInsufficient := !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) + + if isAuthInsufficient || (isConsentMissing(userSession.OIDCWorkflowSession, requestedScopes, requestedAudience)) { + oidcAuthorizeHandleAuthorizationOrConsentInsufficient(ctx, userSession, client, isAuthInsufficient, rw, r, ar) + + return + } + + for _, scope := range requestedScopes { + ar.GrantScope(scope) + } + + for _, a := range requestedAudience { + ar.GrantAudience(a) + } + + userSession.OIDCWorkflowSession = nil + if err := ctx.SaveSession(userSession); err != nil { + ctx.Logger.Errorf("%v", err) + http.Error(rw, err.Error(), http.StatusInternalServerError) + + return + } + + oauthSession, err := newOIDCSession(ctx, ar) + if err != nil { + ctx.Logger.Errorf("Error occurred in NewOIDCSession: %+v", err) + ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err) + + return + } + + response, err := ctx.Providers.OpenIDConnect.Fosite.NewAuthorizeResponse(ctx, ar, oauthSession) + if err != nil { + ctx.Logger.Errorf("Error occurred in NewAuthorizeResponse: %+v", err) + ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err) + + return + } + + ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeResponse(rw, ar, response) +} + +func oidcAuthorizeHandleAuthorizationOrConsentInsufficient( + ctx *middlewares.AutheliaCtx, userSession session.UserSession, client *oidc.InternalClient, isAuthInsufficient bool, + rw http.ResponseWriter, r *http.Request, + ar fosite.AuthorizeRequester) { + forwardedProtoHost, err := ctx.ForwardedProtoHost() + if err != nil { + ctx.Logger.Errorf("%v", err) + http.Error(rw, err.Error(), http.StatusBadRequest) + + return + } + + redirectURL := fmt.Sprintf("%s%s", forwardedProtoHost, string(ctx.Request.RequestURI())) + + ctx.Logger.Debugf("User %s must consent with scopes %s", + userSession.Username, strings.Join(ar.GetRequestedScopes(), ", ")) + + userSession.OIDCWorkflowSession = new(session.OIDCWorkflowSession) + userSession.OIDCWorkflowSession.ClientID = client.ID + userSession.OIDCWorkflowSession.RequestedScopes = ar.GetRequestedScopes() + userSession.OIDCWorkflowSession.RequestedAudience = ar.GetRequestedAudience() + userSession.OIDCWorkflowSession.AuthURI = redirectURL + userSession.OIDCWorkflowSession.TargetURI = ar.GetRedirectURI().String() + userSession.OIDCWorkflowSession.RequiredAuthorizationLevel = client.Policy + + if err := ctx.SaveSession(userSession); err != nil { + ctx.Logger.Errorf("%v", err) + http.Error(rw, err.Error(), http.StatusInternalServerError) + + return + } + + uri, err := ctx.ForwardedProtoHost() + if err != nil { + ctx.Logger.Errorf("%v", err) + http.Error(rw, err.Error(), http.StatusBadRequest) + + return + } + + if isAuthInsufficient { + http.Redirect(rw, r, uri, http.StatusFound) + } else { + http.Redirect(rw, r, fmt.Sprintf("%s/consent", uri), http.StatusFound) + } +} diff --git a/internal/handlers/handler_oidc_consent.go b/internal/handlers/handler_oidc_consent.go new file mode 100644 index 00000000..b96d6840 --- /dev/null +++ b/internal/handlers/handler_oidc_consent.go @@ -0,0 +1,124 @@ +package handlers + +import ( + "encoding/json" + "fmt" + + "github.com/authelia/authelia/internal/middlewares" +) + +func oidcConsent(ctx *middlewares.AutheliaCtx) { + userSession := ctx.GetSession() + + if userSession.OIDCWorkflowSession == nil { + ctx.Logger.Debugf("Cannot consent for user %s when OIDC workflow has not been initiated", userSession.Username) + ctx.ReplyForbidden() + + return + } + + clientID := userSession.OIDCWorkflowSession.ClientID + client, err := ctx.Providers.OpenIDConnect.Store.GetInternalClient(clientID) + + if err != nil { + ctx.Logger.Debugf("Unable to find related client configuration with name '%s': %v", clientID, err) + ctx.ReplyForbidden() + + return + } + + if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) { + ctx.Logger.Debugf("Insufficient permissions to give consent v2 %d -> %d", userSession.AuthenticationLevel, userSession.OIDCWorkflowSession.RequiredAuthorizationLevel) + ctx.ReplyForbidden() + + return + } + + var body ConsentGetResponseBody + body.Scopes = scopeNamesToScopes(userSession.OIDCWorkflowSession.RequestedScopes) + body.Audience = audienceNamesToAudience(userSession.OIDCWorkflowSession.RequestedAudience) + body.ClientID = client.ID + body.ClientDescription = client.Description + + if err := ctx.SetJSONBody(body); err != nil { + ctx.Error(fmt.Errorf("Unable to set JSON body: %v", err), "Operation failed") + } +} + +func oidcConsentPOST(ctx *middlewares.AutheliaCtx) { + userSession := ctx.GetSession() + + if userSession.OIDCWorkflowSession == nil { + ctx.Logger.Debugf("Cannot consent for user %s when OIDC workflow has not been initiated", userSession.Username) + ctx.ReplyForbidden() + + return + } + + client, err := ctx.Providers.OpenIDConnect.Store.GetInternalClient(userSession.OIDCWorkflowSession.ClientID) + + if err != nil { + ctx.Logger.Debugf("Unable to find related client configuration with name '%s': %v", userSession.OIDCWorkflowSession.ClientID, err) + ctx.ReplyForbidden() + + return + } + + if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) { + ctx.Logger.Debugf("Insufficient permissions to give consent v1 %d -> %d", userSession.AuthenticationLevel, userSession.OIDCWorkflowSession.RequiredAuthorizationLevel) + ctx.ReplyForbidden() + + return + } + + var body ConsentPostRequestBody + err = json.Unmarshal(ctx.Request.Body(), &body) + + if err != nil { + ctx.Error(fmt.Errorf("Unable to unmarshal body: %v", err), "Operation failed") + return + } + + if body.AcceptOrReject != accept && body.AcceptOrReject != reject { + ctx.Logger.Infof("User %s tried to reply to consent with an unexpected verb", userSession.Username) + ctx.ReplyBadRequest() + + return + } + + if userSession.OIDCWorkflowSession.ClientID != body.ClientID { + ctx.Logger.Infof("User %s consented to scopes of another client (%s) than expected (%s). Beware this can be a sign of attack", + userSession.Username, body.ClientID, userSession.OIDCWorkflowSession.ClientID) + ctx.ReplyBadRequest() + + return + } + + var redirectionURL string + + if body.AcceptOrReject == accept { + redirectionURL = userSession.OIDCWorkflowSession.AuthURI + userSession.OIDCWorkflowSession.GrantedScopes = userSession.OIDCWorkflowSession.RequestedScopes + userSession.OIDCWorkflowSession.GrantedAudience = userSession.OIDCWorkflowSession.RequestedAudience + + if err := ctx.SaveSession(userSession); err != nil { + ctx.Error(fmt.Errorf("Unable to write session: %v", err), "Operation failed") + return + } + } else if body.AcceptOrReject == reject { + redirectionURL = fmt.Sprintf("%s?error=access_denied&error_description=%s", + userSession.OIDCWorkflowSession.TargetURI, "User has rejected the scopes") + userSession.OIDCWorkflowSession = nil + + if err := ctx.SaveSession(userSession); err != nil { + ctx.Error(fmt.Errorf("Unable to write session: %v", err), "Operation failed") + return + } + } + + response := ConsentPostResponseBody{RedirectURI: redirectionURL} + + if err := ctx.SetJSONBody(response); err != nil { + ctx.Error(fmt.Errorf("Unable to set JSON body in response"), "Operation failed") + } +} diff --git a/internal/handlers/handler_oidc_introspect.go b/internal/handlers/handler_oidc_introspect.go new file mode 100644 index 00000000..6873c8d4 --- /dev/null +++ b/internal/handlers/handler_oidc_introspect.go @@ -0,0 +1,29 @@ +package handlers + +import ( + "net/http" + + "github.com/authelia/authelia/internal/middlewares" +) + +func oidcIntrospect(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *http.Request) { + oidcSession, err := newDefaultOIDCSession(ctx) + + if err != nil { + ctx.Logger.Errorf("Error occurred in NewDefaultOIDCSession: %+v", err) + ctx.Providers.OpenIDConnect.Fosite.WriteIntrospectionError(rw, err) + + return + } + + ir, err := ctx.Providers.OpenIDConnect.Fosite.NewIntrospectionRequest(ctx, req, oidcSession) + + if err != nil { + ctx.Logger.Errorf("Error occurred in NewIntrospectionRequest: %+v", err) + ctx.Providers.OpenIDConnect.Fosite.WriteIntrospectionError(rw, err) + + return + } + + ctx.Providers.OpenIDConnect.Fosite.WriteIntrospectionResponse(rw, ir) +} diff --git a/internal/handlers/handler_oidc_jwks.go b/internal/handlers/handler_oidc_jwks.go new file mode 100644 index 00000000..6ca68f95 --- /dev/null +++ b/internal/handlers/handler_oidc_jwks.go @@ -0,0 +1,15 @@ +package handlers + +import ( + "encoding/json" + + "github.com/authelia/authelia/internal/middlewares" +) + +func oidcJWKs(ctx *middlewares.AutheliaCtx) { + ctx.SetContentType("application/json") + + if err := json.NewEncoder(ctx).Encode(ctx.Providers.OpenIDConnect.GetKeySet()); err != nil { + ctx.Error(err, "failed to serve jwk set") + } +} diff --git a/internal/handlers/handler_oidc_revoke.go b/internal/handlers/handler_oidc_revoke.go new file mode 100644 index 00000000..ac6f9319 --- /dev/null +++ b/internal/handlers/handler_oidc_revoke.go @@ -0,0 +1,13 @@ +package handlers + +import ( + "net/http" + + "github.com/authelia/authelia/internal/middlewares" +) + +func oidcRevoke(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *http.Request) { + err := ctx.Providers.OpenIDConnect.Fosite.NewRevocationRequest(ctx, req) + + ctx.Providers.OpenIDConnect.Fosite.WriteRevocationResponse(rw, err) +} diff --git a/internal/handlers/handler_oidc_token.go b/internal/handlers/handler_oidc_token.go new file mode 100644 index 00000000..ce7f962c --- /dev/null +++ b/internal/handlers/handler_oidc_token.go @@ -0,0 +1,46 @@ +package handlers + +import ( + "net/http" + + "github.com/ory/fosite" + + "github.com/authelia/authelia/internal/middlewares" +) + +func oidcToken(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *http.Request) { + oidcSession, err := newDefaultOIDCSession(ctx) + if err != nil { + ctx.Logger.Errorf("Error occurred in NewDefaultOIDCSession: %+v", err) + ctx.Providers.OpenIDConnect.Fosite.WriteAccessError(rw, nil, err) + + return + } + + accessRequest, accessReqErr := ctx.Providers.OpenIDConnect.Fosite.NewAccessRequest(ctx, req, oidcSession) + if accessReqErr != nil { + ctx.Logger.Errorf("Error occurred in NewAccessRequest: %+v", accessRequest) + ctx.Providers.OpenIDConnect.Fosite.WriteAccessError(rw, accessRequest, accessReqErr) + + return + } + + // If this is a client_credentials grant, grant all scopes the client is allowed to perform. + if accessRequest.GetGrantTypes().ExactOne("client_credentials") { + for _, scope := range accessRequest.GetRequestedScopes() { + if fosite.HierarchicScopeStrategy(accessRequest.GetClient().GetScopes(), scope) { + accessRequest.GrantScope(scope) + } + } + } + + response, err := ctx.Providers.OpenIDConnect.Fosite.NewAccessResponse(ctx, accessRequest) + if err != nil { + ctx.Logger.Errorf("Error occurred in NewAccessResponse: %+v", err) + ctx.Providers.OpenIDConnect.Fosite.WriteAccessError(rw, accessRequest, err) + + return + } + + ctx.Providers.OpenIDConnect.Fosite.WriteAccessResponse(rw, accessRequest, response) +} diff --git a/internal/handlers/handler_oidc_wellknown.go b/internal/handlers/handler_oidc_wellknown.go new file mode 100644 index 00000000..ca0fa06a --- /dev/null +++ b/internal/handlers/handler_oidc_wellknown.go @@ -0,0 +1,73 @@ +package handlers + +import ( + "encoding/json" + "fmt" + + "github.com/valyala/fasthttp" + + "github.com/authelia/authelia/internal/middlewares" +) + +func oidcWellKnown(ctx *middlewares.AutheliaCtx) { + var configuration WellKnownConfigurationJSON + + issuer, err := ctx.ForwardedProtoHost() + if err != nil { + ctx.Logger.Errorf("Error occurred in ForwardedProtoHost: %+v", err) + ctx.Response.SetStatusCode(fasthttp.StatusBadRequest) + + return + } + + configuration.Issuer = issuer + configuration.AuthURL = fmt.Sprintf("%s%s", issuer, oidcAuthorizePath) + configuration.TokenURL = fmt.Sprintf("%s%s", issuer, oidcTokenPath) + configuration.RevocationEndpoint = fmt.Sprintf("%s%s", issuer, oidcRevokePath) + configuration.JWKSURL = fmt.Sprintf("%s%s", issuer, oidcJWKsPath) + configuration.Algorithms = []string{"RS256"} + configuration.ScopesSupported = []string{ + "openid", + "profile", + "groups", + "email", + // Determine if this is really mandatory knowing the RP can request for a refresh token through the authorize + // endpoint anyway. + "offline_access", + } + configuration.ClaimsSupported = []string{ + "aud", + "exp", + "iat", + "iss", + "jti", + "rat", + "sub", + "auth_time", + "nonce", + "email", + "email_verified", + "groups", + "name", + } + configuration.ResponseTypesSupported = []string{ + "code", + "token", + "id_token", + "code token", + "code id_token", + "token id_token", + "code token id_token", + "none", + } + + ctx.SetContentType("application/json") + + if err := json.NewEncoder(ctx).Encode(configuration); err != nil { + ctx.Logger.Errorf("Error occurred in json Encode: %+v", err) + // TODO: Determine if this is the appropriate error code here. + ctx.Response.SetStatusCode(fasthttp.StatusInternalServerError) + + return + } +} diff --git a/internal/handlers/handler_sign_duo.go b/internal/handlers/handler_sign_duo.go index 2eaedb1b..bf4666e4 100644 --- a/internal/handlers/handler_sign_duo.go +++ b/internal/handlers/handler_sign_duo.go @@ -73,6 +73,10 @@ func SecondFactorDuoPost(duoAPI duo.API) middlewares.RequestHandler { return } - Handle2FAResponse(ctx, requestBody.TargetURL) + if userSession.OIDCWorkflowSession != nil { + HandleOIDCWorkflowResponse(ctx) + } else { + Handle2FAResponse(ctx, requestBody.TargetURL) + } } } diff --git a/internal/handlers/handler_sign_totp.go b/internal/handlers/handler_sign_totp.go index 3425b68b..bc604e13 100644 --- a/internal/handlers/handler_sign_totp.go +++ b/internal/handlers/handler_sign_totp.go @@ -10,8 +10,8 @@ import ( // SecondFactorTOTPPost validate the TOTP passcode provided by the user. func SecondFactorTOTPPost(totpVerifier TOTPVerifier) middlewares.RequestHandler { return func(ctx *middlewares.AutheliaCtx) { - bodyJSON := signTOTPRequestBody{} - err := ctx.ParseBody(&bodyJSON) + requestBody := signTOTPRequestBody{} + err := ctx.ParseBody(&requestBody) if err != nil { handleAuthenticationUnauthorized(ctx, err, mfaValidationFailedMessage) @@ -26,7 +26,7 @@ func SecondFactorTOTPPost(totpVerifier TOTPVerifier) middlewares.RequestHandler return } - isValid, err := totpVerifier.Verify(bodyJSON.Token, secret) + isValid, err := totpVerifier.Verify(requestBody.Token, secret) if err != nil { handleAuthenticationUnauthorized(ctx, fmt.Errorf("Error occurred during OTP validation for user %s: %s", userSession.Username, err), mfaValidationFailedMessage) return @@ -52,6 +52,10 @@ func SecondFactorTOTPPost(totpVerifier TOTPVerifier) middlewares.RequestHandler return } - Handle2FAResponse(ctx, bodyJSON.TargetURL) + if userSession.OIDCWorkflowSession != nil { + HandleOIDCWorkflowResponse(ctx) + } else { + Handle2FAResponse(ctx, requestBody.TargetURL) + } } } diff --git a/internal/handlers/handler_sign_u2f_step2.go b/internal/handlers/handler_sign_u2f_step2.go index 5a51722f..e7bd75f1 100644 --- a/internal/handlers/handler_sign_u2f_step2.go +++ b/internal/handlers/handler_sign_u2f_step2.go @@ -55,6 +55,10 @@ func SecondFactorU2FSignPost(u2fVerifier U2FVerifier) middlewares.RequestHandler return } - Handle2FAResponse(ctx, requestBody.TargetURL) + if userSession.OIDCWorkflowSession != nil { + HandleOIDCWorkflowResponse(ctx) + } else { + Handle2FAResponse(ctx, requestBody.TargetURL) + } } } diff --git a/internal/handlers/handler_verify.go b/internal/handlers/handler_verify.go index 951c8d85..0b08059a 100644 --- a/internal/handlers/handler_verify.go +++ b/internal/handlers/handler_verify.go @@ -31,49 +31,6 @@ func isSchemeWSS(url *url.URL) bool { return url.Scheme == "wss" } -// getOriginalURL extract the URL from the request headers (X-Original-URI or X-Forwarded-* headers). -func getOriginalURL(ctx *middlewares.AutheliaCtx) (*url.URL, error) { - originalURL := ctx.XOriginalURL() - if originalURL != nil { - url, err := url.ParseRequestURI(string(originalURL)) - if err != nil { - return nil, fmt.Errorf("Unable to parse URL extracted from X-Original-URL header: %v", err) - } - - ctx.Logger.Trace("Using X-Original-URL header content as targeted site URL") - - return url, nil - } - - forwardedProto := ctx.XForwardedProto() - forwardedHost := ctx.XForwardedHost() - forwardedURI := ctx.XForwardedURI() - - if forwardedProto == nil { - return nil, errMissingXForwardedProto - } - - if forwardedHost == nil { - return nil, errMissingXForwardedHost - } - - var requestURI string - - scheme := append(forwardedProto, protoHostSeparator...) - requestURI = string(append(scheme, - append(forwardedHost, forwardedURI...)...)) - - url, err := url.ParseRequestURI(requestURI) - if err != nil { - return nil, fmt.Errorf("Unable to parse URL %s: %v", requestURI, err) - } - - ctx.Logger.Tracef("Using X-Forwarded-Proto, X-Forwarded-Host and X-Forwarded-URI headers " + - "to construct targeted site URL") - - return url, nil -} - // parseBasicAuth parses an HTTP Basic Authentication string. // "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true). func parseBasicAuth(header, auth string) (username, password string, err error) { @@ -468,7 +425,7 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques return func(ctx *middlewares.AutheliaCtx) { ctx.Logger.Tracef("Headers=%s", ctx.Request.Header.String()) - targetURL, err := getOriginalURL(ctx) + targetURL, err := ctx.GetOriginalURL() if err != nil { ctx.Error(fmt.Errorf("Unable to parse target URL: %s", err), operationFailedMessage) diff --git a/internal/handlers/handler_verify_test.go b/internal/handlers/handler_verify_test.go index b1427fea..14238805 100644 --- a/internal/handlers/handler_verify_test.go +++ b/internal/handlers/handler_verify_test.go @@ -26,49 +26,13 @@ var verifyGetCfg = schema.AuthenticationBackendConfiguration{ LDAP: &schema.LDAPAuthenticationBackendConfiguration{}, } -// Test getOriginalURL. -func TestShouldGetOriginalURLFromOriginalURLHeader(t *testing.T) { - mock := mocks.NewMockAutheliaCtx(t) - defer mock.Close() - - mock.Ctx.Request.Header.Set("X-Original-URL", "https://home.example.com") - originalURL, err := getOriginalURL(mock.Ctx) - assert.NoError(t, err) - - expectedURL, err := url.ParseRequestURI("https://home.example.com") - assert.NoError(t, err) - assert.Equal(t, expectedURL, originalURL) -} - -func TestShouldGetOriginalURLFromForwardedHeadersWithoutURI(t *testing.T) { - mock := mocks.NewMockAutheliaCtx(t) - defer mock.Close() - mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https") - mock.Ctx.Request.Header.Set("X-Forwarded-Host", "home.example.com") - originalURL, err := getOriginalURL(mock.Ctx) - assert.NoError(t, err) - - expectedURL, err := url.ParseRequestURI("https://home.example.com") - assert.NoError(t, err) - assert.Equal(t, expectedURL, originalURL) -} - -func TestShouldGetOriginalURLFromForwardedHeadersWithURI(t *testing.T) { - mock := mocks.NewMockAutheliaCtx(t) - defer mock.Close() - mock.Ctx.Request.Header.Set("X-Original-URL", "htt-ps//home?-.example.com") - _, err := getOriginalURL(mock.Ctx) - assert.Error(t, err) - assert.Equal(t, "Unable to parse URL extracted from X-Original-URL header: parse \"htt-ps//home?-.example.com\": invalid URI for request", err.Error()) -} - func TestShouldRaiseWhenTargetUrlIsMalformed(t *testing.T) { mock := mocks.NewMockAutheliaCtx(t) defer mock.Close() mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https") mock.Ctx.Request.Header.Set("X-Forwarded-Host", "home.example.com") mock.Ctx.Request.Header.Set("X-Forwarded-URI", "/abc") - originalURL, err := getOriginalURL(mock.Ctx) + originalURL, err := mock.Ctx.GetOriginalURL() assert.NoError(t, err) expectedURL, err := url.ParseRequestURI("https://home.example.com/abc") @@ -79,7 +43,7 @@ func TestShouldRaiseWhenTargetUrlIsMalformed(t *testing.T) { func TestShouldRaiseWhenNoHeaderProvidedToDetectTargetURL(t *testing.T) { mock := mocks.NewMockAutheliaCtx(t) defer mock.Close() - _, err := getOriginalURL(mock.Ctx) + _, err := mock.Ctx.GetOriginalURL() assert.Error(t, err) assert.Equal(t, "Missing header X-Forwarded-Proto", err.Error()) } @@ -89,7 +53,7 @@ func TestShouldRaiseWhenNoXForwardedHostHeaderProvidedToDetectTargetURL(t *testi defer mock.Close() mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https") - _, err := getOriginalURL(mock.Ctx) + _, err := mock.Ctx.GetOriginalURL() assert.Error(t, err) assert.Equal(t, "Missing header X-Forwarded-Host", err.Error()) } @@ -101,7 +65,7 @@ func TestShouldRaiseWhenXForwardedProtoIsNotParsable(t *testing.T) { mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "!:;;:,") mock.Ctx.Request.Header.Set("X-Forwarded-Host", "myhost.local") - _, err := getOriginalURL(mock.Ctx) + _, err := mock.Ctx.GetOriginalURL() assert.Error(t, err) assert.Equal(t, "Unable to parse URL !:;;:,://myhost.local: parse \"!:;;:,://myhost.local\": invalid URI for request", err.Error()) } @@ -114,7 +78,7 @@ func TestShouldRaiseWhenXForwardedURIIsNotParsable(t *testing.T) { mock.Ctx.Request.Header.Set("X-Forwarded-Host", "myhost.local") mock.Ctx.Request.Header.Set("X-Forwarded-URI", "!:;;:,") - _, err := getOriginalURL(mock.Ctx) + _, err := mock.Ctx.GetOriginalURL() require.Error(t, err) assert.Equal(t, "Unable to parse URL https://myhost.local!:;;:,: parse \"https://myhost.local!:;;:,\": invalid port \":,\" after host", err.Error()) } diff --git a/internal/handlers/oidc.go b/internal/handlers/oidc.go new file mode 100644 index 00000000..7c650939 --- /dev/null +++ b/internal/handlers/oidc.go @@ -0,0 +1,106 @@ +package handlers + +import ( + "time" + + "github.com/ory/fosite" + "github.com/ory/fosite/handler/openid" + "github.com/ory/fosite/token/jwt" + + "github.com/authelia/authelia/internal/middlewares" + "github.com/authelia/authelia/internal/session" + "github.com/authelia/authelia/internal/utils" +) + +// isConsentMissing compares the requestedScopes and requestedAudience to the workflows +// GrantedScopes and GrantedAudience and returns true if they do not match or the workflow is nil. +func isConsentMissing(workflow *session.OIDCWorkflowSession, requestedScopes, requestedAudience []string) (isMissing bool) { + if workflow == nil { + return true + } + + return len(requestedScopes) > 0 && utils.IsStringSlicesDifferent(requestedScopes, workflow.GrantedScopes) || + len(requestedAudience) > 0 && utils.IsStringSlicesDifferentFold(requestedAudience, workflow.GrantedAudience) +} + +func scopeNamesToScopes(scopeSlice []string) (scopes []Scope) { + for _, name := range scopeSlice { + if val, ok := scopeDescriptions[name]; ok { + scopes = append(scopes, Scope{name, val}) + } else { + scopes = append(scopes, Scope{name, name}) + } + } + + return scopes +} + +func audienceNamesToAudience(scopeSlice []string) (audience []Audience) { + for _, name := range scopeSlice { + if val, ok := audienceDescriptions[name]; ok { + audience = append(audience, Audience{name, val}) + } else { + audience = append(audience, Audience{name, name}) + } + } + + return audience +} + +func newOIDCSession(ctx *middlewares.AutheliaCtx, ar fosite.AuthorizeRequester) (session *openid.DefaultSession, err error) { + userSession := ctx.GetSession() + + scopes := ar.GetGrantedScopes() + + extra := map[string]interface{}{} + + if len(userSession.Emails) != 0 && scopes.Has("email") { + extra["email"] = userSession.Emails[0] + extra["email_verified"] = true + } + + if scopes.Has("groups") { + extra["groups"] = userSession.Groups + } + + if scopes.Has("profile") { + extra["name"] = userSession.DisplayName + } + + /* + TODO: Adjust auth backends to return more profile information. + It's probably ideal to adjust the auth providers at this time to not store 'extra' information in the session + storage, and instead create a memory only storage for them. + This is a simple design, have a map with a key of username, and a struct with the relevant information. + */ + + oidcSession, err := newDefaultOIDCSession(ctx) + if oidcSession == nil { + return nil, err + } + + oidcSession.Claims.Extra = extra + oidcSession.Claims.Subject = userSession.Username + oidcSession.Claims.Audience = ar.GetGrantedAudience() + + return oidcSession, err +} + +func newDefaultOIDCSession(ctx *middlewares.AutheliaCtx) (session *openid.DefaultSession, err error) { + issuer, err := ctx.ForwardedProtoHost() + + return &openid.DefaultSession{ + Claims: &jwt.IDTokenClaims{ + Issuer: issuer, + // TODO(c.michaud): make this configurable + ExpiresAt: time.Now().Add(time.Hour * 6), + IssuedAt: time.Now(), + RequestedAt: time.Now(), + AuthTime: time.Now(), + Extra: make(map[string]interface{}), + }, + Headers: &jwt.Headers{ + Extra: make(map[string]interface{}), + }, + }, err +} diff --git a/internal/handlers/oidc_test.go b/internal/handlers/oidc_test.go new file mode 100644 index 00000000..7e77e6a3 --- /dev/null +++ b/internal/handlers/oidc_test.go @@ -0,0 +1,33 @@ +package handlers + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/authelia/authelia/internal/session" +) + +func TestShouldDetectIfConsentIsMissing(t *testing.T) { + var workflow *session.OIDCWorkflowSession + + requestedScopes := []string{"openid", "profile"} + requestedAudience := []string{"https://authelia.com"} + + assert.True(t, isConsentMissing(workflow, requestedScopes, requestedAudience)) + + workflow = &session.OIDCWorkflowSession{ + GrantedScopes: []string{"openid", "profile"}, + GrantedAudience: []string{"https://authelia.com"}, + } + + assert.False(t, isConsentMissing(workflow, requestedScopes, requestedAudience)) + + requestedScopes = []string{"openid", "profile", "group"} + + assert.True(t, isConsentMissing(workflow, requestedScopes, requestedAudience)) + + requestedScopes = []string{"openid", "profile"} + requestedAudience = []string{"https://not.authelia.com"} + assert.True(t, isConsentMissing(workflow, requestedScopes, requestedAudience)) +} diff --git a/internal/handlers/register_oidc.go b/internal/handlers/register_oidc.go new file mode 100644 index 00000000..75d34fab --- /dev/null +++ b/internal/handlers/register_oidc.go @@ -0,0 +1,29 @@ +package handlers + +import ( + "github.com/fasthttp/router" + + "github.com/authelia/authelia/internal/middlewares" +) + +// RegisterOIDC registers the handlers with the fasthttp *router.Router. TODO: Add paths for UserInfo, Flush, Logout. +func RegisterOIDC(router *router.Router, middleware middlewares.RequestHandlerBridge) { + // TODO: Add OPTIONS handler. + router.GET(oidcWellKnownPath, middleware(oidcWellKnown)) + + router.GET(oidcConsentPath, middleware(oidcConsent)) + + router.POST(oidcConsentPath, middleware(oidcConsentPOST)) + + router.GET(oidcJWKsPath, middleware(oidcJWKs)) + + router.GET(oidcAuthorizePath, middleware(middlewares.NewHTTPToAutheliaHandlerAdaptor(oidcAuthorize))) + + // TODO: Add OPTIONS handler. + router.POST(oidcTokenPath, middleware(middlewares.NewHTTPToAutheliaHandlerAdaptor(oidcToken))) + + router.POST(oidcIntrospectPath, middleware(middlewares.NewHTTPToAutheliaHandlerAdaptor(oidcIntrospect))) + + // TODO: Add OPTIONS handler. + router.POST(oidcRevokePath, middleware(middlewares.NewHTTPToAutheliaHandlerAdaptor(oidcRevoke))) +} diff --git a/internal/handlers/response.go b/internal/handlers/response.go index ebff9dbb..70220879 100644 --- a/internal/handlers/response.go +++ b/internal/handlers/response.go @@ -11,6 +11,42 @@ import ( "github.com/authelia/authelia/internal/utils" ) +// HandleOIDCWorkflowResponse handle the redirection upon authentication in the OIDC workflow. +func HandleOIDCWorkflowResponse(ctx *middlewares.AutheliaCtx) { + userSession := ctx.GetSession() + + if !authorization.IsAuthLevelSufficient(userSession.AuthenticationLevel, userSession.OIDCWorkflowSession.RequiredAuthorizationLevel) { + ctx.Logger.Warn("OIDC requires 2FA, cannot be redirected yet") + ctx.ReplyOK() + + return + } + + uri, err := ctx.ForwardedProtoHost() + if err != nil { + ctx.Logger.Errorf("%v", err) + handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to get forward facing URI"), authenticationFailedMessage) + + return + } + + if isConsentMissing( + userSession.OIDCWorkflowSession, + userSession.OIDCWorkflowSession.RequestedScopes, + userSession.OIDCWorkflowSession.RequestedAudience) { + err := ctx.SetJSONBody(redirectResponse{Redirect: fmt.Sprintf("%s/consent", uri)}) + + if err != nil { + ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err) + } + } else { + err := ctx.SetJSONBody(redirectResponse{Redirect: userSession.OIDCWorkflowSession.AuthURI}) + if err != nil { + ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err) + } + } +} + // Handle1FAResponse handle the redirection upon 1FA authentication. func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI, requestMethod string, username string, groups []string) { if targetURI == "" { diff --git a/internal/handlers/types_oidc.go b/internal/handlers/types_oidc.go new file mode 100644 index 00000000..47bf5361 --- /dev/null +++ b/internal/handlers/types_oidc.go @@ -0,0 +1,62 @@ +package handlers + +import ( + "github.com/dgrijalva/jwt-go" +) + +// ConsentPostRequestBody schema of the request body of the consent POST endpoint. +type ConsentPostRequestBody struct { + ClientID string `json:"client_id"` + AcceptOrReject string `json:"accept_or_reject"` +} + +// ConsentPostResponseBody schema of the response body of the consent POST endpoint. +type ConsentPostResponseBody struct { + RedirectURI string `json:"redirect_uri"` +} + +// ConsentGetResponseBody schema of the response body of the consent GET endpoint. +type ConsentGetResponseBody struct { + ClientID string `json:"client_id"` + ClientDescription string `json:"client_description"` + Scopes []Scope `json:"scopes"` + Audience []Audience `json:"audience"` +} + +// Scope represents the scope information. +type Scope struct { + Name string `json:"name"` + Description string `json:"description"` +} + +// Audience represents the audience information. +type Audience struct { + Name string `json:"name"` + Description string `json:"description"` +} + +// OIDCClaims represents a set of OIDC claims. +type OIDCClaims struct { + jwt.StandardClaims + + Workflow string `json:"workflow"` + Username string `json:"username,omitempty"` + RequestedScopes []string `json:"requested_scopes,omitempty"` +} + +// WellKnownConfigurationJSON is the OIDC well known config struct. +type WellKnownConfigurationJSON struct { + Issuer string `json:"issuer"` + AuthURL string `json:"authorization_endpoint"` + TokenURL string `json:"token_endpoint"` + RevocationEndpoint string `json:"revocation_endpoint"` + JWKSURL string `json:"jwks_uri"` + Algorithms []string `json:"id_token_signing_alg_values_supported"` + ResponseTypesSupported []string `json:"response_types_supported"` + ScopesSupported []string `json:"scopes_supported"` + ClaimsSupported []string `json:"claims_supported"` + BackChannelLogoutSupported bool `json:"backchannel_logout_supported"` + BackChannelLogoutSessionSupported bool `json:"backchannel_logout_session_supported"` + FrontChannelLogoutSupported bool `json:"frontchannel_logout_supported"` + FrontChannelLogoutSessionSupported bool `json:"frontchannel_logout_session_supported"` +} diff --git a/internal/middlewares/authelia_context.go b/internal/middlewares/authelia_context.go index 1ba162e4..fe77f08a 100644 --- a/internal/middlewares/authelia_context.go +++ b/internal/middlewares/authelia_context.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net" + "net/url" "strings" "github.com/asaskevich/govalidator" @@ -37,7 +38,7 @@ func NewAutheliaCtx(ctx *fasthttp.RequestCtx, configuration schema.Configuration } // AutheliaMiddleware is wrapping the RequestCtx into an AutheliaCtx providing Authelia related objects. -func AutheliaMiddleware(configuration schema.Configuration, providers Providers) func(next RequestHandler) fasthttp.RequestHandler { +func AutheliaMiddleware(configuration schema.Configuration, providers Providers) RequestHandlerBridge { return func(next RequestHandler) fasthttp.RequestHandler { return func(ctx *fasthttp.RequestCtx) { autheliaCtx, err := NewAutheliaCtx(ctx, configuration, providers) @@ -87,6 +88,11 @@ func (c *AutheliaCtx) ReplyForbidden() { c.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusForbidden), fasthttp.StatusForbidden) } +// ReplyBadRequest response sent when bad request has been sent. +func (c *AutheliaCtx) ReplyBadRequest() { + c.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusBadRequest), fasthttp.StatusBadRequest) +} + // XForwardedProto return the content of the X-Forwarded-Proto header. func (c *AutheliaCtx) XForwardedProto() []byte { return c.RequestCtx.Request.Header.Peek(xForwardedProtoHeader) @@ -107,6 +113,24 @@ func (c *AutheliaCtx) XForwardedURI() []byte { return c.RequestCtx.Request.Header.Peek(xForwardedURIHeader) } +// ForwardedProtoHost gets the X-Forwarded-Proto and X-Forwarded-Host headers and forms them into a URL. +func (c AutheliaCtx) ForwardedProtoHost() (string, error) { + XForwardedProto := c.XForwardedProto() + + if XForwardedProto == nil { + return "", errMissingXForwardedProto + } + + XForwardedHost := c.XForwardedHost() + + if XForwardedHost == nil { + return "", errMissingXForwardedHost + } + + return fmt.Sprintf("%s://%s", XForwardedProto, + XForwardedHost), nil +} + // XOriginalURL return the content of the X-Original-URL header. func (c *AutheliaCtx) XOriginalURL() []byte { return c.RequestCtx.Request.Header.Peek(xOriginalURLHeader) @@ -181,3 +205,46 @@ func (c *AutheliaCtx) RemoteIP() net.IP { return c.RequestCtx.RemoteIP() } + +// GetOriginalURL extract the URL from the request headers (X-Original-URI or X-Forwarded-* headers). +func (c *AutheliaCtx) GetOriginalURL() (*url.URL, error) { + originalURL := c.XOriginalURL() + if originalURL != nil { + parsedURL, err := url.ParseRequestURI(string(originalURL)) + if err != nil { + return nil, fmt.Errorf("Unable to parse URL extracted from X-Original-URL header: %v", err) + } + + c.Logger.Trace("Using X-Original-URL header content as targeted site URL") + + return parsedURL, nil + } + + forwardedProto := c.XForwardedProto() + forwardedHost := c.XForwardedHost() + forwardedURI := c.XForwardedURI() + + if forwardedProto == nil { + return nil, errMissingXForwardedProto + } + + if forwardedHost == nil { + return nil, errMissingXForwardedHost + } + + var requestURI string + + scheme := append(forwardedProto, protoHostSeparator...) + requestURI = string(append(scheme, + append(forwardedHost, forwardedURI...)...)) + + parsedURL, err := url.ParseRequestURI(requestURI) + if err != nil { + return nil, fmt.Errorf("Unable to parse URL %s: %v", requestURI, err) + } + + c.Logger.Tracef("Using X-Fowarded-Proto, X-Forwarded-Host and X-Forwarded-URI headers " + + "to construct targeted site URL") + + return parsedURL, nil +} diff --git a/internal/middlewares/authelia_context_test.go b/internal/middlewares/authelia_context_test.go index 9d6ee073..e73e1cc8 100644 --- a/internal/middlewares/authelia_context_test.go +++ b/internal/middlewares/authelia_context_test.go @@ -1,6 +1,7 @@ package middlewares_test import ( + "net/url" "testing" "github.com/golang/mock/gomock" @@ -33,3 +34,39 @@ func TestShouldCallNextWithAutheliaCtx(t *testing.T) { assert.True(t, nextCalled) } + +// Test getOriginalURL. +func TestShouldGetOriginalURLFromOriginalURLHeader(t *testing.T) { + mock := mocks.NewMockAutheliaCtx(t) + defer mock.Close() + + mock.Ctx.Request.Header.Set("X-Original-URL", "https://home.example.com") + originalURL, err := mock.Ctx.GetOriginalURL() + assert.NoError(t, err) + + expectedURL, err := url.ParseRequestURI("https://home.example.com") + assert.NoError(t, err) + assert.Equal(t, expectedURL, originalURL) +} + +func TestShouldGetOriginalURLFromForwardedHeadersWithoutURI(t *testing.T) { + mock := mocks.NewMockAutheliaCtx(t) + defer mock.Close() + mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https") + mock.Ctx.Request.Header.Set("X-Forwarded-Host", "home.example.com") + originalURL, err := mock.Ctx.GetOriginalURL() + assert.NoError(t, err) + + expectedURL, err := url.ParseRequestURI("https://home.example.com") + assert.NoError(t, err) + assert.Equal(t, expectedURL, originalURL) +} + +func TestShouldGetOriginalURLFromForwardedHeadersWithURI(t *testing.T) { + mock := mocks.NewMockAutheliaCtx(t) + defer mock.Close() + mock.Ctx.Request.Header.Set("X-Original-URL", "htt-ps//home?-.example.com") + _, err := mock.Ctx.GetOriginalURL() + assert.Error(t, err) + assert.Equal(t, "Unable to parse URL extracted from X-Original-URL header: parse \"htt-ps//home?-.example.com\": invalid URI for request", err.Error()) +} diff --git a/internal/middlewares/const.go b/internal/middlewares/const.go index e15cee80..858c9b70 100644 --- a/internal/middlewares/const.go +++ b/internal/middlewares/const.go @@ -16,3 +16,5 @@ var okMessageBytes = []byte("{\"status\":\"OK\"}") const operationFailedMessage = "Operation failed" const identityVerificationTokenAlreadyUsedMessage = "The identity verification token has already been used" const identityVerificationTokenHasExpiredMessage = "The identity verification token has expired" + +var protoHostSeparator = []byte("://") diff --git a/internal/middlewares/http_to_authelia_handler_adaptor.go b/internal/middlewares/http_to_authelia_handler_adaptor.go new file mode 100644 index 00000000..281e75a4 --- /dev/null +++ b/internal/middlewares/http_to_authelia_handler_adaptor.go @@ -0,0 +1,125 @@ +package middlewares + +import ( + "io" + "net/http" + "net/url" + + "github.com/valyala/fasthttp" +) + +// AutheliaHandlerFunc is used with the NewHTTPToAutheliaHandlerAdaptor to encapsulate a func. +type AutheliaHandlerFunc func(ctx *AutheliaCtx, rw http.ResponseWriter, r *http.Request) + +type netHTTPBody struct { + b []byte +} + +// Read reads the body. +func (r *netHTTPBody) Read(p []byte) (int, error) { + if len(r.b) == 0 { + return 0, io.EOF + } + + n := copy(p, r.b) + r.b = r.b[n:] + + return n, nil +} + +// Close closes the body. +func (r *netHTTPBody) Close() error { + r.b = r.b[:0] + return nil +} + +type netHTTPResponseWriter struct { + statusCode int + h http.Header + body []byte +} + +// StatusCode returns the status code. +func (w *netHTTPResponseWriter) StatusCode() int { + if w.statusCode == 0 { + return http.StatusOK + } + + return w.statusCode +} + +// Header returns the http.Header. +func (w *netHTTPResponseWriter) Header() http.Header { + if w.h == nil { + w.h = make(http.Header) + } + + return w.h +} + +// WriteHeader needs to be documented TODO: document it. +func (w *netHTTPResponseWriter) WriteHeader(statusCode int) { + w.statusCode = statusCode +} + +// Write writes to the body. +func (w *netHTTPResponseWriter) Write(p []byte) (int, error) { + w.body = append(w.body, p...) + return len(p), nil +} + +// NewHTTPToAutheliaHandlerAdaptor creates a new adaptor given the AutheliaHandlerFunc. +func NewHTTPToAutheliaHandlerAdaptor(h AutheliaHandlerFunc) RequestHandler { + return func(ctx *AutheliaCtx) { + var r http.Request + + body := ctx.PostBody() + r.Method = string(ctx.Method()) + r.Proto = "HTTP/1.1" + r.ProtoMajor = 1 + r.ProtoMinor = 1 + r.RequestURI = string(ctx.RequestURI()) + r.ContentLength = int64(len(body)) + r.Host = string(ctx.Host()) + r.RemoteAddr = ctx.RemoteAddr().String() + + hdr := make(http.Header) + ctx.Request.Header.VisitAll(func(k, v []byte) { + sk := string(k) + sv := string(v) + switch sk { + case "Transfer-Encoding": + r.TransferEncoding = append(r.TransferEncoding, sv) + default: + hdr.Set(sk, sv) + } + }) + + r.Header = hdr + r.Body = &netHTTPBody{body} + rURL, err := url.ParseRequestURI(r.RequestURI) + + if err != nil { + ctx.Logger.Errorf("cannot parse requestURI %q: %s", r.RequestURI, err) + ctx.RequestCtx.Error("Internal Server Error", fasthttp.StatusInternalServerError) + + return + } + + r.URL = rURL + + var w netHTTPResponseWriter + + h(ctx, &w, r.WithContext(ctx)) + + ctx.SetStatusCode(w.StatusCode()) + + for k, vv := range w.Header() { + for _, v := range vv { + ctx.Response.Header.Set(k, v) + } + } + + ctx.Write(w.body) //nolint:errcheck + } +} diff --git a/internal/middlewares/identity_verification.go b/internal/middlewares/identity_verification.go index 7b967679..aeb6ec75 100644 --- a/internal/middlewares/identity_verification.go +++ b/internal/middlewares/identity_verification.go @@ -6,7 +6,7 @@ import ( "fmt" "time" - jwt "github.com/dgrijalva/jwt-go" + "github.com/dgrijalva/jwt-go" "github.com/authelia/authelia/internal/templates" ) @@ -51,18 +51,13 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs) RequestHandle return } - if ctx.XForwardedProto() == nil { - ctx.Error(errMissingXForwardedProto, operationFailedMessage) + uri, err := ctx.ForwardedProtoHost() + if err != nil { + ctx.Error(err, operationFailedMessage) return } - if ctx.XForwardedHost() == nil { - ctx.Error(errMissingXForwardedHost, operationFailedMessage) - return - } - - link := fmt.Sprintf("%s://%s%s%s?token=%s", ctx.XForwardedProto(), - ctx.XForwardedHost(), ctx.Configuration.Server.Path, args.TargetEndpoint, ss) + link := fmt.Sprintf("%s%s%s?token=%s", uri, ctx.Configuration.Server.Path, args.TargetEndpoint, ss) bufHTML := new(bytes.Buffer) diff --git a/internal/middlewares/types.go b/internal/middlewares/types.go index 753af13a..9f956d59 100644 --- a/internal/middlewares/types.go +++ b/internal/middlewares/types.go @@ -1,7 +1,7 @@ package middlewares import ( - jwt "github.com/dgrijalva/jwt-go" + "github.com/dgrijalva/jwt-go" "github.com/sirupsen/logrus" "github.com/valyala/fasthttp" @@ -9,6 +9,7 @@ import ( "github.com/authelia/authelia/internal/authorization" "github.com/authelia/authelia/internal/configuration/schema" "github.com/authelia/authelia/internal/notification" + "github.com/authelia/authelia/internal/oidc" "github.com/authelia/authelia/internal/regulation" "github.com/authelia/authelia/internal/session" "github.com/authelia/authelia/internal/storage" @@ -31,6 +32,7 @@ type Providers struct { Authorizer *authorization.Authorizer SessionProvider *session.Provider Regulator *regulation.Regulator + OpenIDConnect oidc.OpenIDConnectProvider UserProvider authentication.UserProvider StorageProvider storage.Provider @@ -43,6 +45,9 @@ type RequestHandler = func(*AutheliaCtx) // Middleware represent an Authelia middleware. type Middleware = func(RequestHandler) RequestHandler +// RequestHandlerBridge bridge a AutheliaCtx handle to a RequestHandler handler. +type RequestHandlerBridge = func(RequestHandler) fasthttp.RequestHandler + // IdentityVerificationStartArgs represent the arguments used to customize the starting phase // of the identity verification process. type IdentityVerificationStartArgs struct { diff --git a/internal/oidc/client.go b/internal/oidc/client.go new file mode 100644 index 00000000..d49c20d2 --- /dev/null +++ b/internal/oidc/client.go @@ -0,0 +1,75 @@ +package oidc + +import ( + "github.com/ory/fosite" + + "github.com/authelia/authelia/internal/authentication" + "github.com/authelia/authelia/internal/authorization" +) + +// InternalClient represents the client internally. +type InternalClient struct { + ID string `json:"id"` + Description string `json:"-"` + Secret []byte `json:"client_secret,omitempty"` + RedirectURIs []string `json:"redirect_uris"` + GrantTypes []string `json:"grant_types"` + ResponseTypes []string `json:"response_types"` + Scopes []string `json:"scopes"` + Audience []string `json:"audience"` + Public bool `json:"public"` + Policy authorization.Level `json:"-"` +} + +// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient. +func (c InternalClient) IsAuthenticationLevelSufficient(level authentication.Level) bool { + return authorization.IsAuthLevelSufficient(level, c.Policy) +} + +// GetID returns the ID. +func (c InternalClient) GetID() string { + return c.ID +} + +// GetHashedSecret returns the Secret. +func (c InternalClient) GetHashedSecret() []byte { + return c.Secret +} + +// GetRedirectURIs returns the RedirectURIs. +func (c InternalClient) GetRedirectURIs() []string { + return c.RedirectURIs +} + +// GetGrantTypes returns the GrantTypes. +func (c InternalClient) GetGrantTypes() fosite.Arguments { + if len(c.GrantTypes) == 0 { + return fosite.Arguments{"authorization_code"} + } + + return c.GrantTypes +} + +// GetResponseTypes returns the ResponseTypes. +func (c InternalClient) GetResponseTypes() fosite.Arguments { + if len(c.ResponseTypes) == 0 { + return fosite.Arguments{"code"} + } + + return c.ResponseTypes +} + +// GetScopes returns the Scopes. +func (c InternalClient) GetScopes() fosite.Arguments { + return c.Scopes +} + +// IsPublic returns the value of the Public property. +func (c InternalClient) IsPublic() bool { + return c.Public +} + +// GetAudience returns the Audience. +func (c InternalClient) GetAudience() fosite.Arguments { + return c.Audience +} diff --git a/internal/oidc/client_test.go b/internal/oidc/client_test.go new file mode 100644 index 00000000..2ecd75ae --- /dev/null +++ b/internal/oidc/client_test.go @@ -0,0 +1,34 @@ +package oidc + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/authelia/authelia/internal/authentication" + "github.com/authelia/authelia/internal/authorization" +) + +func TestIsAuthenticationLevelSufficient(t *testing.T) { + c := InternalClient{} + + c.Policy = authorization.Bypass + assert.True(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated)) + assert.True(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor)) + assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor)) + + c.Policy = authorization.OneFactor + assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated)) + assert.True(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor)) + assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor)) + + c.Policy = authorization.TwoFactor + assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated)) + assert.False(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor)) + assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor)) + + c.Policy = authorization.Denied + assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated)) + assert.False(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor)) + assert.False(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor)) +} diff --git a/internal/oidc/errors.go b/internal/oidc/errors.go new file mode 100644 index 00000000..239b3e54 --- /dev/null +++ b/internal/oidc/errors.go @@ -0,0 +1,5 @@ +package oidc + +import "errors" + +var errPasswordsDoNotMatch = errors.New("the passwords don't match") diff --git a/internal/oidc/hasher.go b/internal/oidc/hasher.go new file mode 100644 index 00000000..29267080 --- /dev/null +++ b/internal/oidc/hasher.go @@ -0,0 +1,24 @@ +package oidc + +import ( + "context" + "crypto/subtle" +) + +// AutheliaHasher implements the fosite.Hasher interface without an actual hashing algo. +type AutheliaHasher struct { +} + +// Compare compares the hash with the data and returns an error if they don't match. +func (h AutheliaHasher) Compare(ctx context.Context, hash, data []byte) (err error) { + if subtle.ConstantTimeCompare(hash, data) == 0 { + return errPasswordsDoNotMatch + } + + return nil +} + +// Hash creates a new hash from data. +func (h AutheliaHasher) Hash(ctx context.Context, data []byte) (hash []byte, err error) { + return data, nil +} diff --git a/internal/oidc/hasher_test.go b/internal/oidc/hasher_test.go new file mode 100644 index 00000000..bc93a235 --- /dev/null +++ b/internal/oidc/hasher_test.go @@ -0,0 +1,47 @@ +package oidc + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestShouldNotRaiseErrorOnEqualPasswordsPlainText(t *testing.T) { + hasher := AutheliaHasher{} + + a := []byte("abc") + b := []byte("abc") + + ctx := context.Background() + + err := hasher.Compare(ctx, a, b) + + assert.NoError(t, err) +} + +func TestShouldRaiseErrorOnNonEqualPasswordsPlainText(t *testing.T) { + hasher := AutheliaHasher{} + + a := []byte("abc") + b := []byte("abcd") + + ctx := context.Background() + + err := hasher.Compare(ctx, a, b) + + assert.Equal(t, errPasswordsDoNotMatch, err) +} + +func TestShouldHashPassword(t *testing.T) { + hasher := AutheliaHasher{} + + data := []byte("abc") + + ctx := context.Background() + + hash, err := hasher.Hash(ctx, data) + + assert.NoError(t, err) + assert.Equal(t, data, hash) +} diff --git a/internal/oidc/provider.go b/internal/oidc/provider.go new file mode 100644 index 00000000..cf9ab8a7 --- /dev/null +++ b/internal/oidc/provider.go @@ -0,0 +1,108 @@ +package oidc + +import ( + "crypto/rsa" + "fmt" + + "github.com/ory/fosite" + "github.com/ory/fosite/compose" + "github.com/ory/fosite/token/jwt" + "gopkg.in/square/go-jose.v2" + + "github.com/authelia/authelia/internal/configuration/schema" + "github.com/authelia/authelia/internal/utils" +) + +// OpenIDConnectProvider for OpenID Connect. +type OpenIDConnectProvider struct { + privateKeys map[string]*rsa.PrivateKey + + Fosite fosite.OAuth2Provider + Store *OpenIDConnectStore +} + +// NewOpenIDConnectProvider new-ups a OpenIDConnectProvider. +func NewOpenIDConnectProvider(configuration *schema.OpenIDConnectConfiguration) (provider OpenIDConnectProvider, err error) { + provider = OpenIDConnectProvider{ + Fosite: nil, + } + + if configuration == nil { + return provider, nil + } + + provider.Store = NewOpenIDConnectStore(configuration) + + composeConfiguration := new(compose.Config) + + key, err := utils.ParseRsaPrivateKeyFromPemStr(configuration.IssuerPrivateKey) + if err != nil { + return provider, fmt.Errorf("unable to parse the private key of the OpenID issuer: %w", err) + } + + provider.privateKeys = make(map[string]*rsa.PrivateKey) + provider.privateKeys["main-key"] = key + + // TODO: Consider implementing RS512 as well. + jwtStrategy := &jwt.RS256JWTStrategy{PrivateKey: key} + + strategy := &compose.CommonStrategy{ + CoreStrategy: compose.NewOAuth2HMACStrategy( + composeConfiguration, + []byte(utils.HashSHA256FromString(configuration.HMACSecret)), + nil, + ), + OpenIDConnectTokenStrategy: compose.NewOpenIDConnectStrategy( + composeConfiguration, + provider.privateKeys["main-key"], + ), + JWTStrategy: jwtStrategy, + } + + provider.Fosite = compose.Compose( + composeConfiguration, + provider.Store, + strategy, + AutheliaHasher{}, + + /* + These are the OAuth2 and OpenIDConnect factories. Order is important (the OAuth2 factories at the top must + be before the OpenIDConnect factories) and taken directly from fosite.compose.ComposeAllEnabled. The + commented factories are not enabled as we don't yet use them but are still here for reference purposes. + */ + compose.OAuth2AuthorizeExplicitFactory, + compose.OAuth2AuthorizeImplicitFactory, + compose.OAuth2ClientCredentialsGrantFactory, + compose.OAuth2RefreshTokenGrantFactory, + compose.OAuth2ResourceOwnerPasswordCredentialsFactory, + // compose.RFC7523AssertionGrantFactory, + + compose.OpenIDConnectExplicitFactory, + compose.OpenIDConnectImplicitFactory, + compose.OpenIDConnectHybridFactory, + compose.OpenIDConnectRefreshFactory, + + compose.OAuth2TokenIntrospectionFactory, + compose.OAuth2TokenRevocationFactory, + + // compose.OAuth2PKCEFactory, + ) + + return provider, nil +} + +// GetKeySet returns the jose.JSONWebKeySet for the OpenIDConnectProvider. +func (p OpenIDConnectProvider) GetKeySet() (webKeySet jose.JSONWebKeySet) { + for keyID, key := range p.privateKeys { + webKey := jose.JSONWebKey{ + Key: &key.PublicKey, + KeyID: keyID, + Algorithm: "RS256", + Use: "sig", + } + + webKeySet.Keys = append(webKeySet.Keys, webKey) + } + + return webKeySet +} diff --git a/internal/oidc/provider_test.go b/internal/oidc/provider_test.go new file mode 100644 index 00000000..9eacfad2 --- /dev/null +++ b/internal/oidc/provider_test.go @@ -0,0 +1,40 @@ +package oidc + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/authelia/authelia/internal/configuration/schema" +) + +var exampleIssuerPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAvcMVMB2vEbqI6PlSNJ4HmUyMxBDJ5iY7FS+zDDAHOZBg9S3S\nKcAn1CZcnyL0VvJ7wcdhR6oTnOwR94eKvzUyJZ+GL2hTMm27dubEYsNdhoCl6N3X\nyEEohNfoxiiCYraVauX8X3M9jFzbEz9+pacaDbHB2syaJ1qFmMNR+HSu2jPzOo7M\nlqKIOgUzA0741MaYNt47AEVg4XU5ORLdolbAkItmYg1QbyFndg9H5IvwKkYaXTGE\nlgDBcPUC0yVjAC15Mguquq+jZeQay+6PSbHTD8PQMOkLjyChI2xEhVNbdCXe676R\ncMW2R/gjrcK23zmtmTWRfdC1iZLSlHO+bJj9vQIDAQABAoIBAEZvkP/JJOCJwqPn\nV3IcbmmilmV4bdi1vByDFgyiDyx4wOSA24+PubjvfFW9XcCgRPuKjDtTj/AhWBHv\nB7stfa2lZuNV7/u562mZArA+IAr62Zp0LdIxDV8x3T8gbjVB3HhPYbv0RJZDKTYd\nzV6jhfIrVu9mHpoY6ZnodhapCPYIyk/d49KBIHZuAc25CUjMXgTeaVtf0c996036\nUxW6ef33wAOJAvW0RCvbXAJfmBeEq2qQlkjTIlpYx71fhZWexHifi8Ouv3Zonc+1\n/P2Adq5uzYVBT92f9RKHg9QxxNzVrLjSMaxyvUtWQCAQfW0tFIRdqBGsHYsQrFtI\nF4yzv8ECgYEA7ntpyN9HD9Z9lYQzPCR73sFCLM+ID99aVij0wHuxK97bkSyyvkLd\n7MyTaym3lg1UEqWNWBCLvFULZx7F0Ah6qCzD4ymm3Bj/ADpWWPgljBI0AFml+HHs\nhcATmXUrj5QbLyhiP2gmJjajp1o/rgATx6ED66seSynD6JOH8wUhhZUCgYEAy7OA\n06PF8GfseNsTqlDjNF0K7lOqd21S0prdwrsJLiVzUlfMM25MLE0XLDUutCnRheeh\nIlcuDoBsVTxz6rkvFGD74N+pgXlN4CicsBq5ofK060PbqCQhSII3fmHobrZ9Cr75\nHmBjAxHx998SKaAAGbBbcYGUAp521i1pH5CEPYkCgYEAkUd1Zf0+2RMdZhwm6hh/\nrW+l1I6IoMK70YkZsLipccRNld7Y9LbfYwYtODcts6di9AkOVfueZJiaXbONZfIE\nZrb+jkAteh9wGL9xIrnohbABJcV3Kiaco84jInUSmGDtPokncOENfHIEuEpuSJ2b\nbx1TuhmAVuGWivR0+ULC7RECgYEAgS0cDRpWc9Xzh9Cl7+PLsXEvdWNpPsL9OsEq\n0Ep7z9+/+f/jZtoTRCS/BTHUpDvAuwHglT5j3p5iFMt5VuiIiovWLwynGYwrbnNS\nqfrIrYKUaH1n1oDS+oBZYLQGCe9/7EifAjxtjYzbvSyg//SPG7tSwfBCREbpZXj2\nqSWkNsECgYA/mCDzCTlrrWPuiepo6kTmN+4TnFA+hJI6NccDVQ+jvbqEdoJ4SW4L\nzqfZSZRFJMNpSgIqkQNRPJqMP0jQ5KRtJrjMWBnYxktwKz9fDg2R2MxdFgMF2LH2\nHEMMhFHlv8NDjVOXh1KwRoltNGVWYsSrD9wKU9GhRCEfmNCGrvBcEg==\n-----END RSA PRIVATE KEY-----" + +func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing.T) { + provider, err := NewOpenIDConnectProvider(nil) + + assert.NoError(t, err) + assert.Nil(t, provider.Fosite) + assert.Nil(t, provider.Store) +} + +func TestOpenIDConnectProvider_NewOpenIDConnectProvider_BadIssuerKey(t *testing.T) { + _, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ + IssuerPrivateKey: "BAD KEY", + }) + + assert.Error(t, err, "abc") +} + +func TestOpenIDConnectProvider_GetKeySet(t *testing.T) { + p, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ + IssuerPrivateKey: exampleIssuerPrivateKey, + }) + + assert.NoError(t, err) + + assert.Len(t, p.GetKeySet().Keys, 1) + assert.Equal(t, "RS256", p.GetKeySet().Keys[0].Algorithm) + assert.Equal(t, "sig", p.GetKeySet().Keys[0].Use) + assert.Equal(t, "main-key", p.GetKeySet().Keys[0].KeyID) +} diff --git a/internal/oidc/store.go b/internal/oidc/store.go new file mode 100644 index 00000000..0707ec78 --- /dev/null +++ b/internal/oidc/store.go @@ -0,0 +1,219 @@ +package oidc + +import ( + "context" + "time" + + "github.com/ory/fosite" + "github.com/ory/fosite/storage" + "gopkg.in/square/go-jose.v2" + + "github.com/authelia/authelia/internal/authorization" + "github.com/authelia/authelia/internal/configuration/schema" + "github.com/authelia/authelia/internal/logging" +) + +// NewOpenIDConnectStore returns a new OpenIDConnectStore using the provided schema.OpenIDConnectConfiguration. +func NewOpenIDConnectStore(configuration *schema.OpenIDConnectConfiguration) (store *OpenIDConnectStore) { + store = &OpenIDConnectStore{} + + store.clients = make(map[string]*InternalClient) + + for _, clientConf := range configuration.Clients { + policy := authorization.PolicyToLevel(clientConf.Policy) + logging.Logger().Debugf("Registering client %s with policy %s (%v)", clientConf.ID, clientConf.Policy, policy) + + client := &InternalClient{ + ID: clientConf.ID, + Description: clientConf.Description, + Policy: authorization.PolicyToLevel(clientConf.Policy), + Secret: []byte(clientConf.Secret), + RedirectURIs: clientConf.RedirectURIs, + GrantTypes: clientConf.GrantTypes, + ResponseTypes: clientConf.ResponseTypes, + Scopes: clientConf.Scopes, + } + + store.clients[client.ID] = client + } + + store.memory = &storage.MemoryStore{ + IDSessions: make(map[string]fosite.Requester), + Users: map[string]storage.MemoryUserRelation{}, + AuthorizeCodes: map[string]storage.StoreAuthorizeCode{}, + AccessTokens: map[string]fosite.Requester{}, + RefreshTokens: map[string]storage.StoreRefreshToken{}, + PKCES: map[string]fosite.Requester{}, + AccessTokenRequestIDs: map[string]string{}, + RefreshTokenRequestIDs: map[string]string{}, + } + + return store +} + +// OpenIDConnectStore is Authelia's internal representation of the fosite.Storage interface. +// +// Currently it is mostly just implementing a decorator pattern other then GetInternalClient. +// The long term plan is to have these methods interact with the Authelia storage and +// session providers where applicable. +type OpenIDConnectStore struct { + clients map[string]*InternalClient + memory *storage.MemoryStore +} + +// GetClientPolicy retrieves the policy from the client with the matching provided id. +func (s OpenIDConnectStore) GetClientPolicy(id string) (level authorization.Level) { + client, err := s.GetInternalClient(id) + if err != nil { + return authorization.TwoFactor + } + + return client.Policy +} + +// GetInternalClient returns a fosite.Client asserted as an InternalClient matching the provided id. +func (s OpenIDConnectStore) GetInternalClient(id string) (client *InternalClient, err error) { + client, ok := s.clients[id] + if !ok { + return nil, fosite.ErrNotFound + } + + return client, nil +} + +// IsValidClientID returns true if the provided id exists in the OpenIDConnectProvider.Clients map. +func (s OpenIDConnectStore) IsValidClientID(id string) (valid bool) { + _, err := s.GetInternalClient(id) + + return err == nil +} + +// CreateOpenIDConnectSession decorates fosite's storage.MemoryStore CreateOpenIDConnectSession method. +func (s *OpenIDConnectStore) CreateOpenIDConnectSession(ctx context.Context, authorizeCode string, requester fosite.Requester) error { + return s.memory.CreateOpenIDConnectSession(ctx, authorizeCode, requester) +} + +// GetOpenIDConnectSession decorates fosite's storage.MemoryStore GetOpenIDConnectSession method. +func (s *OpenIDConnectStore) GetOpenIDConnectSession(ctx context.Context, authorizeCode string, requester fosite.Requester) (fosite.Requester, error) { + return s.memory.GetOpenIDConnectSession(ctx, authorizeCode, requester) +} + +// DeleteOpenIDConnectSession decorates fosite's storage.MemoryStore DeleteOpenIDConnectSession method. +func (s *OpenIDConnectStore) DeleteOpenIDConnectSession(ctx context.Context, authorizeCode string) error { + return s.memory.DeleteOpenIDConnectSession(ctx, authorizeCode) +} + +// GetClient decorates fosite's storage.MemoryStore GetClient method. +func (s *OpenIDConnectStore) GetClient(_ context.Context, id string) (fosite.Client, error) { + return s.GetInternalClient(id) +} + +// ClientAssertionJWTValid decorates fosite's storage.MemoryStore ClientAssertionJWTValid method. +func (s *OpenIDConnectStore) ClientAssertionJWTValid(ctx context.Context, jti string) error { + return s.memory.ClientAssertionJWTValid(ctx, jti) +} + +// SetClientAssertionJWT decorates fosite's storage.MemoryStore SetClientAssertionJWT method. +func (s *OpenIDConnectStore) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) error { + return s.memory.SetClientAssertionJWT(ctx, jti, exp) +} + +// CreateAuthorizeCodeSession decorates fosite's storage.MemoryStore CreateAuthorizeCodeSession method. +func (s *OpenIDConnectStore) CreateAuthorizeCodeSession(ctx context.Context, code string, req fosite.Requester) error { + return s.memory.CreateAuthorizeCodeSession(ctx, code, req) +} + +// GetAuthorizeCodeSession decorates fosite's storage.MemoryStore GetAuthorizeCodeSession method. +func (s *OpenIDConnectStore) GetAuthorizeCodeSession(ctx context.Context, code string, session fosite.Session) (fosite.Requester, error) { + return s.memory.GetAuthorizeCodeSession(ctx, code, session) +} + +// InvalidateAuthorizeCodeSession decorates fosite's storage.MemoryStore InvalidateAuthorizeCodeSession method. +func (s *OpenIDConnectStore) InvalidateAuthorizeCodeSession(ctx context.Context, code string) error { + return s.memory.InvalidateAuthorizeCodeSession(ctx, code) +} + +// CreatePKCERequestSession decorates fosite's storage.MemoryStore CreatePKCERequestSession method. +func (s *OpenIDConnectStore) CreatePKCERequestSession(ctx context.Context, code string, req fosite.Requester) error { + return s.memory.CreatePKCERequestSession(ctx, code, req) +} + +// GetPKCERequestSession decorates fosite's storage.MemoryStore GetPKCERequestSession method. +func (s *OpenIDConnectStore) GetPKCERequestSession(ctx context.Context, code string, session fosite.Session) (fosite.Requester, error) { + return s.memory.GetPKCERequestSession(ctx, code, session) +} + +// DeletePKCERequestSession decorates fosite's storage.MemoryStore DeletePKCERequestSession method. +func (s *OpenIDConnectStore) DeletePKCERequestSession(ctx context.Context, code string) error { + return s.memory.DeletePKCERequestSession(ctx, code) +} + +// CreateAccessTokenSession decorates fosite's storage.MemoryStore CreateAccessTokenSession method. +func (s *OpenIDConnectStore) CreateAccessTokenSession(ctx context.Context, signature string, req fosite.Requester) error { + return s.memory.CreateAccessTokenSession(ctx, signature, req) +} + +// GetAccessTokenSession decorates fosite's storage.MemoryStore GetAccessTokenSession method. +func (s *OpenIDConnectStore) GetAccessTokenSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error) { + return s.memory.GetAccessTokenSession(ctx, signature, session) +} + +// DeleteAccessTokenSession decorates fosite's storage.MemoryStore DeleteAccessTokenSession method. +func (s *OpenIDConnectStore) DeleteAccessTokenSession(ctx context.Context, signature string) error { + return s.memory.DeleteAccessTokenSession(ctx, signature) +} + +// CreateRefreshTokenSession decorates fosite's storage.MemoryStore CreateRefreshTokenSession method. +func (s *OpenIDConnectStore) CreateRefreshTokenSession(ctx context.Context, signature string, req fosite.Requester) error { + return s.memory.CreateRefreshTokenSession(ctx, signature, req) +} + +// GetRefreshTokenSession decorates fosite's storage.MemoryStore GetRefreshTokenSession method. +func (s *OpenIDConnectStore) GetRefreshTokenSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error) { + return s.memory.GetRefreshTokenSession(ctx, signature, session) +} + +// DeleteRefreshTokenSession decorates fosite's storage.MemoryStore DeleteRefreshTokenSession method. +func (s *OpenIDConnectStore) DeleteRefreshTokenSession(ctx context.Context, signature string) error { + return s.memory.DeleteRefreshTokenSession(ctx, signature) +} + +// Authenticate decorates fosite's storage.MemoryStore Authenticate method. +func (s *OpenIDConnectStore) Authenticate(ctx context.Context, name string, secret string) error { + return s.memory.Authenticate(ctx, name, secret) +} + +// RevokeRefreshToken decorates fosite's storage.MemoryStore RevokeRefreshToken method. +func (s *OpenIDConnectStore) RevokeRefreshToken(ctx context.Context, requestID string) error { + return s.memory.RevokeRefreshToken(ctx, requestID) +} + +// RevokeAccessToken decorates fosite's storage.MemoryStore RevokeAccessToken method. +func (s *OpenIDConnectStore) RevokeAccessToken(ctx context.Context, requestID string) error { + return s.memory.RevokeAccessToken(ctx, requestID) +} + +// GetPublicKey decorates fosite's storage.MemoryStore GetPublicKey method. +func (s *OpenIDConnectStore) GetPublicKey(ctx context.Context, issuer string, subject string, keyID string) (*jose.JSONWebKey, error) { + return s.memory.GetPublicKey(ctx, issuer, subject, keyID) +} + +// GetPublicKeys decorates fosite's storage.MemoryStore GetPublicKeys method. +func (s *OpenIDConnectStore) GetPublicKeys(ctx context.Context, issuer string, subject string) (*jose.JSONWebKeySet, error) { + return s.memory.GetPublicKeys(ctx, issuer, subject) +} + +// GetPublicKeyScopes decorates fosite's storage.MemoryStore GetPublicKeyScopes method. +func (s *OpenIDConnectStore) GetPublicKeyScopes(ctx context.Context, issuer string, subject string, keyID string) ([]string, error) { + return s.memory.GetPublicKeyScopes(ctx, issuer, subject, keyID) +} + +// IsJWTUsed decorates fosite's storage.MemoryStore IsJWTUsed method. +func (s *OpenIDConnectStore) IsJWTUsed(ctx context.Context, jti string) (bool, error) { + return s.memory.IsJWTUsed(ctx, jti) +} + +// MarkJWTUsedForTime decorates fosite's storage.MemoryStore MarkJWTUsedForTime method. +func (s *OpenIDConnectStore) MarkJWTUsedForTime(ctx context.Context, jti string, exp time.Time) error { + return s.memory.MarkJWTUsedForTime(ctx, jti, exp) +} diff --git a/internal/oidc/store_test.go b/internal/oidc/store_test.go new file mode 100644 index 00000000..62bab489 --- /dev/null +++ b/internal/oidc/store_test.go @@ -0,0 +1,132 @@ +package oidc + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/authelia/authelia/internal/authorization" + "github.com/authelia/authelia/internal/configuration/schema" +) + +func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) { + s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{ + IssuerPrivateKey: exampleIssuerPrivateKey, + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "myclient", + Description: "myclient desc", + Policy: "one_factor", + Scopes: []string{"openid", "profile"}, + Secret: "mysecret", + }, + { + ID: "myotherclient", + Description: "myclient desc", + Policy: "two_factor", + Scopes: []string{"openid", "profile"}, + Secret: "mysecret", + }, + }, + }) + + policyOne := s.GetClientPolicy("myclient") + assert.Equal(t, authorization.OneFactor, policyOne) + + policyTwo := s.GetClientPolicy("myotherclient") + assert.Equal(t, authorization.TwoFactor, policyTwo) + + policyInvalid := s.GetClientPolicy("invalidclient") + assert.Equal(t, authorization.TwoFactor, policyInvalid) +} + +func TestOpenIDConnectStore_GetInternalClient(t *testing.T) { + s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{ + IssuerPrivateKey: exampleIssuerPrivateKey, + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "myclient", + Description: "myclient desc", + Policy: "one_factor", + Scopes: []string{"openid", "profile"}, + Secret: "mysecret", + }, + }, + }) + + client, err := s.GetClient(context.Background(), "myinvalidclient") + assert.EqualError(t, err, "not_found") + assert.Nil(t, client) + + client, err = s.GetClient(context.Background(), "myclient") + require.NoError(t, err) + require.NotNil(t, client) + assert.Equal(t, "myclient", client.GetID()) +} + +func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) { + c1 := schema.OpenIDConnectClientConfiguration{ + ID: "myclient", + Description: "myclient desc", + Policy: "one_factor", + Scopes: []string{"openid", "profile"}, + Secret: "mysecret", + } + s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{ + IssuerPrivateKey: exampleIssuerPrivateKey, + Clients: []schema.OpenIDConnectClientConfiguration{c1}, + }) + + client, err := s.GetInternalClient(c1.ID) + require.NoError(t, err) + require.NotNil(t, client) + assert.Equal(t, client.ID, c1.ID) + assert.Equal(t, client.Description, c1.Description) + assert.Equal(t, client.Scopes, c1.Scopes) + assert.Equal(t, client.GrantTypes, c1.GrantTypes) + assert.Equal(t, client.ResponseTypes, c1.ResponseTypes) + assert.Equal(t, client.RedirectURIs, c1.RedirectURIs) + assert.Equal(t, client.Policy, authorization.OneFactor) + assert.Equal(t, client.Secret, []byte(c1.Secret)) +} + +func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) { + c1 := schema.OpenIDConnectClientConfiguration{ + ID: "myclient", + Description: "myclient desc", + Policy: "one_factor", + Scopes: []string{"openid", "profile"}, + Secret: "mysecret", + } + s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{ + IssuerPrivateKey: exampleIssuerPrivateKey, + Clients: []schema.OpenIDConnectClientConfiguration{c1}, + }) + + client, err := s.GetInternalClient("another-client") + assert.Nil(t, client) + assert.EqualError(t, err, "not_found") +} + +func TestOpenIDConnectStore_IsValidClientID(t *testing.T) { + s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{ + IssuerPrivateKey: exampleIssuerPrivateKey, + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "myclient", + Description: "myclient desc", + Policy: "one_factor", + Scopes: []string{"openid", "profile"}, + Secret: "mysecret", + }, + }, + }) + + validClient := s.IsValidClientID("myclient") + invalidClient := s.IsValidClientID("myinvalidclient") + + assert.True(t, validClient) + assert.False(t, invalidClient) +} diff --git a/internal/server/server.go b/internal/server/server.go index 60aff102..44557706 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -28,9 +28,7 @@ import ( //go:embed public_html var assets embed.FS -// StartServer start Authelia server with the given configuration and providers. -func StartServer(configuration schema.Configuration, providers middlewares.Providers) { - logger := logging.Logger() +func registerRoutes(configuration schema.Configuration, providers middlewares.Providers) fasthttp.RequestHandler { autheliaMiddleware := middlewares.AutheliaMiddleware(configuration, providers) rememberMe := strconv.FormatBool(configuration.Session.RememberMeDuration != "0") resetPassword := strconv.FormatBool(!configuration.AuthenticationBackend.DisableResetPassword) @@ -142,6 +140,19 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi handler = middlewares.StripPathMiddleware(handler) } + if providers.OpenIDConnect.Fosite != nil { + handlers.RegisterOIDC(r, autheliaMiddleware) + } + + return handler +} + +// StartServer start Authelia server with the given configuration and providers. +func StartServer(configuration schema.Configuration, providers middlewares.Providers) { + logger := logging.Logger() + + handler := registerRoutes(configuration, providers) + server := &fasthttp.Server{ ErrorHandler: autheliaErrorHandler, Handler: handler, @@ -157,6 +168,7 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi logger.Fatalf("Error initializing listener: %s", err) } + // TODO(clems4ever): move that piece to a more related location, probably in the configuration package. if configuration.AuthenticationBackend.File != nil && configuration.AuthenticationBackend.File.Password.Algorithm == "argon2id" && runtime.GOOS == "linux" { f, err := ioutil.ReadFile("/sys/fs/cgroup/memory/memory.limit_in_bytes") if err != nil { diff --git a/internal/session/provider.go b/internal/session/provider.go index a8544ae5..df9036fb 100644 --- a/internal/session/provider.go +++ b/internal/session/provider.go @@ -87,6 +87,7 @@ func (p *Provider) GetSession(ctx *fasthttp.RequestCtx) (UserSession, error) { // and save it in the store. if !ok { userSession := NewDefaultUserSession() + store.Set(userSessionStorerKey, userSession) return userSession, nil @@ -130,6 +131,7 @@ func (p *Provider) SaveSession(ctx *fasthttp.RequestCtx, userSession UserSession // RegenerateSession regenerate a session ID. func (p *Provider) RegenerateSession(ctx *fasthttp.RequestCtx) error { err := p.sessionHolder.Regenerate(ctx) + return err } diff --git a/internal/session/types.go b/internal/session/types.go index de2649f3..3ebe30da 100644 --- a/internal/session/types.go +++ b/internal/session/types.go @@ -8,6 +8,7 @@ import ( "github.com/tstranex/u2f" "github.com/authelia/authelia/internal/authentication" + "github.com/authelia/authelia/internal/authorization" ) // ProviderConfig is the configuration used to create the session provider. @@ -43,6 +44,9 @@ type UserSession struct { // This is used in second phase of a U2F authentication. U2FRegistration *U2FRegistration + // Represent an OIDC workflow session initiated by the client if not null. + OIDCWorkflowSession *OIDCWorkflowSession + // This boolean is set to true after identity verification and checked // while doing the query actually updating the password. PasswordResetUsername *string @@ -55,3 +59,15 @@ type Identity struct { Username string Email string } + +// OIDCWorkflowSession represent an OIDC workflow session. +type OIDCWorkflowSession struct { + ClientID string + RequestedScopes []string + GrantedScopes []string + RequestedAudience []string + GrantedAudience []string + TargetURI string + AuthURI string + RequiredAuthorizationLevel authorization.Level +} diff --git a/internal/suites/OIDC/configuration.yml b/internal/suites/OIDC/configuration.yml new file mode 100644 index 00000000..44569ce3 --- /dev/null +++ b/internal/suites/OIDC/configuration.yml @@ -0,0 +1,99 @@ +--- +port: 9091 +tls_cert: /config/ssl/cert.pem +tls_key: /config/ssl/key.pem + +log_level: debug + +jwt_secret: unsecure_secret + +authentication_backend: + file: + path: /config/users.yml + +session: + secret: unsecure_session_secret + domain: example.com + expiration: 3600 # 1 hour + inactivity: 300 # 5 minutes + remember_me_duration: 1y + # We use redis here to keep the users authenticated when Authelia restarts + # It eases development. + redis: + host: redis + port: 6379 + +storage: + local: + path: /config/db.sqlite + +access_control: + default_policy: deny + rules: + - domain: "home.example.com" + policy: bypass + - domain: "public.example.com" + policy: bypass + - domain: "admin.example.com" + policy: two_factor + - domain: "secure.example.com" + policy: two_factor + - domain: "singlefactor.example.com" + policy: one_factor + - domain: "oidc.example.com" + policy: two_factor + - domain: "oidc-public.example.com" + policy: bypass + +notifier: + smtp: + host: smtp + port: 1025 + sender: admin@example.com + disable_require_tls: true + +identity_providers: + oidc: + hmac_secret: IVPWBkAdJHje3uz7LtFTDU2pFUfh39Xm + issuer_private_key: | + -----BEGIN RSA PRIVATE KEY----- + MIIEogIBAAKCAQEAvOFmoEJFt1JkfdlwM3vJFg5rrY9d6LyyqezjZkBZDQ4qdEEU + dCrbW8ISFTtg9sfbrS3qingUzVP9VOfYPMC3r0ugjJXjhvJdBSaoLlzL3saeyrXk + frOOvkcWKzeOynqUNPhKy9dchmuLALFfd/Jy7Wzq0y7XxGeNidEmFjMAf9dwf6/+ + PjQjbG7zBFu/XSajITPHlDXPVDd0j2qw2wu5Z9iqn4LRXnAFnC438hZZKZU/+JxU + 2ezr6Sefiy8XTC2kDiq3cgLeEjSywlJOs+4TLjVS/3h75sh2Wk0xVaSwjPEjCOgm + a+2E3GJrGdQBiAjMSu101VBVwHUHaLDCn1T4NwIDAQABAoIBADWkupXnXI99Ogc4 + GxK0JF88Rz6qyhwQg5mZKthejCwWCt6roRiBF33O933KOHa+OljMAqHDCv1pzjgw + BIz0mvaRPw7OfylTajHNUdShDFHADVc7I6MMcgz+eYBarhY5jCAjKHMOPjv7DSZs + OdYCKLvfxC2oTyV714n9uZhyccDcvQpkgZuBDL0oxPom1GOI8TGhPjxvFOovEHWA + Q8q9XY4cUVNDikZmvpgeUkJHWYHYb+11vKeSupnYD03yJ3sDy+F6+m+3/XmzFbXb + 1p43ermHQsMfDlxPyulUUI0viSo2UhlMC/moAb9FusOv+dTl2lt0gGqzDJ9gg1z1 + XpHRnwkCgYEA5x48dyxd4lydtVYef9sBmbLJEYozsYyOwLcnrLSNaZxeCza1exyR + QIRogswoLDacxrYvO8FY6LtAEMkisv732M29zthBPm5wyoSZiM1X2YfQXKsmyh2h + x1/yCWv/BQjj68A8IAxToaXxSG4WAr/X00RGUkXgkgw122FxcmGuFyUCgYEA0TcR + dnt/oRMK4aCZHcBgTknzDfxKlJh4S0C9WjxKgr8IlW4LTeVSBuuqOObOQYImEhtw + TRTKZIViL0roDF79cioQSp1Tk5h6uy8wr6VyhWRnWfTz2/azoTHnmQ780rtAuEI/ + NvE6FiqwikJLjma1YJoRfr/bfmgMdxcYbJI1MSsCgYAEZ5Yda1IKu1siFpcUNrdM + F5UvaWPc0WHzGEqARxye06UTL6K7yuqVwTBAteVaGlxYiSZTTDcGkHMDHuIzaRqO + HjWs2IA90VsC8Q4ABnHTKnx1F6nwlin8I774IP/GN8ooNwyuS63YWdJEYBy5RrC1 + TQrODJjgD62DFdNUq7nmpQKBgFMJEzI+Q+KPJ0NztTG8t7x61y/W0Vb2yM+9Syn0 + QfJwlZyRR4VMHelHQZFB8dzIJgoLv9+n/8gztEtm5IB8dwUHst2aYaBz5UpDqYQd + Gz3cIrTuZpcH7DVvFCeIbknJLh+zk1lgFpjTqqvFMi27kANeQtFWnmwmKcRec0As + K1ZvAoGAV/3YB44/zIoB590+yhpx2HTmDPVHH+J+5O71Pi1D9W13ClBFLrE69wo+ + IQLIstBI5tGOGeuQNjXhDKJ1U30xppZXcnebrkA+oOo+6dy20zghFR2maAGXfWFU + pM4GsSnSTm0bXPebVouQFqhj7LqcQQzCqRDThmw/Lp1tJUmu40g= + -----END RSA PRIVATE KEY----- + clients: + - id: oidc-tester-app + secret: foobar + policy: two_factor + redirect_uris: + - https://oidc.example.com:8080/oauth2/callback + # This client is used for testing purpose. As of now, the app must be protected by ACLs + # otherwise it won't work properly. + - id: oidc-tester-app-public + secret: foobar + authorization_policy: one_factor + redirect_uris: + - https://oidc-public.example.com:8080/oauth2/callback +... diff --git a/internal/suites/OIDC/docker-compose.yml b/internal/suites/OIDC/docker-compose.yml new file mode 100644 index 00000000..2da124b7 --- /dev/null +++ b/internal/suites/OIDC/docker-compose.yml @@ -0,0 +1,10 @@ +--- +version: '3' +services: + authelia-backend: + volumes: + - './OIDC/configuration.yml:/config/configuration.yml:ro' + - './OIDC/users.yml:/config/users.yml' + - './OIDC/keypair/key.pem:/config/issuer.pem:ro' + - './common/ssl:/config/ssl:ro' +... diff --git a/internal/suites/OIDC/keypair/key.pem b/internal/suites/OIDC/keypair/key.pem new file mode 100644 index 00000000..c5df003c --- /dev/null +++ b/internal/suites/OIDC/keypair/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAvOFmoEJFt1JkfdlwM3vJFg5rrY9d6LyyqezjZkBZDQ4qdEEU +dCrbW8ISFTtg9sfbrS3qingUzVP9VOfYPMC3r0ugjJXjhvJdBSaoLlzL3saeyrXk +frOOvkcWKzeOynqUNPhKy9dchmuLALFfd/Jy7Wzq0y7XxGeNidEmFjMAf9dwf6/+ +PjQjbG7zBFu/XSajITPHlDXPVDd0j2qw2wu5Z9iqn4LRXnAFnC438hZZKZU/+JxU +2ezr6Sefiy8XTC2kDiq3cgLeEjSywlJOs+4TLjVS/3h75sh2Wk0xVaSwjPEjCOgm +a+2E3GJrGdQBiAjMSu101VBVwHUHaLDCn1T4NwIDAQABAoIBADWkupXnXI99Ogc4 +GxK0JF88Rz6qyhwQg5mZKthejCwWCt6roRiBF33O933KOHa+OljMAqHDCv1pzjgw +BIz0mvaRPw7OfylTajHNUdShDFHADVc7I6MMcgz+eYBarhY5jCAjKHMOPjv7DSZs +OdYCKLvfxC2oTyV714n9uZhyccDcvQpkgZuBDL0oxPom1GOI8TGhPjxvFOovEHWA +Q8q9XY4cUVNDikZmvpgeUkJHWYHYb+11vKeSupnYD03yJ3sDy+F6+m+3/XmzFbXb +1p43ermHQsMfDlxPyulUUI0viSo2UhlMC/moAb9FusOv+dTl2lt0gGqzDJ9gg1z1 +XpHRnwkCgYEA5x48dyxd4lydtVYef9sBmbLJEYozsYyOwLcnrLSNaZxeCza1exyR +QIRogswoLDacxrYvO8FY6LtAEMkisv732M29zthBPm5wyoSZiM1X2YfQXKsmyh2h +x1/yCWv/BQjj68A8IAxToaXxSG4WAr/X00RGUkXgkgw122FxcmGuFyUCgYEA0TcR +dnt/oRMK4aCZHcBgTknzDfxKlJh4S0C9WjxKgr8IlW4LTeVSBuuqOObOQYImEhtw +TRTKZIViL0roDF79cioQSp1Tk5h6uy8wr6VyhWRnWfTz2/azoTHnmQ780rtAuEI/ +NvE6FiqwikJLjma1YJoRfr/bfmgMdxcYbJI1MSsCgYAEZ5Yda1IKu1siFpcUNrdM +F5UvaWPc0WHzGEqARxye06UTL6K7yuqVwTBAteVaGlxYiSZTTDcGkHMDHuIzaRqO +HjWs2IA90VsC8Q4ABnHTKnx1F6nwlin8I774IP/GN8ooNwyuS63YWdJEYBy5RrC1 +TQrODJjgD62DFdNUq7nmpQKBgFMJEzI+Q+KPJ0NztTG8t7x61y/W0Vb2yM+9Syn0 +QfJwlZyRR4VMHelHQZFB8dzIJgoLv9+n/8gztEtm5IB8dwUHst2aYaBz5UpDqYQd +Gz3cIrTuZpcH7DVvFCeIbknJLh+zk1lgFpjTqqvFMi27kANeQtFWnmwmKcRec0As +K1ZvAoGAV/3YB44/zIoB590+yhpx2HTmDPVHH+J+5O71Pi1D9W13ClBFLrE69wo+ +IQLIstBI5tGOGeuQNjXhDKJ1U30xppZXcnebrkA+oOo+6dy20zghFR2maAGXfWFU +pM4GsSnSTm0bXPebVouQFqhj7LqcQQzCqRDThmw/Lp1tJUmu40g= +-----END RSA PRIVATE KEY----- diff --git a/internal/suites/OIDC/keypair/key.pub b/internal/suites/OIDC/keypair/key.pub new file mode 100644 index 00000000..b8e37ffa --- /dev/null +++ b/internal/suites/OIDC/keypair/key.pub @@ -0,0 +1,9 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvOFmoEJFt1JkfdlwM3vJ +Fg5rrY9d6LyyqezjZkBZDQ4qdEEUdCrbW8ISFTtg9sfbrS3qingUzVP9VOfYPMC3 +r0ugjJXjhvJdBSaoLlzL3saeyrXkfrOOvkcWKzeOynqUNPhKy9dchmuLALFfd/Jy +7Wzq0y7XxGeNidEmFjMAf9dwf6/+PjQjbG7zBFu/XSajITPHlDXPVDd0j2qw2wu5 +Z9iqn4LRXnAFnC438hZZKZU/+JxU2ezr6Sefiy8XTC2kDiq3cgLeEjSywlJOs+4T +LjVS/3h75sh2Wk0xVaSwjPEjCOgma+2E3GJrGdQBiAjMSu101VBVwHUHaLDCn1T4 +NwIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/internal/suites/OIDC/users.yml b/internal/suites/OIDC/users.yml new file mode 100644 index 00000000..a52978b2 --- /dev/null +++ b/internal/suites/OIDC/users.yml @@ -0,0 +1,35 @@ +--- +############################################################### +# Users Database # +############################################################### + +# This file can be used if you do not have an LDAP set up. + +# List of users +users: + john: + displayname: "John Doe" + password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length + email: john.doe@authelia.com + groups: + - admins + - dev + + harry: + displayname: "Harry Potter" + password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length + email: harry.potter@authelia.com + groups: [] + + bob: + displayname: "Bob Dylan" + password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length + email: bob.dylan@authelia.com + groups: + - dev + + james: + displayname: "James Dean" + password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length + email: james.dean@authelia.com +... diff --git a/internal/suites/OIDCTraefik/configuration.yml b/internal/suites/OIDCTraefik/configuration.yml new file mode 100644 index 00000000..7ad402bd --- /dev/null +++ b/internal/suites/OIDCTraefik/configuration.yml @@ -0,0 +1,101 @@ +--- +port: 9091 +tls_cert: /config/ssl/cert.pem +tls_key: /config/ssl/key.pem + +log_level: debug + +jwt_secret: unsecure_secret + +authentication_backend: + file: + path: /config/users.yml + +session: + secret: unsecure_session_secret + domain: example.com + expiration: 3600 # 1 hour + inactivity: 300 # 5 minutes + remember_me_duration: 1y + # We use redis here to keep the users authenticated when Authelia restarts + # It eases development. + redis: + host: redis + port: 6379 + +storage: + local: + path: /config/db.sqlite + +access_control: + default_policy: deny + rules: + - domain: "home.example.com" + policy: bypass + - domain: "public.example.com" + policy: bypass + - domain: "admin.example.com" + policy: two_factor + - domain: "secure.example.com" + policy: two_factor + - domain: "singlefactor.example.com" + policy: one_factor + - domain: "oidc.example.com" + policy: two_factor + - domain: "oidc-public.example.com" + policy: bypass + - domain: "traefik.example.com" + policy: bypass + +notifier: + smtp: + host: smtp + port: 1025 + sender: admin@example.com + disable_require_tls: true + +identity_providers: + oidc: + hmac_secret: IVPWBkAdJHje3uz7LtFTDU2pFUfh39Xm + issuer_private_key: | + -----BEGIN RSA PRIVATE KEY----- + MIIEogIBAAKCAQEAvOFmoEJFt1JkfdlwM3vJFg5rrY9d6LyyqezjZkBZDQ4qdEEU + dCrbW8ISFTtg9sfbrS3qingUzVP9VOfYPMC3r0ugjJXjhvJdBSaoLlzL3saeyrXk + frOOvkcWKzeOynqUNPhKy9dchmuLALFfd/Jy7Wzq0y7XxGeNidEmFjMAf9dwf6/+ + PjQjbG7zBFu/XSajITPHlDXPVDd0j2qw2wu5Z9iqn4LRXnAFnC438hZZKZU/+JxU + 2ezr6Sefiy8XTC2kDiq3cgLeEjSywlJOs+4TLjVS/3h75sh2Wk0xVaSwjPEjCOgm + a+2E3GJrGdQBiAjMSu101VBVwHUHaLDCn1T4NwIDAQABAoIBADWkupXnXI99Ogc4 + GxK0JF88Rz6qyhwQg5mZKthejCwWCt6roRiBF33O933KOHa+OljMAqHDCv1pzjgw + BIz0mvaRPw7OfylTajHNUdShDFHADVc7I6MMcgz+eYBarhY5jCAjKHMOPjv7DSZs + OdYCKLvfxC2oTyV714n9uZhyccDcvQpkgZuBDL0oxPom1GOI8TGhPjxvFOovEHWA + Q8q9XY4cUVNDikZmvpgeUkJHWYHYb+11vKeSupnYD03yJ3sDy+F6+m+3/XmzFbXb + 1p43ermHQsMfDlxPyulUUI0viSo2UhlMC/moAb9FusOv+dTl2lt0gGqzDJ9gg1z1 + XpHRnwkCgYEA5x48dyxd4lydtVYef9sBmbLJEYozsYyOwLcnrLSNaZxeCza1exyR + QIRogswoLDacxrYvO8FY6LtAEMkisv732M29zthBPm5wyoSZiM1X2YfQXKsmyh2h + x1/yCWv/BQjj68A8IAxToaXxSG4WAr/X00RGUkXgkgw122FxcmGuFyUCgYEA0TcR + dnt/oRMK4aCZHcBgTknzDfxKlJh4S0C9WjxKgr8IlW4LTeVSBuuqOObOQYImEhtw + TRTKZIViL0roDF79cioQSp1Tk5h6uy8wr6VyhWRnWfTz2/azoTHnmQ780rtAuEI/ + NvE6FiqwikJLjma1YJoRfr/bfmgMdxcYbJI1MSsCgYAEZ5Yda1IKu1siFpcUNrdM + F5UvaWPc0WHzGEqARxye06UTL6K7yuqVwTBAteVaGlxYiSZTTDcGkHMDHuIzaRqO + HjWs2IA90VsC8Q4ABnHTKnx1F6nwlin8I774IP/GN8ooNwyuS63YWdJEYBy5RrC1 + TQrODJjgD62DFdNUq7nmpQKBgFMJEzI+Q+KPJ0NztTG8t7x61y/W0Vb2yM+9Syn0 + QfJwlZyRR4VMHelHQZFB8dzIJgoLv9+n/8gztEtm5IB8dwUHst2aYaBz5UpDqYQd + Gz3cIrTuZpcH7DVvFCeIbknJLh+zk1lgFpjTqqvFMi27kANeQtFWnmwmKcRec0As + K1ZvAoGAV/3YB44/zIoB590+yhpx2HTmDPVHH+J+5O71Pi1D9W13ClBFLrE69wo+ + IQLIstBI5tGOGeuQNjXhDKJ1U30xppZXcnebrkA+oOo+6dy20zghFR2maAGXfWFU + pM4GsSnSTm0bXPebVouQFqhj7LqcQQzCqRDThmw/Lp1tJUmu40g= + -----END RSA PRIVATE KEY----- + clients: + - id: oidc-tester-app + secret: foobar + policy: two_factor + redirect_uris: + - https://oidc.example.com:8080/oauth2/callback + # This client is used for testing purpose. As of now, the app must be protected by ACLs + # otherwise it won't work properly. + - id: oidc-tester-app-public + secret: foobar + authorization_policy: one_factor + redirect_uris: + - https://oidc-public.example.com:8080/oauth2/callback +... diff --git a/internal/suites/OIDCTraefik/docker-compose.yml b/internal/suites/OIDCTraefik/docker-compose.yml new file mode 100644 index 00000000..a5f4dd82 --- /dev/null +++ b/internal/suites/OIDCTraefik/docker-compose.yml @@ -0,0 +1,10 @@ +--- +version: '3' +services: + authelia-backend: + volumes: + - './OIDCTraefik/configuration.yml:/config/configuration.yml:ro' + - './OIDCTraefik/users.yml:/config/users.yml' + - './OIDCTraefik/keypair/key.pem:/config/issuer.pem:ro' + - './common/ssl:/config/ssl:ro' +... diff --git a/internal/suites/OIDCTraefik/keypair/key.pem b/internal/suites/OIDCTraefik/keypair/key.pem new file mode 100644 index 00000000..c5df003c --- /dev/null +++ b/internal/suites/OIDCTraefik/keypair/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAvOFmoEJFt1JkfdlwM3vJFg5rrY9d6LyyqezjZkBZDQ4qdEEU +dCrbW8ISFTtg9sfbrS3qingUzVP9VOfYPMC3r0ugjJXjhvJdBSaoLlzL3saeyrXk +frOOvkcWKzeOynqUNPhKy9dchmuLALFfd/Jy7Wzq0y7XxGeNidEmFjMAf9dwf6/+ +PjQjbG7zBFu/XSajITPHlDXPVDd0j2qw2wu5Z9iqn4LRXnAFnC438hZZKZU/+JxU +2ezr6Sefiy8XTC2kDiq3cgLeEjSywlJOs+4TLjVS/3h75sh2Wk0xVaSwjPEjCOgm +a+2E3GJrGdQBiAjMSu101VBVwHUHaLDCn1T4NwIDAQABAoIBADWkupXnXI99Ogc4 +GxK0JF88Rz6qyhwQg5mZKthejCwWCt6roRiBF33O933KOHa+OljMAqHDCv1pzjgw +BIz0mvaRPw7OfylTajHNUdShDFHADVc7I6MMcgz+eYBarhY5jCAjKHMOPjv7DSZs +OdYCKLvfxC2oTyV714n9uZhyccDcvQpkgZuBDL0oxPom1GOI8TGhPjxvFOovEHWA +Q8q9XY4cUVNDikZmvpgeUkJHWYHYb+11vKeSupnYD03yJ3sDy+F6+m+3/XmzFbXb +1p43ermHQsMfDlxPyulUUI0viSo2UhlMC/moAb9FusOv+dTl2lt0gGqzDJ9gg1z1 +XpHRnwkCgYEA5x48dyxd4lydtVYef9sBmbLJEYozsYyOwLcnrLSNaZxeCza1exyR +QIRogswoLDacxrYvO8FY6LtAEMkisv732M29zthBPm5wyoSZiM1X2YfQXKsmyh2h +x1/yCWv/BQjj68A8IAxToaXxSG4WAr/X00RGUkXgkgw122FxcmGuFyUCgYEA0TcR +dnt/oRMK4aCZHcBgTknzDfxKlJh4S0C9WjxKgr8IlW4LTeVSBuuqOObOQYImEhtw +TRTKZIViL0roDF79cioQSp1Tk5h6uy8wr6VyhWRnWfTz2/azoTHnmQ780rtAuEI/ +NvE6FiqwikJLjma1YJoRfr/bfmgMdxcYbJI1MSsCgYAEZ5Yda1IKu1siFpcUNrdM +F5UvaWPc0WHzGEqARxye06UTL6K7yuqVwTBAteVaGlxYiSZTTDcGkHMDHuIzaRqO +HjWs2IA90VsC8Q4ABnHTKnx1F6nwlin8I774IP/GN8ooNwyuS63YWdJEYBy5RrC1 +TQrODJjgD62DFdNUq7nmpQKBgFMJEzI+Q+KPJ0NztTG8t7x61y/W0Vb2yM+9Syn0 +QfJwlZyRR4VMHelHQZFB8dzIJgoLv9+n/8gztEtm5IB8dwUHst2aYaBz5UpDqYQd +Gz3cIrTuZpcH7DVvFCeIbknJLh+zk1lgFpjTqqvFMi27kANeQtFWnmwmKcRec0As +K1ZvAoGAV/3YB44/zIoB590+yhpx2HTmDPVHH+J+5O71Pi1D9W13ClBFLrE69wo+ +IQLIstBI5tGOGeuQNjXhDKJ1U30xppZXcnebrkA+oOo+6dy20zghFR2maAGXfWFU +pM4GsSnSTm0bXPebVouQFqhj7LqcQQzCqRDThmw/Lp1tJUmu40g= +-----END RSA PRIVATE KEY----- diff --git a/internal/suites/OIDCTraefik/keypair/key.pub b/internal/suites/OIDCTraefik/keypair/key.pub new file mode 100644 index 00000000..b8e37ffa --- /dev/null +++ b/internal/suites/OIDCTraefik/keypair/key.pub @@ -0,0 +1,9 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvOFmoEJFt1JkfdlwM3vJ +Fg5rrY9d6LyyqezjZkBZDQ4qdEEUdCrbW8ISFTtg9sfbrS3qingUzVP9VOfYPMC3 +r0ugjJXjhvJdBSaoLlzL3saeyrXkfrOOvkcWKzeOynqUNPhKy9dchmuLALFfd/Jy +7Wzq0y7XxGeNidEmFjMAf9dwf6/+PjQjbG7zBFu/XSajITPHlDXPVDd0j2qw2wu5 +Z9iqn4LRXnAFnC438hZZKZU/+JxU2ezr6Sefiy8XTC2kDiq3cgLeEjSywlJOs+4T +LjVS/3h75sh2Wk0xVaSwjPEjCOgma+2E3GJrGdQBiAjMSu101VBVwHUHaLDCn1T4 +NwIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/internal/suites/OIDCTraefik/users.yml b/internal/suites/OIDCTraefik/users.yml new file mode 100644 index 00000000..a52978b2 --- /dev/null +++ b/internal/suites/OIDCTraefik/users.yml @@ -0,0 +1,35 @@ +--- +############################################################### +# Users Database # +############################################################### + +# This file can be used if you do not have an LDAP set up. + +# List of users +users: + john: + displayname: "John Doe" + password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length + email: john.doe@authelia.com + groups: + - admins + - dev + + harry: + displayname: "Harry Potter" + password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length + email: harry.potter@authelia.com + groups: [] + + bob: + displayname: "Bob Dylan" + password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length + email: bob.dylan@authelia.com + groups: + - dev + + james: + displayname: "James Dean" + password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length + email: james.dean@authelia.com +... diff --git a/internal/suites/const.go b/internal/suites/const.go index 2053161e..d1300159 100644 --- a/internal/suites/const.go +++ b/internal/suites/const.go @@ -41,6 +41,9 @@ var MX1MailBaseURL = fmt.Sprintf("https://mx1.mail.%s", BaseDomain) // MX2MailBaseURL the base URL of the mx2.mail domain. var MX2MailBaseURL = fmt.Sprintf("https://mx2.mail.%s", BaseDomain) +// OIDCBaseURL the base URL of the oidc domain. +var OIDCBaseURL = fmt.Sprintf("https://oidc.%s", BaseDomain) + // DuoBaseURL the base URL of the Duo configuration API. var DuoBaseURL = "https://duo.example.com" diff --git a/internal/suites/docker.go b/internal/suites/docker.go index 2c978766..cb5349d3 100644 --- a/internal/suites/docker.go +++ b/internal/suites/docker.go @@ -45,6 +45,11 @@ func (de *DockerEnvironment) createCommand(cmd string) *exec.Cmd { return utils.Command("bash", "-c", dockerCmdLine) } +// Pull pull all images of needed in the environment. +func (de *DockerEnvironment) Pull(images ...string) error { + return de.createCommandWithStdout(fmt.Sprintf("pull %s", strings.Join(images, " "))).Run() +} + // Up spawn a docker environment. func (de *DockerEnvironment) Up() error { return de.createCommandWithStdout("up --build -d").Run() diff --git a/internal/suites/example/compose/authelia/docker-compose.backend.dev.yml b/internal/suites/example/compose/authelia/docker-compose.backend.dev.yml index 099af651..8fe1be0a 100644 --- a/internal/suites/example/compose/authelia/docker-compose.backend.dev.yml +++ b/internal/suites/example/compose/authelia/docker-compose.backend.dev.yml @@ -24,7 +24,7 @@ services: - 'traefik.frontend.rule=Host:login.example.com;PathPrefix:/api' - 'traefik.protocol=https' # Traefik 2.x - - 'traefik.http.routers.authelia_backend.rule=Host(`login.example.com`) && PathPrefix(`/api`) || Host(`login.example.com`) && PathPrefix(`${PathPrefix}/api/`)' # yamllint disable-line rule:line-length + - 'traefik.http.routers.authelia_backend.rule=Host(`login.example.com`) && PathPrefix(`/.well-known/openid-configuration`) || Host(`login.example.com`) && PathPrefix(`/api`) || Host(`login.example.com`) && PathPrefix(`${PathPrefix}/api/`)' # yamllint disable-line rule:line-length - 'traefik.http.routers.authelia_backend.entrypoints=https' - 'traefik.http.routers.authelia_backend.tls=true' - 'traefik.http.services.authelia_backend.loadbalancer.server.scheme=https' diff --git a/internal/suites/example/compose/authelia/docker-compose.backend.dist.yml b/internal/suites/example/compose/authelia/docker-compose.backend.dist.yml index 9a00dbce..524f8c1d 100644 --- a/internal/suites/example/compose/authelia/docker-compose.backend.dist.yml +++ b/internal/suites/example/compose/authelia/docker-compose.backend.dist.yml @@ -8,10 +8,11 @@ services: - 'traefik.frontend.rule=Host:login.example.com;PathPrefix:/api' - 'traefik.protocol=https' # Traefik 2.x - - 'traefik.http.routers.authelia_backend.rule=Host(`login.example.com`) && PathPrefix(`/api`) || Host(`login.example.com`) && PathPrefix(`${PathPrefix}/api/`)' # yamllint disable-line rule:line-length + - 'traefik.http.routers.authelia_backend.rule=Host(`login.example.com`) && PathPrefix(`/.well-known/openid-configuration`) || Host(`login.example.com`) && PathPrefix(`/api`) || Host(`login.example.com`) && PathPrefix(`${PathPrefix}/api/`)' # yamllint disable-line rule:line-length - 'traefik.http.routers.authelia_backend.entrypoints=https' - 'traefik.http.routers.authelia_backend.tls=true' - 'traefik.http.services.authelia_backend.loadbalancer.server.scheme=https' + - 'traefik.http.services.authelia_backend.passHostHeader=true' volumes: - '../..:/authelia' environment: diff --git a/internal/suites/example/compose/nginx/backend/html/home/index.html b/internal/suites/example/compose/nginx/backend/html/home/index.html index 1178229a..2eea6802 100644 --- a/internal/suites/example/compose/nginx/backend/html/home/index.html +++ b/internal/suites/example/compose/nginx/backend/html/home/index.html @@ -58,6 +58,9 @@
  • mx2.main.example.com / secret.html
  • +
  • + oidc.example.com / (only in OIDC suite). +
  • You can also log off by visiting the following [1-9]\d*?)(?P[smhdwMy])?$`) -// Hour is an int based representation of the time unit. -const Hour = time.Minute * 60 - -// Day is an int based representation of the time unit. -const Day = Hour * 24 - -// Week is an int based representation of the time unit. -const Week = Day * 7 - -// Year is an int based representation of the time unit. -const Year = Day * 365 - -// Month is an int based representation of the time unit. -const Month = Year / 12 - -const windows = "windows" - -// RFC3339Zero is the default value for time.Time.Unix(). -const RFC3339Zero = int64(-62135596800) - -const testStringInput = "abcdefghijkl" - // AlphaNumericCharacters are literally just valid alphanumeric chars. var AlphaNumericCharacters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") // ErrTLSVersionNotSupported returned when an unknown TLS version supplied. var ErrTLSVersionNotSupported = errors.New("supplied TLS version isn't supported") - -// TLS13 is the textual representation of TLS 1.3. -const TLS13 = "1.3" - -// TLS12 is the textual representation of TLS 1.2. -const TLS12 = "1.2" - -// TLS11 is the textual representation of TLS 1.1. -const TLS11 = "1.1" - -// TLS10 is the textual representation of TLS 1.0. -const TLS10 = "1.0" diff --git a/internal/utils/files.go b/internal/utils/files.go index a70d11c0..6a14f091 100644 --- a/internal/utils/files.go +++ b/internal/utils/files.go @@ -1,12 +1,49 @@ package utils import ( + "errors" "os" ) -// FileExists returns whether the given file or directory exists. -func FileExists(path string) (bool, error) { - _, err := os.Stat(path) +// FileExists returns true if the given path exists and is a file. +func FileExists(path string) (exists bool, err error) { + info, err := os.Stat(path) + if err == nil { + if info.IsDir() { + return false, errors.New("path is a directory") + } + + return true, nil + } + + if os.IsNotExist(err) { + return false, nil + } + + return false, err +} + +// DirectoryExists returns true if the given path exists and is a directory. +func DirectoryExists(path string) (exists bool, err error) { + info, err := os.Stat(path) + if err == nil { + if info.IsDir() { + return true, nil + } + + return false, errors.New("path is a file") + } + + if os.IsNotExist(err) { + return false, nil + } + + return false, err +} + +// PathExists returns true if the given path exists. +func PathExists(path string) (exists bool, err error) { + _, err = os.Stat(path) if err == nil { return true, nil } diff --git a/internal/utils/files_test.go b/internal/utils/files_test.go new file mode 100644 index 00000000..73b313d8 --- /dev/null +++ b/internal/utils/files_test.go @@ -0,0 +1,56 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestShouldCheckIfFileExists(t *testing.T) { + exists, err := FileExists("../../README.md") + + assert.NoError(t, err) + assert.True(t, exists) + + exists, err = FileExists("../../") + assert.EqualError(t, err, "path is a directory") + assert.False(t, exists) + + exists, err = FileExists("../../NOTAFILE.md") + assert.NoError(t, err) + assert.False(t, exists) +} + +func TestShouldCheckIfDirectoryExists(t *testing.T) { + exists, err := DirectoryExists("../../") + + assert.NoError(t, err) + assert.True(t, exists) + + exists, err = DirectoryExists("../../README.md") + assert.EqualError(t, err, "path is a file") + assert.False(t, exists) + + exists, err = DirectoryExists("../../NOTADIRECTORY/") + assert.NoError(t, err) + assert.False(t, exists) +} + +func TestShouldCheckIfPathExists(t *testing.T) { + exists, err := PathExists("../../README.md") + + assert.NoError(t, err) + assert.True(t, exists) + + exists, err = PathExists("../../") + assert.NoError(t, err) + assert.True(t, exists) + + exists, err = PathExists("../../NOTAFILE.md") + assert.NoError(t, err) + assert.False(t, exists) + + exists, err = PathExists("../../NOTADIRECTORY/") + assert.NoError(t, err) + assert.False(t, exists) +} diff --git a/internal/utils/hashing.go b/internal/utils/hashing.go new file mode 100644 index 00000000..16a670bc --- /dev/null +++ b/internal/utils/hashing.go @@ -0,0 +1,13 @@ +package utils + +import ( + "crypto/sha256" + "fmt" +) + +// HashSHA256FromString takes an input string and calculates the SHA256 checksum returning it as a base16 hash string. +func HashSHA256FromString(input string) (output string) { + sum := sha256.Sum256([]byte(input)) + + return fmt.Sprintf("%x", sum) +} diff --git a/internal/utils/rsa.go b/internal/utils/rsa.go new file mode 100644 index 00000000..cc5a09d6 --- /dev/null +++ b/internal/utils/rsa.go @@ -0,0 +1,83 @@ +package utils + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" +) + +// GenerateRsaKeyPair generate an RSA key pair. +// bits can be 2048 or 4096. +func GenerateRsaKeyPair(bits int) (*rsa.PrivateKey, *rsa.PublicKey) { + privkey, _ := rsa.GenerateKey(rand.Reader, bits) + return privkey, &privkey.PublicKey +} + +// ExportRsaPrivateKeyAsPemStr marshal a rsa private key into PEM string. +func ExportRsaPrivateKeyAsPemStr(privkey *rsa.PrivateKey) string { + privkeyBytes := x509.MarshalPKCS1PrivateKey(privkey) + privkeyPem := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: privkeyBytes, + }, + ) + + return string(privkeyPem) +} + +// ParseRsaPrivateKeyFromPemStr parse a RSA private key from PEM string. +func ParseRsaPrivateKeyFromPemStr(privPEM string) (*rsa.PrivateKey, error) { + block, _ := pem.Decode([]byte(privPEM)) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the key") + } + + priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + return priv, nil +} + +// ExportRsaPublicKeyAsPemStr marshal a RSA public into a PEM string. +func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) (string, error) { + pubkeyBytes, err := x509.MarshalPKIXPublicKey(pubkey) + if err != nil { + return "", err + } + + pubkeyPem := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: pubkeyBytes, + }, + ) + + return string(pubkeyPem), nil +} + +// ParseRsaPublicKeyFromPemStr parse RSA public key from a PEM string. +func ParseRsaPublicKeyFromPemStr(pubPEM string) (*rsa.PublicKey, error) { + block, _ := pem.Decode([]byte(pubPEM)) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the key") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + + switch pub := pub.(type) { + case *rsa.PublicKey: + return pub, nil + default: + break // fall through + } + + return nil, errors.New("Key type is not RSA") +} diff --git a/internal/utils/strings.go b/internal/utils/strings.go index a1e07e53..56676f3b 100644 --- a/internal/utils/strings.go +++ b/internal/utils/strings.go @@ -68,15 +68,13 @@ func SliceString(s string, d int) (array []string) { return } -// IsStringSlicesDifferent checks two slices of strings and on the first occurrence of a string item not existing in the -// other slice returns true, otherwise returns false. -func IsStringSlicesDifferent(a, b []string) (different bool) { +func isStringSlicesDifferent(a, b []string, method func(s string, b []string) bool) (different bool) { if len(a) != len(b) { return true } for _, s := range a { - if !IsStringInSlice(s, b) { + if !method(s, b) { return true } } @@ -84,6 +82,18 @@ func IsStringSlicesDifferent(a, b []string) (different bool) { return false } +// IsStringSlicesDifferent checks two slices of strings and on the first occurrence of a string item not existing in the +// other slice returns true, otherwise returns false. +func IsStringSlicesDifferent(a, b []string) (different bool) { + return isStringSlicesDifferent(a, b, IsStringInSlice) +} + +// IsStringSlicesDifferentFold checks two slices of strings and on the first occurrence of a string item not existing in +// the other slice (case insensitive) returns true, otherwise returns false. +func IsStringSlicesDifferentFold(a, b []string) (different bool) { + return isStringSlicesDifferent(a, b, IsStringInSliceFold) +} + // StringSlicesDelta takes a before and after []string and compares them returning a added and removed []string. func StringSlicesDelta(before, after []string) (added, removed []string) { for _, s := range before { diff --git a/internal/utils/strings_test.go b/internal/utils/strings_test.go index 793d42c9..a774ed52 100644 --- a/internal/utils/strings_test.go +++ b/internal/utils/strings_test.go @@ -7,6 +7,12 @@ import ( "github.com/stretchr/testify/require" ) +func TestShouldDetectAlphaNumericString(t *testing.T) { + assert.True(t, IsStringAlphaNumeric("abc")) + assert.True(t, IsStringAlphaNumeric("abc123")) + assert.False(t, IsStringAlphaNumeric("abc123@")) +} + func TestShouldSplitIntoEvenStringsOfFour(t *testing.T) { input := testStringInput @@ -70,6 +76,12 @@ func TestShouldFindSliceDifferences(t *testing.T) { b := []string{"abc", "xyz"} assert.True(t, IsStringSlicesDifferent(a, b)) + assert.True(t, IsStringSlicesDifferentFold(a, b)) + + c := []string{"Abc", "xyz"} + + assert.True(t, IsStringSlicesDifferent(b, c)) + assert.False(t, IsStringSlicesDifferentFold(b, c)) } func TestShouldNotFindSliceDifferences(t *testing.T) { @@ -77,6 +89,7 @@ func TestShouldNotFindSliceDifferences(t *testing.T) { b := []string{"abc", "onetwothree"} assert.False(t, IsStringSlicesDifferent(a, b)) + assert.False(t, IsStringSlicesDifferentFold(a, b)) } func TestShouldFindSliceDifferenceWhenDifferentLength(t *testing.T) { @@ -84,6 +97,7 @@ func TestShouldFindSliceDifferenceWhenDifferentLength(t *testing.T) { b := []string{"abc", "onetwothree", "more"} assert.True(t, IsStringSlicesDifferent(a, b)) + assert.True(t, IsStringSlicesDifferentFold(a, b)) } func TestShouldFindStringInSliceContains(t *testing.T) { diff --git a/web/src/App.tsx b/web/src/App.tsx index c7a70b33..48402a15 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -14,12 +14,14 @@ import { RegisterSecurityKeyRoute, RegisterOneTimePasswordRoute, LogoutRoute, + ConsentRoute, } from "./Routes"; import * as themes from "./themes"; import { getBasePath } from "./utils/BasePath"; import { getRememberMe, getResetPassword, getTheme } from "./utils/Configuration"; import RegisterOneTimePassword from "./views/DeviceRegistration/RegisterOneTimePassword"; import RegisterSecurityKey from "./views/DeviceRegistration/RegisterSecurityKey"; +import ConsentView from "./views/LoginPortal/ConsentView/ConsentView"; import LoginPortal from "./views/LoginPortal/LoginPortal"; import SignOut from "./views/LoginPortal/SignOut/SignOut"; import ResetPasswordStep1 from "./views/ResetPassword/ResetPasswordStep1"; @@ -65,6 +67,9 @@ const App: React.FC = () => { + + + diff --git a/web/src/Routes.ts b/web/src/Routes.ts index 3e3ee0ac..670580f7 100644 --- a/web/src/Routes.ts +++ b/web/src/Routes.ts @@ -1,13 +1,14 @@ -export const FirstFactorRoute = "/"; -export const AuthenticatedRoute = "/authenticated"; +export const FirstFactorRoute: string = "/"; +export const AuthenticatedRoute: string = "/authenticated"; +export const ConsentRoute: string = "/consent"; -export const SecondFactorRoute = "/2fa"; -export const SecondFactorU2FRoute = "/2fa/security-key"; -export const SecondFactorTOTPRoute = "/2fa/one-time-password"; -export const SecondFactorPushRoute = "/2fa/push-notification"; +export const SecondFactorRoute: string = "/2fa"; +export const SecondFactorU2FRoute: string = "/2fa/security-key"; +export const SecondFactorTOTPRoute: string = "/2fa/one-time-password"; +export const SecondFactorPushRoute: string = "/2fa/push-notification"; -export const ResetPasswordStep1Route = "/reset-password/step1"; -export const ResetPasswordStep2Route = "/reset-password/step2"; -export const RegisterSecurityKeyRoute = "/security-key/register"; -export const RegisterOneTimePasswordRoute = "/one-time-password/register"; -export const LogoutRoute = "/logout"; +export const ResetPasswordStep1Route: string = "/reset-password/step1"; +export const ResetPasswordStep2Route: string = "/reset-password/step2"; +export const RegisterSecurityKeyRoute: string = "/security-key/register"; +export const RegisterOneTimePasswordRoute: string = "/one-time-password/register"; +export const LogoutRoute: string = "/logout"; diff --git a/web/src/components/NotificationBar.tsx b/web/src/components/NotificationBar.tsx index f4202bf4..b58e2c90 100644 --- a/web/src/components/NotificationBar.tsx +++ b/web/src/components/NotificationBar.tsx @@ -18,7 +18,7 @@ const NotificationBar = function (props: Props) { if (notification && notification !== null) { setTmpNotification(notification); } - }, [notification]); + }, [notification, setTmpNotification]); const shouldSnackbarBeOpen = notification !== undefined && notification !== null; diff --git a/web/src/hooks/Consent.ts b/web/src/hooks/Consent.ts new file mode 100644 index 00000000..9a7023b2 --- /dev/null +++ b/web/src/hooks/Consent.ts @@ -0,0 +1,6 @@ +import { getRequestedScopes } from "../services/Consent"; +import { useRemoteCall } from "./RemoteCall"; + +export function useRequestedScopes() { + return useRemoteCall(getRequestedScopes, []); +} diff --git a/web/src/hooks/Redirector.ts b/web/src/hooks/Redirector.ts new file mode 100644 index 00000000..18197a12 --- /dev/null +++ b/web/src/hooks/Redirector.ts @@ -0,0 +1,5 @@ +export function useRedirector() { + return (url: string) => { + window.location.href = url; + }; +} diff --git a/web/src/hooks/RemoteCall.ts b/web/src/hooks/RemoteCall.ts index a33afb37..be26ae18 100644 --- a/web/src/hooks/RemoteCall.ts +++ b/web/src/hooks/RemoteCall.ts @@ -5,23 +5,25 @@ type PromisifiedFunction = (...args: any) => Promise; export function useRemoteCall( fn: PromisifiedFunction, deps: DependencyList, -): [Ret | undefined, PromisifiedFunction, boolean, Error | undefined] { +): [Ret | undefined, () => void, boolean, Error | undefined] { const [data, setData] = useState(undefined as Ret | undefined); const [inProgress, setInProgress] = useState(false); const [error, setError] = useState(undefined as Error | undefined); const fnCallback = useCallback(fn, [fn, deps]); - const triggerCallback = useCallback(async () => { - try { - setInProgress(true); - const res = await fnCallback(); - setInProgress(false); - setData(res); - } catch (err) { - console.error(err); - setError(err); - } + const triggerCallback = useCallback(() => { + (async () => { + try { + setInProgress(true); + const res = await fnCallback(); + setInProgress(false); + setData(res); + } catch (err) { + console.error(err); + setError(err); + } + })(); }, [setInProgress, setError, fnCallback]); return [data, triggerCallback, inProgress, error]; diff --git a/web/src/layouts/LoginLayout.tsx b/web/src/layouts/LoginLayout.tsx index 858abc8d..81a6106d 100644 --- a/web/src/layouts/LoginLayout.tsx +++ b/web/src/layouts/LoginLayout.tsx @@ -8,7 +8,7 @@ import { ReactComponent as UserSvg } from "../assets/images/user.svg"; export interface Props { id?: string; children?: ReactNode; - title: string; + title?: string; showBrand?: boolean; } @@ -21,11 +21,13 @@ const LoginLayout = function (props: Props) { - - - {props.title} - - + {props.title ? ( + + + {props.title} + + + ) : null} {props.children} @@ -63,7 +65,11 @@ const useStyles = makeStyles((theme) => ({ width: "64px", fill: theme.custom.icon, }, - body: {}, + body: { + marginTop: theme.spacing(), + paddingTop: theme.spacing(), + paddingBottom: theme.spacing(), + }, poweredBy: { fontSize: "0.7em", color: grey[500], diff --git a/web/src/services/Api.ts b/web/src/services/Api.ts index 2b5be2ce..a298723d 100644 --- a/web/src/services/Api.ts +++ b/web/src/services/Api.ts @@ -4,6 +4,9 @@ import { getBasePath } from "../utils/BasePath"; const basePath = getBasePath(); +// Note: If you change this const you must also do so in the backend at internal/handlers/cost.go. +export const ConsentPath = basePath + "/api/oidc/consent"; + export const FirstFactorPath = basePath + "/api/firstfactor"; export const InitiateTOTPRegistrationPath = basePath + "/api/secondfactor/totp/identity/start"; export const CompleteTOTPRegistrationPath = basePath + "/api/secondfactor/totp/identity/finish"; diff --git a/web/src/services/Consent.ts b/web/src/services/Consent.ts new file mode 100644 index 00000000..da38e11c --- /dev/null +++ b/web/src/services/Consent.ts @@ -0,0 +1,42 @@ +import { ConsentPath } from "./Api"; +import { Post, Get } from "./Client"; + +interface ConsentPostRequestBody { + client_id: string; + accept_or_reject: "accept" | "reject"; +} + +interface ConsentPostResponseBody { + redirect_uri: string; +} + +interface ConsentGetResponseBody { + client_id: string; + client_description: string; + scopes: Scope[]; + audience: Audience[]; +} + +interface Scope { + name: string; + description: string; +} + +interface Audience { + name: string; + description: string; +} + +export function getRequestedScopes() { + return Get(ConsentPath); +} + +export function acceptConsent(clientID: string) { + const body: ConsentPostRequestBody = { client_id: clientID, accept_or_reject: "accept" }; + return Post(ConsentPath, body); +} + +export function rejectConsent(clientID: string) { + const body: ConsentPostRequestBody = { client_id: clientID, accept_or_reject: "reject" }; + return Post(ConsentPath, body); +} diff --git a/web/src/views/LoginPortal/ConsentView/ConsentView.tsx b/web/src/views/LoginPortal/ConsentView/ConsentView.tsx new file mode 100644 index 00000000..62437b25 --- /dev/null +++ b/web/src/views/LoginPortal/ConsentView/ConsentView.tsx @@ -0,0 +1,187 @@ +import React, { useEffect, Fragment, ReactNode } from "react"; + +import { Button, Grid, List, ListItem, ListItemIcon, ListItemText, Tooltip, makeStyles } from "@material-ui/core"; +import { AccountBox, CheckBox, Contacts, Drafts, Group } from "@material-ui/icons"; +import { useHistory } from "react-router-dom"; + +import { useRequestedScopes } from "../../../hooks/Consent"; +import { useNotifications } from "../../../hooks/NotificationsContext"; +import { useRedirector } from "../../../hooks/Redirector"; +import LoginLayout from "../../../layouts/LoginLayout"; +import { acceptConsent, rejectConsent } from "../../../services/Consent"; +import LoadingPage from "../../LoadingPage/LoadingPage"; + +export interface Props {} + +function showListItemAvatar(id: string) { + switch (id) { + case "openid": + return ; + case "profile": + return ; + case "groups": + return ; + case "email": + return ; + default: + return ; + } +} + +const ConsentView = function (props: Props) { + const classes = useStyles(); + const history = useHistory(); + const redirect = useRedirector(); + const { createErrorNotification, resetNotification } = useNotifications(); + const [resp, fetch, , err] = useRequestedScopes(); + + useEffect(() => { + if (err) { + history.replace("/"); + console.error(`Unable to display consent screen: ${err.message}`); + } + }, [history, resetNotification, createErrorNotification, err]); + + useEffect(() => { + fetch(); + }, [fetch]); + + const handleAcceptConsent = async () => { + // This case should not happen in theory because the buttons are disabled when response is undefined. + if (!resp) { + return; + } + const res = await acceptConsent(resp.client_id); + if (res.redirect_uri) { + redirect(res.redirect_uri); + } else { + throw new Error("Unable to redirect the user"); + } + }; + + const handleRejectConsent = async () => { + if (!resp) { + return; + } + const res = await rejectConsent(resp.client_id); + if (res.redirect_uri) { + redirect(res.redirect_uri); + } else { + throw new Error("Unable to redirect the user"); + } + }; + + return ( + + + + +
    + The application + {` ${resp?.client_description} (${resp?.client_id}) `} + is requesting the following permissions +
    +
    + +
    + + {resp?.scopes.map((s) => ( + + + {showListItemAvatar(s.name)} + + + + ))} + +
    +
    + + + + + + + + + + +
    +
    +
    + ); +}; + +const useStyles = makeStyles((theme) => ({ + container: { + paddingTop: theme.spacing(4), + paddingBottom: theme.spacing(4), + display: "block", + justifyContent: "center", + }, + scopesListContainer: { + textAlign: "center", + }, + scopesList: { + display: "inline-block", + backgroundColor: theme.palette.background.paper, + marginTop: theme.spacing(2), + marginBottom: theme.spacing(2), + }, + clientID: { + fontWeight: "bold", + }, + button: { + marginLeft: theme.spacing(), + marginRight: theme.spacing(), + width: "100%", + }, + bulletIcon: { + display: "inline-block", + }, + permissionsContainer: { + border: "1px solid #dedede", + margin: theme.spacing(4), + }, + listItem: { + textAlign: "center", + marginRight: theme.spacing(2), + }, +})); + +export default ConsentView; + +interface ComponentOrLoadingProps { + ready: boolean; + + children: ReactNode; +} + +function ComponentOrLoading(props: ComponentOrLoadingProps) { + return ( + +
    + +
    + {props.ready ? props.children : null} +
    + ); +} diff --git a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx index a9e8b900..346310ac 100644 --- a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx +++ b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx @@ -79,7 +79,7 @@ const FirstFactorForm = function (props: Props) { return ( - + ({ - root: { - marginTop: theme.spacing(), - marginBottom: theme.spacing(), - }, actionRow: { display: "flex", flexDirection: "row", diff --git a/web/src/views/LoginPortal/LoginPortal.tsx b/web/src/views/LoginPortal/LoginPortal.tsx index 9601f8f2..a53cf8f2 100644 --- a/web/src/views/LoginPortal/LoginPortal.tsx +++ b/web/src/views/LoginPortal/LoginPortal.tsx @@ -5,6 +5,7 @@ import { Switch, Route, Redirect, useHistory, useLocation } from "react-router"; import { useConfiguration } from "../../hooks/Configuration"; import { useNotifications } from "../../hooks/NotificationsContext"; import { useRedirectionURL } from "../../hooks/RedirectionURL"; +import { useRedirector } from "../../hooks/Redirector"; import { useRequestMethod } from "../../hooks/RequestMethod"; import { useAutheliaState } from "../../hooks/State"; import { useUserPreferences as userUserInfo } from "../../hooks/UserInfo"; @@ -35,6 +36,7 @@ const LoginPortal = function (props: Props) { const requestMethod = useRequestMethod(); const { createErrorNotification } = useNotifications(); const [firstFactorDisabled, setFirstFactorDisabled] = useState(true); + const redirector = useRedirector(); const [state, fetchState, , fetchStateError] = useAutheliaState(); const [userInfo, fetchUserInfo, , fetchUserInfoError] = userUserInfo(); @@ -112,7 +114,7 @@ const LoginPortal = function (props: Props) { const handleAuthSuccess = async (redirectionURL: string | undefined) => { if (redirectionURL) { // Do an external redirection pushed by the server. - window.location.href = redirectionURL; + redirector(redirectionURL); } else { // Refresh state fetchState(); @@ -152,6 +154,7 @@ const LoginPortal = function (props: Props) { {userInfo ? : null} + {/* By default we route to first factor page */} diff --git a/web/src/views/LoginPortal/SignOut/SignOut.tsx b/web/src/views/LoginPortal/SignOut/SignOut.tsx index c1a95794..dd9e6b6b 100644 --- a/web/src/views/LoginPortal/SignOut/SignOut.tsx +++ b/web/src/views/LoginPortal/SignOut/SignOut.tsx @@ -6,6 +6,7 @@ import { Redirect } from "react-router"; import { useIsMountedRef } from "../../../hooks/Mounted"; import { useNotifications } from "../../../hooks/NotificationsContext"; import { useRedirectionURL } from "../../../hooks/RedirectionURL"; +import { useRedirector } from "../../../hooks/Redirector"; import LoginLayout from "../../../layouts/LoginLayout"; import { FirstFactorRoute } from "../../../Routes"; import { signOut } from "../../../services/SignOut"; @@ -17,6 +18,7 @@ const SignOut = function (props: Props) { const style = useStyles(); const { createErrorNotification } = useNotifications(); const redirectionURL = useRedirectionURL(); + const redirector = useRedirector(); const [timedOut, setTimedOut] = useState(false); const [safeRedirect, setSafeRedirect] = useState(false); @@ -44,7 +46,7 @@ const SignOut = function (props: Props) { if (timedOut) { if (redirectionURL && safeRedirect) { - window.location.href = redirectionURL; + redirector(redirectionURL); } else { return ; }