Initial IDP version

- copied/stripped down from
  https://git.dittberner.info/jan/hydra_oidc_poc
This commit is contained in:
Jan Dittberner 2021-09-11 13:35:15 +02:00 committed by Jan Dittberner
parent 721dbc7e65
commit 88bfe0a5df
22 changed files with 2569 additions and 2 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
.idea/
/static
certs/
idp.toml

View file

@ -70,7 +70,7 @@ csrf.key = "<32 bytes of base64 encoded data>"
Now you can start the IDP:
```
go run cmd/idp/main.go
go run cmd/idp.go
```
## Translations
@ -105,4 +105,4 @@ goi18n merge active.*.toml translate.*.toml
to merge the messages back into the active translation files. To add a new
language you need to add the language code to the languages configuration
option (default is defined in the configmap in cmd/idp/main.go).
option (default is defined in the configmap in cmd/idp.go).

86
active.de.toml Normal file
View file

@ -0,0 +1,86 @@
[CertLoginIntroText]
hash = "sha1-e9f7c0522e49ffacc49e3fc35c6ffd31e495baf6"
other = "Die Anwendung <strong>{{ .ClientName }}</strong> fragt nach einer Anmeldung."
[CertLoginRequestText]
hash = "sha1-1b20eea0f6fbb4ff139ecfe6b7a93c98cb14b8d7"
other = "Willst du die ausgewählte Identität für die Anmeldung verwenden?"
[ClaimsInformation]
hash = "sha1-4a6721995b5d87c02be77695910af642ca30b18a"
other = "Zusätzlich möchte die Anwendung Zugriff auf folgende Informationen:"
[EmailChoiceText]
hash = "sha1-8bba8cd3a8724d8c5b75da9b7d2ac084b6e9df90"
one = "Du hast ein gültiges Client-Zertifikat für die folgende E-Mail-Adresse vorgelegt:"
other = "Du hast ein gültiges Client-Zertifikate für mehrere E-Mail-Adressen vorgelegt. Bitte wähle aus, welches Du der Anwendung vorlegen möchtest:"
[ErrorTitle]
hash = "sha1-736aec25a98f5ec5b71400bb0163f891f509b566"
other = "Es ist ein Fehler aufgetreten"
[ErrorUnknown]
hash = "sha1-e5fd9aa24c9417e7332e6f25936ae2a6ec8f1524"
other = "Unbekannter Fehler"
[HintChooseAnIdentityForAuthentication]
hash = "sha1-7ee5b067009bbedc061274ee50a3027b50a06163"
other = "Wähle eine Identität für die Anmeldung."
[IntroConsentMoreInformation]
hash = "sha1-f58b8378238bd433deef3c3e6b0b70d0fd0dd59e"
other = "Auf der <a href=\"{{ .clientLink }}\">Beschreibungsseite</a> findest du mehr Informationen zu <strong>{{ .client }}</strong>."
[IntroConsentRequested]
hash = "sha1-3ac6a3583d40b5e8930c57531f0be9706f1e0194"
other = "Die Anwendung <strong>{{ .client }}</strong> hat deine Zustimmung für die Erteilung der folgenden Berechtigungen angefragt:"
[LabelAcceptCertLogin]
description = "Label for a button to accept certificate login"
hash = "sha1-59723066893f638afd7411540134ce200e970a5f"
other = "Ja, bitte"
[LabelConsent]
hash = "sha1-5e56a367cf99015bbe98488845541db00b7e04f6"
other = "Ich erteile hiermit meine Einwilligung, dass die Anwendung die angefragten Berechtigungen erhalten darf."
[LabelRejectCertLogin]
description = "Label for a button to reject certificate login"
hash = "sha1-49c4d0d1da1c0a7d7e9bf491b28a6e6825f4c2cd"
other = "Nein, schick mich zurück"
[LabelSubmit]
hash = "sha1-2dacf65959849884a011f36f76a04eebea94c5ea"
other = "Abschicken"
[LoginDeniedByUser]
hash = "sha1-bbad650536bfb091ad55d576262bbe4358277c73"
other = "Die Anmeldung wurde durch den Nutzer abgelehnt."
[LoginTitle]
hash = "sha1-9a24c8b64e047edc13f3c41ef7785bb2044a6d69"
other = "Anmelden mit einem Client-Zertifikat"
[Scope-email-Description]
hash = "sha1-3d2b89e9e0c5fcb8221c4e0e5cc3c42a684cd96b"
other = "Zugriff auf deine E-Mail-Adresse."
[Scope-offline-Description]
hash = "sha1-732881bf998daa62cbad8615b2e6feb7a053b123"
other = "Zugriff auf deine Daten behalten, bis du diese Berechtigung widerrufst."
[Scope-openid-Description]
hash = "sha1-0ad714e7a22b97d8247b70254990256bffa2ef76"
other = "Informationen über deine Identität abfragen."
[Scope-profile-Description]
hash = "sha1-67fced5e8a83bdac4b9eb30ec2edccafc8bb47ef"
other = "Zugriff auf deine Profilinformationen (Deinen Namen)."
[TitleRequestConsent]
hash = "sha1-14984a9e08cda5390677458a98408baa955f9f8b"
other = "Anwendung erbittet deine Zustimmung"
[WrongOrLockedUserOrInvalidPassword]
hash = "sha1-87e0a0ac67c6c3a06bed184e10b22aae4d075b64"
other = "Du hast einen ungültigen Nutzernamen oder ein ungültiges Passwort eingegeben oder dein Benutzerkonto wurde gesperrt."

30
active.en.toml Normal file
View file

@ -0,0 +1,30 @@
CertLoginIntroText = "The application <strong>{{ .ClientName }}</strong> requests a login."
CertLoginRequestText = "Do you want to use the chosen identity from the certificate for authentication?"
ClaimsInformation = "In addition the application wants access to the following information:"
ErrorTitle = "An error has occurred"
ErrorUnknown = "Unknown error"
HintChooseAnIdentityForAuthentication = "Choose an identity for authentication."
IntroConsentMoreInformation = "You can find more information about <strong>{{ .client }}</strong> at <a href=\"{{ .clientLink }}\">its description page</a>."
IntroConsentRequested = "The <strong>{{ .client }}</strong> application requested your consent for the following set of permissions:"
LabelConsent = "I hereby agree that the application may get the requested permissions."
LabelSubmit = "Submit"
LoginDeniedByUser = "Login has been denied by the user."
LoginTitle = "Authenticate with a client certificate"
Scope-email-Description = "Access your email address."
Scope-offline-Description = "Keep access to your information until you revoke the permission."
Scope-openid-Description = "Request information about your identity."
Scope-profile-Description = "Access your user profile information (your name)."
TitleRequestConsent = "Application requests your consent"
WrongOrLockedUserOrInvalidPassword = "You entered an invalid username or password or your account has been locked."
[EmailChoiceText]
one = "You have presented a valid client certificate for the following email address:"
other = "You have presented a valid client certificate for multiple email addresses. Please choose which one you want to present to the application:"
[LabelAcceptCertLogin]
description = "Label for a button to accept certificate login"
other = "Yes, please use this identity"
[LabelRejectCertLogin]
description = "Label for a button to reject certificate login"
other = "No, please send me back"

199
cmd/idp.go Normal file
View file

@ -0,0 +1,199 @@
/*
Copyright 2020, 2021 Jan Dittberner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"os/signal"
"sync/atomic"
"time"
"github.com/go-openapi/runtime/client"
"github.com/gorilla/csrf"
hydra "github.com/ory/hydra-client-go/client"
log "github.com/sirupsen/logrus"
"git.cacert.org/oidc_idp/handlers"
"git.cacert.org/oidc_idp/services"
)
func main() {
logger := log.New()
config, err := services.ConfigureApplication(
logger,
"IDP",
map[string]interface{}{
"server.port": 3000,
"server.name": "login.cacert.localhost",
"server.key": "certs/idp.cacert.localhost.key",
"server.certificate": "certs/idp.cacert.localhost.crt.pem",
"security.client.ca-file": "certs/client_ca.pem",
"admin.url": "https://hydra.cacert.localhost:4445/",
"i18n.languages": []string{"en", "de"},
})
if err != nil {
log.Fatalf("error loading configuration: %v", err)
}
logger.Infoln("Server is starting")
ctx := context.Background()
ctx = services.InitI18n(ctx, logger, config.Strings("i18n.languages"))
services.AddMessages(ctx)
adminURL, err := url.Parse(config.MustString("admin.url"))
if err != nil {
logger.Fatalf("error parsing admin URL: %v", err)
}
tlsClientConfig := &tls.Config{MinVersion: tls.VersionTLS12}
if config.Exists("api-client.rootCAs") {
rootCAFile := config.MustString("api-client.rootCAs")
caCertPool := x509.NewCertPool()
pemBytes, err := ioutil.ReadFile(rootCAFile)
if err != nil {
log.Fatalf("could not read CA certificate file: %v", err)
}
caCertPool.AppendCertsFromPEM(pemBytes)
tlsClientConfig.RootCAs = caCertPool
}
tlsClientTransport := &http.Transport{TLSClientConfig: tlsClientConfig}
httpClient := &http.Client{Transport: tlsClientTransport}
clientTransport := client.NewWithClient(
adminURL.Host,
adminURL.Path,
[]string{adminURL.Scheme},
httpClient,
)
adminClient := hydra.New(clientTransport, nil)
handlerContext := context.WithValue(ctx, handlers.CtxAdminClient, adminClient.Admin)
loginHandler, err := handlers.NewLoginHandler(handlerContext, logger)
if err != nil {
logger.Fatalf("error initializing login handler: %v", err)
}
consentHandler, err := handlers.NewConsentHandler(handlerContext, logger)
if err != nil {
logger.Fatalf("error initializing consent handler: %v", err)
}
logoutHandler := handlers.NewLogoutHandler(handlerContext, logger)
logoutSuccessHandler := handlers.NewLogoutSuccessHandler()
errorHandler := handlers.NewErrorHandler()
staticFiles := http.FileServer(http.Dir("static"))
router := http.NewServeMux()
router.Handle("/login", loginHandler)
router.Handle("/consent", consentHandler)
router.Handle("/logout", logoutHandler)
router.Handle("/error", errorHandler)
router.Handle("/logout-successful", logoutSuccessHandler)
router.Handle("/health", handlers.NewHealthHandler())
router.Handle("/images/", staticFiles)
router.Handle("/css/", staticFiles)
router.Handle("/js/", staticFiles)
if err != nil {
logger.Fatal(err)
}
csrfKey, err := base64.StdEncoding.DecodeString(config.MustString("security.csrf.key"))
if err != nil {
logger.Fatalf("could not parse CSRF key bytes: %v", err)
}
nextRequestId := func() string {
return fmt.Sprintf("%d", time.Now().UnixNano())
}
tracing := handlers.Tracing(nextRequestId)
logging := handlers.Logging(logger)
hsts := handlers.EnableHSTS()
csrfProtect := csrf.Protect(
csrfKey,
csrf.Secure(true),
csrf.SameSite(csrf.SameSiteStrictMode),
csrf.MaxAge(600))
errorMiddleware, err := handlers.ErrorHandling(
ctx,
logger,
"templates",
)
if err != nil {
logger.Fatalf("could not initialize request error handling: %v", err)
}
clientCertPool := x509.NewCertPool()
pemBytes, err := ioutil.ReadFile(config.MustString("security.client.ca-file"))
if err != nil {
logger.Fatalf("could not load client CA certificates: %v", err)
}
clientCertPool.AppendCertsFromPEM(pemBytes)
tlsConfig := &tls.Config{
ServerName: config.String("server.name"),
MinVersion: tls.VersionTLS12,
ClientAuth: tls.VerifyClientCertIfGiven,
ClientCAs: clientCertPool,
}
server := &http.Server{
Addr: fmt.Sprintf("%s:%d", config.String("server.name"), config.Int("server.port")),
Handler: tracing(logging(hsts(errorMiddleware(csrfProtect(router))))),
ReadTimeout: 20 * time.Second,
WriteTimeout: 20 * time.Second,
IdleTimeout: 30 * time.Second,
TLSConfig: tlsConfig,
}
done := make(chan bool)
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
go func() {
<-quit
logger.Infoln("Server is shutting down...")
atomic.StoreInt32(&handlers.Healthy, 0)
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil {
logger.Fatalf("Could not gracefully shutdown the server: %v\n", err)
}
close(done)
}()
logger.Infof("Server is ready to handle requests at https://%s/", server.Addr)
atomic.StoreInt32(&handlers.Healthy, 1)
if err := server.ListenAndServeTLS(
config.String("server.certificate"), config.String("server.key"),
); err != nil && err != http.ErrServerClosed {
logger.Fatalf("Could not listen on %s: %v\n", server.Addr, err)
}
<-done
logger.Infoln("Server stopped")
}

56
go.mod
View file

@ -1,3 +1,59 @@
module git.cacert.org/oidc_idp
go 1.17
require (
github.com/BurntSushi/toml v0.3.1
github.com/go-openapi/runtime v0.19.31
github.com/go-playground/form/v4 v4.2.0
github.com/gorilla/csrf v1.7.1
github.com/knadh/koanf v1.2.3
github.com/lestrrat-go/jwx v1.2.6
github.com/nicksnyder/go-i18n/v2 v2.1.2
github.com/ory/hydra-client-go v1.10.6
github.com/sirupsen/logrus v1.8.1
github.com/spf13/pflag v1.0.5
golang.org/x/text v0.3.6
)
require (
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-openapi/analysis v0.20.0 // indirect
github.com/go-openapi/errors v0.20.1 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/loads v0.20.2 // indirect
github.com/go-openapi/spec v0.20.3 // indirect
github.com/go-openapi/strfmt v0.20.2 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-openapi/validate v0.20.2 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/goccy/go-json v0.7.6 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.0 // indirect
github.com/lestrrat-go/httpcc v1.0.0 // indirect
github.com/lestrrat-go/iter v1.0.1 // indirect
github.com/lestrrat-go/option v1.0.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pelletier/go-toml v1.7.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
go.mongodb.org/mongo-driver v1.5.1 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

471
go.sum Normal file
View file

@ -0,0 +1,471 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
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/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
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-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ=
github.com/go-openapi/analysis v0.19.16/go.mod h1:GLInF007N83Ad3m8a/CbQ5TPzdnGT7workfHwuVjNVk=
github.com/go-openapi/analysis v0.20.0 h1:UN09o0kNhleunxW7LR+KnltD0YrJ8FF03pSqvAN3Vro=
github.com/go-openapi/analysis v0.20.0/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og=
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.20.1 h1:j23mMDtRxMwIobkpId7sWh7Ddcx4ivaoqUbfXx5P+a8=
github.com/go-openapi/errors v0.20.1/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI=
github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY=
github.com/go-openapi/loads v0.19.6/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc=
github.com/go-openapi/loads v0.19.7/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc=
github.com/go-openapi/loads v0.20.0/go.mod h1:2LhKquiE513rN5xC6Aan6lYOSddlL8Mp20AW9kpviM4=
github.com/go-openapi/loads v0.20.2 h1:z5p5Xf5wujMxS1y8aP+vxwW5qYT2zdJBbXKmQUG3lcc=
github.com/go-openapi/loads v0.20.2/go.mod h1:hTVUotJ+UonAMMZsvakEgmWKgtulweO9vYP2bQYKA/o=
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98=
github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
github.com/go-openapi/runtime v0.19.31 h1:GX+MgBxN12s/tQiHNJpvHDIoZiEXAz6j6Rqg0oJcnpg=
github.com/go-openapi/runtime v0.19.31/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
github.com/go-openapi/spec v0.19.15/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU=
github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU=
github.com/go-openapi/spec v0.20.1/go.mod h1:93x7oh+d+FQsmsieroS4cmR3u0p/ywH649a3qwC9OsQ=
github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ=
github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg=
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
github.com/go-openapi/strfmt v0.19.11/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc=
github.com/go-openapi/strfmt v0.20.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc=
github.com/go-openapi/strfmt v0.20.2 h1:6XZL+fF4VZYFxKQGLAUB358hOrRh/wS51uWEtlONADE=
github.com/go-openapi/strfmt v0.20.2/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M=
github.com/go-openapi/swag v0.19.13/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo=
github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8=
github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4=
github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9GA7monOmWBbeCI=
github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0=
github.com/go-openapi/validate v0.20.2 h1:AhqDegYV3J3iQkMPJSXkvzymHKMTw0BST3RK3hTT4ts=
github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZIVCbJBpTUoY0=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic=
github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
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/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/goccy/go-json v0.7.6 h1:H0wq4jppBQ+9222sk5+hPLL25abZQiRuQ6YPnjO9c+A=
github.com/goccy/go-json v0.7.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
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/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
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/uuid v1.0.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/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE=
github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/knadh/koanf v1.2.3 h1:2Rkr0YhhYk+4QEOm800Q3Pu0Wi87svTxM6uuEb4WhYw=
github.com/knadh/koanf v1.2.3/go.mod h1:xpPTwMhsA/aaQLAilyCCqfpEiY1gpa160AiCuWHJUjY=
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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4=
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
github.com/lestrrat-go/codegen v1.0.1/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM=
github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc=
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A=
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
github.com/lestrrat-go/jwx v1.2.6 h1:XAgfuHaOB7fDZ/6WhVgl8K89af768dU+3Nx4DlTbLIk=
github.com/lestrrat-go/jwx v1.2.6/go.mod h1:tJuGuAI3LC71IicTx82Mz1n3w9woAs2bYJZpkjJQ5aU=
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/nicksnyder/go-i18n/v2 v2.1.2 h1:QHYxcUJnGHBaq7XbvgunmZ2Pn0focXFqTD61CkH146c=
github.com/nicksnyder/go-i18n/v2 v2.1.2/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/ory/hydra-client-go v1.10.6 h1:w+uPgePbmztyLzwxWxOF89E/AG6wZuWTteHILn57BoQ=
github.com/ory/hydra-client-go v1.10.6/go.mod h1:HK2SkwXHKFC2TxHd+Ll9Xq2kJIYTQf2JTkqiC+sKcuA=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/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/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=
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
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/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.mongodb.org/mongo-driver v1.5.1 h1:9nOVLGDfOaZ9R0tBumx/BcuqkbFpyTCU2r/Po7A2azI=
go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/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-20180826012351-8a410e7b638d/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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-20190827160401-ba9fcec4b297/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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/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/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-20190412183630-56d357773e84/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-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-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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-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-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
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.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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

24
handlers/common.go Normal file
View file

@ -0,0 +1,24 @@
/*
Copyright 2020, 2021 Jan Dittberner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package handlers
type handlerContextKey int
const (
CtxAdminClient handlerContextKey = iota
)

444
handlers/consent.go Normal file
View file

@ -0,0 +1,444 @@
/*
Copyright 2020, 2021 Jan Dittberner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package handlers
import (
"context"
"encoding/json"
"html/template"
"net/http"
"net/url"
"strings"
"time"
commonModels "git.cacert.org/oidc_idp/models"
"github.com/go-playground/form/v4"
"github.com/gorilla/csrf"
"github.com/lestrrat-go/jwx/jwt/openid"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/ory/hydra-client-go/client/admin"
"github.com/ory/hydra-client-go/models"
log "github.com/sirupsen/logrus"
"git.cacert.org/oidc_idp/services"
)
type consentHandler struct {
adminClient *admin.Client
bundle *i18n.Bundle
consentTemplate *template.Template
context context.Context
logger *log.Logger
messageCatalog *services.MessageCatalog
}
type ConsentInformation struct {
GrantedScopes []string `form:"scope"`
SelectedClaims []string `form:"claims"`
ConsentChecked bool `form:"consent"`
}
type UserInfo struct {
Email string
EmailVerified bool
CommonName string
}
var supportedScopes, supportedClaims map[string]*i18n.Message
const (
ScopeOpenID = "openid"
ScopeOffline = "offline"
ScopeOfflineAccess = "offline_access"
ScopeProfile = "profile"
ScopeEmail = "email"
)
func init() {
supportedScopes = make(map[string]*i18n.Message)
supportedScopes[ScopeOpenID] = &i18n.Message{
ID: "Scope-openid-Description",
Other: "Request information about your identity.",
}
supportedScopes[ScopeOffline] = &i18n.Message{
ID: "Scope-offline-Description",
Other: "Keep access to your information until you revoke the permission.",
}
supportedScopes[ScopeOfflineAccess] = supportedScopes[ScopeOffline]
supportedScopes[ScopeProfile] = &i18n.Message{
ID: "Scope-profile-Description",
Other: "Access your user profile information (your name).",
}
supportedScopes[ScopeEmail] = &i18n.Message{
ID: "Scope-email-Description",
Other: "Access your email address.",
}
supportedClaims = make(map[string]*i18n.Message)
supportedClaims[openid.SubjectKey] = nil
supportedClaims[openid.EmailKey] = nil
supportedClaims[openid.EmailVerifiedKey] = nil
supportedClaims[openid.GivenNameKey] = nil
supportedClaims[openid.FamilyNameKey] = nil
supportedClaims[openid.MiddleNameKey] = nil
supportedClaims[openid.NameKey] = nil
supportedClaims[openid.BirthdateKey] = nil
supportedClaims[openid.ZoneinfoKey] = nil
supportedClaims[openid.LocaleKey] = nil
}
func (i *UserInfo) GetFullName() string {
return i.CommonName
}
func (h *consentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
challenge := r.URL.Query().Get("consent_challenge")
h.logger.Debugf("received consent challenge %s", challenge)
accept := r.Header.Get("Accept-Language")
localizer := i18n.NewLocalizer(h.bundle, accept)
// retrieve consent information
consentData, requestedClaims, err := h.getRequestedConsentInformation(challenge, r)
if err != nil {
// error is already handled in getRequestConsentInformation
return
}
switch r.Method {
case http.MethodGet:
h.renderConsentForm(w, r, consentData, requestedClaims, err, localizer)
break
case http.MethodPost:
var consentInfo ConsentInformation
// validate input
decoder := form.NewDecoder()
if err := decoder.Decode(&consentInfo, r.Form); err != nil {
h.logger.Error(err)
http.Error(
w,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError,
)
return
}
if consentInfo.ConsentChecked {
sessionData, err := h.getSessionData(r, consentInfo, requestedClaims, consentData.Payload)
if err != nil {
h.logger.Errorf("could not get session data: %v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
consentRequest, err := h.adminClient.AcceptConsentRequest(
admin.NewAcceptConsentRequestParams().WithConsentChallenge(challenge).WithBody(
&models.AcceptConsentRequest{
GrantAccessTokenAudience: nil,
GrantScope: consentInfo.GrantedScopes,
HandledAt: models.NullTime(time.Now()),
Remember: true,
RememberFor: 86400,
Session: sessionData,
}).WithTimeout(time.Second * 10))
if err != nil {
h.logger.Error(err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
w.Header().Add("Location", *consentRequest.GetPayload().RedirectTo)
w.WriteHeader(http.StatusFound)
return
} else {
consentRequest, err := h.adminClient.RejectConsentRequest(
admin.NewRejectConsentRequestParams().WithConsentChallenge(challenge).WithBody(
&models.RejectRequest{}))
if err != nil {
h.logger.Error(err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
w.Header().Add("Location", *consentRequest.GetPayload().RedirectTo)
w.WriteHeader(http.StatusFound)
}
}
}
func (h *consentHandler) getRequestedConsentInformation(challenge string, r *http.Request) (
*admin.GetConsentRequestOK,
*commonModels.OIDCClaimsRequest,
error,
) {
consentData, err := h.adminClient.GetConsentRequest(
admin.NewGetConsentRequestParams().WithConsentChallenge(challenge))
if err != nil {
h.logger.Errorf("error getting consent information: %v", err)
var errorDetails *ErrorDetails
errorDetails = &ErrorDetails{
ErrorMessage: "could not get consent details",
ErrorDetails: []string{http.StatusText(http.StatusInternalServerError)},
}
GetErrorBucket(r).AddError(errorDetails)
return nil, nil, err
}
var requestedClaims commonModels.OIDCClaimsRequest
requestUrl, err := url.Parse(consentData.Payload.RequestURL)
if err != nil {
h.logger.Warnf("could not parse original request URL %s: %v", consentData.Payload.RequestURL, err)
} else {
claimsParameter := requestUrl.Query().Get("claims")
if claimsParameter != "" {
decoder := json.NewDecoder(strings.NewReader(claimsParameter))
err := decoder.Decode(&requestedClaims)
if err != nil {
h.logger.Warnf(
"ignoring claims request parameter %s that could not be decoded: %v",
claimsParameter,
err,
)
}
}
}
return consentData, &requestedClaims, nil
}
func (h *consentHandler) renderConsentForm(
w http.ResponseWriter,
r *http.Request,
consentData *admin.GetConsentRequestOK,
claims *commonModels.OIDCClaimsRequest,
err error,
localizer *i18n.Localizer,
) {
trans := func(id string, values ...map[string]interface{}) string {
if len(values) > 0 {
return h.messageCatalog.LookupMessage(id, values[0], localizer)
}
return h.messageCatalog.LookupMessage(id, nil, localizer)
}
// render consent form
client := consentData.GetPayload().Client
err = h.consentTemplate.Lookup("base").Execute(w, map[string]interface{}{
"Title": trans("TitleRequestConsent"),
csrf.TemplateTag: csrf.TemplateField(r),
"errors": map[string]string{},
"client": client,
"requestedScope": h.mapRequestedScope(consentData.GetPayload().RequestedScope, localizer),
"requestedClaims": h.mapRequestedClaims(claims, localizer),
"LabelSubmit": trans("LabelSubmit"),
"LabelConsent": trans("LabelConsent"),
"IntroMoreInformation": template.HTML(trans("IntroConsentMoreInformation", map[string]interface{}{
"client": client.ClientName,
"clientLink": client.ClientURI,
})),
"ClaimsInformation": template.HTML(trans("ClaimsInformation", nil)),
"IntroConsentRequested": template.HTML(trans("IntroConsentRequested", map[string]interface{}{
"client": client.ClientName,
})),
})
}
type scopeWithLabel struct {
Name string
Label string
}
func (h *consentHandler) mapRequestedScope(scope models.StringSlicePipeDelimiter, localizer *i18n.Localizer) []*scopeWithLabel {
result := make([]*scopeWithLabel, 0)
for _, scopeName := range scope {
if _, ok := supportedScopes[scopeName]; !ok {
h.logger.Warnf("unsupported scope %s ignored", scopeName)
continue
}
label, err := localizer.Localize(&i18n.LocalizeConfig{
DefaultMessage: supportedScopes[scopeName],
})
if err != nil {
h.logger.Warnf("could not localize label for scope %s: %v", scopeName, err)
label = scopeName
}
result = append(result, &scopeWithLabel{Name: scopeName, Label: label})
}
return result
}
type claimWithLabel struct {
Name string
Label string
Essential bool
}
func (h *consentHandler) mapRequestedClaims(claims *commonModels.OIDCClaimsRequest, localizer *i18n.Localizer) []*claimWithLabel {
result := make([]*claimWithLabel, 0)
known := make(map[string]bool)
for _, claimElement := range []*commonModels.ClaimElement{claims.GetUserInfo(), claims.GetIDToken()} {
if claimElement != nil {
for k, v := range *claimElement {
if _, ok := supportedClaims[k]; !ok {
h.logger.Warnf("unsupported claim %s ignored", k)
continue
}
label, err := localizer.Localize(&i18n.LocalizeConfig{
DefaultMessage: supportedClaims[k],
})
if err != nil {
h.logger.Warnf("could not localize label for claim %s: %v", k, err)
label = k
}
if !known[k] {
result = append(result, &claimWithLabel{
Name: k,
Label: label,
Essential: v.IsEssential(),
})
known[k] = true
}
}
}
}
return result
}
func (h *consentHandler) getSessionData(
r *http.Request,
info ConsentInformation,
claims *commonModels.OIDCClaimsRequest,
payload *models.ConsentRequest,
) (*models.ConsentRequestSession, error) {
idTokenData := make(map[string]interface{}, 0)
accessTokenData := make(map[string]interface{}, 0)
userInfo := h.GetUserInfoFromClientCertificate(r, payload.Subject)
h.fillTokenData(accessTokenData, payload.RequestedScope, claims, info, userInfo)
h.fillTokenData(idTokenData, payload.RequestedScope, claims, info, userInfo)
return &models.ConsentRequestSession{
AccessToken: accessTokenData,
IDToken: idTokenData,
}, nil
}
func (h *consentHandler) fillTokenData(
m map[string]interface{},
requestedScope models.StringSlicePipeDelimiter,
claimsRequest *commonModels.OIDCClaimsRequest,
consentInformation ConsentInformation,
userInfo *UserInfo,
) {
for _, scope := range requestedScope {
granted := false
for _, k := range consentInformation.GrantedScopes {
if k == scope {
granted = true
break
}
}
if !granted {
continue
}
switch scope {
case ScopeEmail:
// email
// OPTIONAL. This scope value requests access to the email and
// email_verified Claims.
m[openid.EmailKey] = userInfo.Email
m[openid.EmailVerifiedKey] = userInfo.EmailVerified
break
case ScopeProfile:
// profile
// OPTIONAL. This scope value requests access to the
// End-User's default profile Claims, which are: name,
// family_name, given_name, middle_name, nickname,
// preferred_username, profile, picture, website, gender,
// birthdate, zoneinfo, locale, and updated_at.
m[openid.NameKey] = userInfo.GetFullName()
break
}
}
if userInfoClaims := claimsRequest.GetUserInfo(); userInfoClaims != nil {
for claimName, claim := range *userInfoClaims {
granted := false
for _, k := range consentInformation.SelectedClaims {
if k == claimName {
granted = true
break
}
}
if !granted {
continue
}
if claim.WantedValue() != nil {
m[claimName] = *claim.WantedValue()
continue
}
if claim.IsEssential() {
h.logger.Warnf(
"handling for essential claim name %s not implemented",
claimName,
)
} else {
h.logger.Warnf(
"handling for claim name %s not implemented",
claimName,
)
}
}
}
}
func (h *consentHandler) GetUserInfoFromClientCertificate(r *http.Request, subject string) *UserInfo {
if r.TLS != nil && r.TLS.PeerCertificates != nil && len(r.TLS.PeerCertificates) > 0 {
firstCert := r.TLS.PeerCertificates[0]
var verified bool
for _, email := range firstCert.EmailAddresses {
h.logger.Infof("authenticated with a client certificate for email address %s", email)
if subject == email {
verified = true
}
}
if !verified {
h.logger.Warnf("authentication attempt with a wrong certificate that did not contain the requested address %s", subject)
return nil
}
return &UserInfo{
Email: subject,
EmailVerified: verified,
CommonName: firstCert.Subject.CommonName,
}
}
return nil
}
func NewConsentHandler(ctx context.Context, logger *log.Logger) (*consentHandler, error) {
consentTemplate := template.Must(template.ParseFiles(
"templates/base.gohtml", "templates/consent.gohtml"))
return &consentHandler{
adminClient: ctx.Value(CtxAdminClient).(*admin.Client),
bundle: services.GetI18nBundle(ctx),
consentTemplate: consentTemplate,
context: ctx,
logger: logger,
messageCatalog: services.GetMessageCatalog(ctx),
}, nil
}

168
handlers/error.go Normal file
View file

@ -0,0 +1,168 @@
/*
Copyright 2020, 2021 Jan Dittberner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package handlers
import (
"context"
"fmt"
"html/template"
"net/http"
"path"
"git.cacert.org/oidc_idp/services"
"github.com/nicksnyder/go-i18n/v2/i18n"
log "github.com/sirupsen/logrus"
)
type errorKey int
const (
errorBucketKey errorKey = iota
)
type ErrorDetails struct {
ErrorMessage string
ErrorDetails []string
ErrorCode string
Error error
}
type ErrorBucket struct {
errorDetails *ErrorDetails
templates *template.Template
logger *log.Logger
bundle *i18n.Bundle
messageCatalog *services.MessageCatalog
}
func (b *ErrorBucket) serveHTTP(w http.ResponseWriter, r *http.Request) {
if b.errorDetails != nil {
accept := r.Header.Get("Accept-Language")
localizer := i18n.NewLocalizer(b.bundle, accept)
err := b.templates.Lookup("base").Execute(w, map[string]interface{}{
"Title": b.messageCatalog.LookupMessage(
"ErrorTitle",
nil,
localizer,
),
"details": b.errorDetails,
})
if err != nil {
log.Errorf("error rendering error template: %v", err)
http.Error(
w,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError,
)
}
}
}
func GetErrorBucket(r *http.Request) *ErrorBucket {
return r.Context().Value(errorBucketKey).(*ErrorBucket)
}
// call this from your application's handler
func (b *ErrorBucket) AddError(details *ErrorDetails) {
b.errorDetails = details
}
type errorResponseWriter struct {
http.ResponseWriter
ctx context.Context
statusCode int
}
func (w *errorResponseWriter) WriteHeader(code int) {
w.statusCode = code
if code >= 400 {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
errorBucket := w.ctx.Value(errorBucketKey).(*ErrorBucket)
if errorBucket != nil && errorBucket.errorDetails == nil {
errorBucket.AddError(&ErrorDetails{
ErrorMessage: http.StatusText(code),
})
}
}
w.ResponseWriter.WriteHeader(code)
}
func (w *errorResponseWriter) Write(content []byte) (int, error) {
if w.statusCode > 400 {
errorBucket := w.ctx.Value(errorBucketKey).(*ErrorBucket)
if errorBucket != nil {
if errorBucket.errorDetails.ErrorDetails == nil {
errorBucket.errorDetails.ErrorDetails = make([]string, 0)
}
errorBucket.errorDetails.ErrorDetails = append(
errorBucket.errorDetails.ErrorDetails, string(content),
)
return len(content), nil
}
}
return w.ResponseWriter.Write(content)
}
func ErrorHandling(
handlerContext context.Context,
logger *log.Logger,
templateBaseDir string,
) (func(http.Handler) http.Handler, error) {
errorTemplates, err := template.ParseFiles(
path.Join(templateBaseDir, "base.gohtml"),
path.Join(templateBaseDir, "errors.gohtml"),
)
if err != nil {
return nil, err
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
errorBucket := &ErrorBucket{
templates: errorTemplates,
logger: logger,
bundle: services.GetI18nBundle(handlerContext),
messageCatalog: services.GetMessageCatalog(handlerContext),
}
ctx := context.WithValue(r.Context(), errorBucketKey, errorBucket)
interCeptingResponseWriter := &errorResponseWriter{
w,
ctx,
http.StatusOK,
}
next.ServeHTTP(
interCeptingResponseWriter,
r.WithContext(ctx),
)
errorBucket.serveHTTP(w, r)
})
}, nil
}
type errorHandler struct {
}
func (e *errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
_, _ = fmt.Fprintf(w, `
didumm %#v
`, r.URL.Query())
}
func NewErrorHandler() *errorHandler {
return &errorHandler{}
}

251
handlers/login.go Normal file
View file

@ -0,0 +1,251 @@
/*
Copyright 2020, 2021 Jan Dittberner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package handlers
import (
"bytes"
"context"
"errors"
"fmt"
"html/template"
"net/http"
"strconv"
"time"
"github.com/gorilla/csrf"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/ory/hydra-client-go/client/admin"
"github.com/ory/hydra-client-go/models"
log "github.com/sirupsen/logrus"
"git.cacert.org/oidc_idp/services"
)
type acrType string
const (
ClientCertificate acrType = "cert" // client certificate login
// ClientCertificateOTP acrType = "cert+otp"
// ClientCertificateToken acrType = "cert+token"
)
type templateName string
const (
CertificateLogin templateName = "cert"
NoEmailsInClientCertificate = "no_emails"
)
type loginHandler struct {
adminClient *admin.Client
bundle *i18n.Bundle
context context.Context
logger *log.Logger
templates map[templateName]*template.Template
messageCatalog *services.MessageCatalog
}
func (h *loginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var err error
challenge := r.URL.Query().Get("login_challenge")
h.logger.Debugf("received login challenge %s\n", challenge)
accept := r.Header.Get("Accept-Language")
localizer := i18n.NewLocalizer(h.bundle, accept)
certEmails := h.getEmailAddressesFromClientCertificate(r)
if certEmails == nil {
h.renderNoEmailsInClientCertificate(w, localizer)
return
}
switch r.Method {
case http.MethodGet:
loginRequest, err := h.adminClient.GetLoginRequest(admin.NewGetLoginRequestParams().WithLoginChallenge(challenge))
if err != nil {
h.logger.Warnf("could not get login request for challenge %s: %v", challenge, err)
var e *admin.GetLoginRequestGone
if errors.As(err, &e) {
w.Header().Set("Location", *e.GetPayload().RedirectTo)
w.WriteHeader(http.StatusGone)
return
}
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
h.renderRequestForClientCert(w, r, certEmails, localizer, loginRequest)
break
case http.MethodPost:
if r.FormValue("use-identity") != "accept" {
h.rejectLogin(w, challenge, localizer)
return
}
var userId string
// perform certificate auth
h.logger.Infof("would perform certificate authentication with: %+v", certEmails)
userId, err = h.performCertificateLogin(certEmails, r)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
// finish login and redirect to target
loginRequest, err := h.adminClient.AcceptLoginRequest(
admin.NewAcceptLoginRequestParams().WithLoginChallenge(challenge).WithBody(
&models.AcceptLoginRequest{
Acr: string(ClientCertificate),
Remember: true,
RememberFor: 0,
Subject: &userId,
}).WithTimeout(time.Second * 10))
if err != nil {
h.logger.Errorf("error getting login request: %#v", err)
var errorDetails *ErrorDetails
switch v := err.(type) {
case *admin.AcceptLoginRequestNotFound:
payload := v.GetPayload()
errorDetails = &ErrorDetails{
ErrorMessage: payload.Error,
ErrorDetails: []string{payload.ErrorDescription},
}
if v.Payload.StatusCode != 0 {
errorDetails.ErrorCode = strconv.Itoa(int(payload.StatusCode))
}
break
default:
errorDetails = &ErrorDetails{
ErrorMessage: "could not accept login",
ErrorDetails: []string{err.Error()},
}
}
GetErrorBucket(r).AddError(errorDetails)
return
}
w.Header().Add("Location", *loginRequest.GetPayload().RedirectTo)
w.WriteHeader(http.StatusFound)
default:
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
}
func (h *loginHandler) rejectLogin(w http.ResponseWriter, challenge string, localizer *i18n.Localizer) {
rejectLoginRequest, err := h.adminClient.RejectLoginRequest(admin.NewRejectLoginRequestParams().WithLoginChallenge(challenge).WithBody(
&models.RejectRequest{
ErrorDescription: h.messageCatalog.LookupMessage("LoginDeniedByUser", nil, localizer),
ErrorHint: h.messageCatalog.LookupMessage("HintChooseAnIdentityForAuthentication", nil, localizer),
StatusCode: http.StatusForbidden,
},
).WithTimeout(time.Second * 10))
if err != nil {
h.logger.Errorf("error getting reject login request: %#v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
w.Header().Set("Location", *rejectLoginRequest.GetPayload().RedirectTo)
w.WriteHeader(http.StatusFound)
}
func (h *loginHandler) getEmailAddressesFromClientCertificate(r *http.Request) []string {
if r.TLS != nil && r.TLS.PeerCertificates != nil && len(r.TLS.PeerCertificates) > 0 {
firstCert := r.TLS.PeerCertificates[0]
for _, email := range firstCert.EmailAddresses {
h.logger.Infof("authenticated with a client certificate for email address %s", email)
}
return firstCert.EmailAddresses
}
return nil
}
func (h *loginHandler) renderRequestForClientCert(w http.ResponseWriter, r *http.Request, emails []string, localizer *i18n.Localizer, loginRequest *admin.GetLoginRequestOK) {
trans := func(label string) string {
return h.messageCatalog.LookupMessage(label, nil, localizer)
}
rendered := bytes.NewBuffer(make([]byte, 0))
err := h.templates[CertificateLogin].Lookup("base").Execute(rendered, map[string]interface{}{
"Title": trans("LoginTitle"),
csrf.TemplateTag: csrf.TemplateField(r),
"IntroText": template.HTML(h.messageCatalog.LookupMessage(
"CertLoginIntroText",
map[string]interface{}{"ClientName": loginRequest.GetPayload().Client.ClientName},
localizer,
)),
"EmailChoiceText": h.messageCatalog.LookupMessagePlural("EmailChoiceText", nil, localizer, len(emails)),
"emails": emails,
"RequestText": trans("CertLoginRequestText"),
"AcceptLabel": trans("LabelAcceptCertLogin"),
"RejectLabel": trans("LabelRejectCertLogin"),
})
if err != nil {
h.logger.Error(err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
w.Header().Add("Pragma", "no-cache")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
_, _ = w.Write(rendered.Bytes())
}
func (h *loginHandler) performCertificateLogin(emails []string, r *http.Request) (string, error) {
requestedEmail := r.PostFormValue("email")
for _, email := range emails {
if email == requestedEmail {
return email, nil
}
}
return "", fmt.Errorf("no user found")
}
func (h *loginHandler) renderNoEmailsInClientCertificate(w http.ResponseWriter, localizer *i18n.Localizer) {
trans := func(label string) string {
return h.messageCatalog.LookupMessage(label, nil, localizer)
}
err := h.templates[NoEmailsInClientCertificate].Lookup("base").Execute(w, map[string]interface{}{
"Title": trans("NoEmailsInClientCertificateTitle"),
"Explanation": trans("NoEmailsInClientCertificateExplanation"),
})
if err != nil {
h.logger.Error(err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
}
func NewLoginHandler(ctx context.Context, logger *log.Logger) (*loginHandler, error) {
return &loginHandler{
adminClient: ctx.Value(CtxAdminClient).(*admin.Client),
bundle: services.GetI18nBundle(ctx),
context: ctx,
logger: logger,
templates: map[templateName]*template.Template{
CertificateLogin: template.Must(template.ParseFiles(
"templates/base.gohtml",
"templates/client_certificate.gohtml",
)),
NoEmailsInClientCertificate: template.Must(template.ParseFiles(
"templates/base.gohtml",
"templates/no_email_in_client_certificate.gohtml",
)),
},
messageCatalog: services.GetMessageCatalog(ctx),
}, nil
}

75
handlers/logout.go Normal file
View file

@ -0,0 +1,75 @@
/*
Copyright 2020, 2021 Jan Dittberner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package handlers
import (
"context"
"net/http"
"time"
"github.com/ory/hydra-client-go/client/admin"
log "github.com/sirupsen/logrus"
)
type logoutHandler struct {
adminClient *admin.Client
logger *log.Logger
}
func (h *logoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
challenge := r.URL.Query().Get("logout_challenge")
h.logger.Debugf("received challenge %s\n", challenge)
logoutRequest, err := h.adminClient.GetLogoutRequest(
admin.NewGetLogoutRequestParams().WithLogoutChallenge(challenge).WithTimeout(time.Second * 10))
if err != nil {
h.logger.Errorf("error getting logout requests: %v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
h.logger.Debugf("received logout request: %#v", logoutRequest.Payload)
acceptLogoutRequest, err := h.adminClient.AcceptLogoutRequest(
admin.NewAcceptLogoutRequestParams().WithLogoutChallenge(challenge))
if err != nil {
h.logger.Errorf("error accepting logout: %v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
w.Header().Set("Location", *acceptLogoutRequest.GetPayload().RedirectTo)
w.WriteHeader(http.StatusFound)
}
func NewLogoutHandler(ctx context.Context, logger *log.Logger) *logoutHandler {
return &logoutHandler{
logger: logger,
adminClient: ctx.Value(CtxAdminClient).(*admin.Client),
}
}
type logoutSuccessHandler struct {
}
func (l *logoutSuccessHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
panic("implement me")
}
func NewLogoutSuccessHandler() *logoutSuccessHandler {
return &logoutSuccessHandler{}
}

100
handlers/observability.go Normal file
View file

@ -0,0 +1,100 @@
/*
Copyright 2020, 2021 Jan Dittberner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package handlers
import (
"context"
"net/http"
"sync/atomic"
log "github.com/sirupsen/logrus"
)
type key int
const (
requestIdKey key = iota
)
type statusCodeInterceptor struct {
http.ResponseWriter
code int
count int
}
func (sci *statusCodeInterceptor) WriteHeader(code int) {
sci.code = code
sci.ResponseWriter.WriteHeader(code)
}
func (sci *statusCodeInterceptor) Write(content []byte) (int, error) {
count, err := sci.ResponseWriter.Write(content)
sci.count += count
return count, err
}
func Logging(logger *log.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
interceptor := &statusCodeInterceptor{w, http.StatusOK, 0}
defer func() {
requestId, ok := r.Context().Value(requestIdKey).(string)
if !ok {
requestId = "unknown"
}
logger.Infof(
"%s %s \"%s %s\" %d %d \"%s\"",
requestId,
r.RemoteAddr,
r.Method,
r.URL.Path,
interceptor.code,
interceptor.count,
r.UserAgent(),
)
}()
next.ServeHTTP(interceptor, r)
})
}
}
func Tracing(nextRequestId func() string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestId := r.Header.Get("X-Request-Id")
if requestId == "" {
requestId = nextRequestId()
}
ctx := context.WithValue(r.Context(), requestIdKey, requestId)
w.Header().Set("X-Request-Id", requestId)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
var Healthy int32
func NewHealthHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if atomic.LoadInt32(&Healthy) == 1 {
w.WriteHeader(http.StatusNoContent)
return
}
w.WriteHeader(http.StatusServiceUnavailable)
})
}

33
handlers/security.go Normal file
View file

@ -0,0 +1,33 @@
/*
Copyright 2020, 2021 Jan Dittberner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package handlers
import (
"fmt"
"net/http"
"time"
)
func EnableHSTS() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Strict-Transport-Security", fmt.Sprintf("max-age=%d", int((time.Hour*24*180).Seconds())))
next.ServeHTTP(w, r)
})
}
}

183
models/oidc.go Normal file
View file

@ -0,0 +1,183 @@
/*
Copyright 2020, 2021 Jan Dittberner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
This package contains data models.
*/
package models
// An individual claim request.
//
// Specification
//
// https://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests
type IndividualClaimRequest map[string]interface{}
// ClaimElement represents a claim element
type ClaimElement map[string]*IndividualClaimRequest
// OIDCClaimsRequest the claims request parameter sent with the authorization request.
//
// Specification
//
// https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter
type OIDCClaimsRequest map[string]ClaimElement
// GetUserInfo extracts the userinfo claim element from the request.
//
// Specification
//
// https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
//
// Requests that the listed individual Claims be returned from the UserInfo
// Endpoint. If present, the listed Claims are being requested to be added to
// any Claims that are being requested using scope values. If not present, the
// Claims being requested from the UserInfo Endpoint are only those requested
// using scope values.
//
// When the userinfo member is used, the request MUST also use a response_type
// value that results in an Access Token being issued to the Client for use at
// the UserInfo Endpoint.
func (r OIDCClaimsRequest) GetUserInfo() *ClaimElement {
if userInfo, ok := r["userinfo"]; ok {
return &userInfo
}
return nil
}
// GetIDToken extracts the id_token claim element from the request.
//
// Specification
//
// https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
//
// Requests that the listed individual Claims be returned in the ID Token. If
// present, the listed Claims are being requested to be added to the default
// Claims in the ID Token. If not present, the default ID Token Claims are
// requested, as per the ID Token definition in Section 2 and per the
// additional per-flow ID Token requirements in Sections 3.1.3.6, 3.2.2.10,
// 3.3.2.11, and 3.3.3.6.
func (r OIDCClaimsRequest) GetIDToken() *ClaimElement {
if idToken, ok := r["id_token"]; ok {
return &idToken
}
return nil
}
// Checks whether the individual claim is an essential claim.
//
// Specification
//
// https://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests
//
// Indicates whether the Claim being requested is an Essential Claim. If the
// value is true, this indicates that the Claim is an Essential Claim. For
// instance, the Claim request:
//
// "auth_time": {"essential": true}
//
// can be used to specify that it is Essential to return an auth_time Claim
// Value. If the value is false, it indicates that it is a Voluntary Claim.
// The default is false.
//
// By requesting Claims as Essential Claims, the RP indicates to the End-User
// that releasing these Claims will ensure a smooth authorization for the
// specific task requested by the End-User.
//
// Note that even if the Claims are not available because the End-User did not
// authorize their release or they are not present, the Authorization Server
// MUST NOT generate an error when Claims are not returned, whether they are
// Essential or Voluntary, unless otherwise specified in the description of
// the specific claim.
func (i IndividualClaimRequest) IsEssential() bool {
if essential, ok := i["essential"]; ok {
return essential.(bool)
}
return false
}
// Returns the wanted value for an individual claim request.
//
// Specification
//
// https://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests
//
// Requests that the Claim be returned with a particular value. For instance
// the Claim request:
//
// "sub": {"value": "248289761001"}
//
// can be used to specify that the request apply to the End-User with Subject
// Identifier 248289761001. The value of the value member MUST be a valid
// value for the Claim being requested. Definitions of individual Claims can
// include requirements on how and whether the value qualifier is to be used
// when requesting that Claim.
func (i IndividualClaimRequest) WantedValue() *string {
if value, ok := i["value"]; ok {
valueString := value.(string)
return &valueString
}
return nil
}
// Get the allowed values for an individual claim request that specifies
// a values field.
//
// Specification
//
// https://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests
//
// Requests that the Claim be returned with one of a set of values, with the
// values appearing in order of preference. For instance the Claim request:
//
// "acr": {"essential": true,
// "values": ["urn:mace:incommon:iap:silver",
// "urn:mace:incommon:iap:bronze"]}
//
// specifies that it is Essential that the acr Claim be returned with either
// the value urn:mace:incommon:iap:silver or urn:mace:incommon:iap:bronze.
// The values in the values member array MUST be valid values for the Claim
// being requested. Definitions of individual Claims can include requirements
// on how and whether the values qualifier is to be used when requesting that
// Claim.
func (i IndividualClaimRequest) AllowedValues() []string {
if values, ok := i["values"]; ok {
return values.([]string)
}
return nil
}
// OpenIDConfiguration contains the parts of the OpenID discovery information
// that are relevant for us.
//
// Specifications
//
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
//
// https://openid.net/specs/openid-connect-rpinitiated-1_0.html#OPMetadata
type OpenIDConfiguration struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
UserInfoEndpoint string `json:"userinfo_endpoint"`
JwksUri string `json:"jwks_uri"`
RegistrationEndpoint string `json:"registration_endpoint"`
ScopesSupported []string `json:"scopes_supported"`
EndSessionEndpoint string `json:"end_session_endpoint"`
ClaimTypesSupported []string `json:"claim_types_supported"`
ClaimsSupported []string `json:"claims_supported"`
}

82
services/configuration.go Normal file
View file

@ -0,0 +1,82 @@
/*
Copyright 2020, 2021 Jan Dittberner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package services
import (
"fmt"
"os"
"strings"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/posflag"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
)
func ConfigureApplication(
logger *logrus.Logger,
appName string,
defaultConfig map[string]interface{},
) (*koanf.Koanf, error) {
f := pflag.NewFlagSet("config", pflag.ContinueOnError)
f.Usage = func() {
fmt.Println(f.FlagUsages())
os.Exit(0)
}
f.StringSlice(
"conf",
[]string{fmt.Sprintf("%s.toml", strings.ToLower(appName))},
"path to one or more .toml files",
)
var err error
if err = f.Parse(os.Args[1:]); err != nil {
logger.Fatal(err)
}
config := koanf.New(".")
_ = config.Load(confmap.Provider(defaultConfig, "."), nil)
cFiles, _ := f.GetStringSlice("conf")
for _, c := range cFiles {
if err := config.Load(file.Provider(c), toml.Parser()); err != nil {
logger.Fatalf("error loading config file: %s", err)
}
}
if err := config.Load(posflag.Provider(f, ".", config), nil); err != nil {
logger.Fatalf("error loading configuration: %s", err)
}
if err := config.Load(
file.Provider("resource_app.toml"),
toml.Parser(),
); err != nil && !os.IsNotExist(err) {
logrus.Fatalf("error loading config: %v", err)
}
prefix := fmt.Sprintf("%s_", strings.ToUpper(appName))
if err := config.Load(env.Provider(prefix, ".", func(s string) string {
return strings.Replace(strings.ToLower(
strings.TrimPrefix(s, prefix)), "_", ".", -1)
}), nil); err != nil {
logrus.Fatalf("error loading config: %v", err)
}
return config, err
}

233
services/i18n.go Normal file
View file

@ -0,0 +1,233 @@
/*
Copyright 2020, 2021 Jan Dittberner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package services
import (
"context"
"fmt"
log "github.com/sirupsen/logrus"
"github.com/BurntSushi/toml"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
)
func AddMessages(ctx context.Context) {
messages := make(map[string]*i18n.Message)
messages["unknown"] = &i18n.Message{
ID: "ErrorUnknown",
Other: "Unknown error",
}
messages["TitleRequestConsent"] = &i18n.Message{
ID: "TitleRequestConsent",
Other: "Application requests your consent",
}
messages["LabelSubmit"] = &i18n.Message{
ID: "LabelSubmit",
Other: "Submit",
}
messages["LabelConsent"] = &i18n.Message{
ID: "LabelConsent",
Other: "I hereby agree that the application may get the requested permissions.",
}
messages["IntroConsentRequested"] = &i18n.Message{
ID: "IntroConsentRequested",
Other: "The <strong>{{ .client }}</strong> application requested your consent for the following set of permissions:",
}
messages["IntroConsentMoreInformation"] = &i18n.Message{
ID: "IntroConsentMoreInformation",
Other: "You can find more information about <strong>{{ .client }}</strong> at <a href=\"{{ .clientLink }}\">its description page</a>.",
}
messages["ClaimsInformation"] = &i18n.Message{
ID: "ClaimsInformation",
Other: "In addition the application wants access to the following information:",
}
messages["WrongOrLockedUserOrInvalidPassword"] = &i18n.Message{
ID: "WrongOrLockedUserOrInvalidPassword",
Other: "You entered an invalid username or password or your account has been locked.",
}
messages["CertLoginIntroText"] = &i18n.Message{
ID: "CertLoginIntroText",
Other: "The application <strong>{{ .ClientName }}</strong> requests a login.",
}
messages["EmailChoiceText"] = &i18n.Message{
ID: "EmailChoiceText",
One: "You have presented a valid client certificate for the following email address:",
Other: "You have presented a valid client certificate for multiple email addresses. Please choose which one you want to present to the application:",
}
messages["LoginTitle"] = &i18n.Message{
ID: "LoginTitle",
Other: "Authenticate with a client certificate",
}
messages["CertLoginRequestText"] = &i18n.Message{
ID: "CertLoginRequestText",
Other: "Do you want to use the chosen identity from the certificate for authentication?",
}
messages["LabelAcceptCertLogin"] = &i18n.Message{
ID: "LabelAcceptCertLogin",
Description: "Label for a button to accept certificate login",
Other: "Yes, please use this identity",
}
messages["LabelRejectCertLogin"] = &i18n.Message{
ID: "LabelRejectCertLogin",
Description: "Label for a button to reject certificate login",
Other: "No, please send me back",
}
messages["LoginDeniedByUser"] = &i18n.Message{
ID: "LoginDeniedByUser",
Other: "Login has been denied by the user.",
}
messages["HintChooseAnIdentityForAuthentication"] = &i18n.Message{
ID: "HintChooseAnIdentityForAuthentication",
Other: "Choose an identity for authentication.",
}
GetMessageCatalog(ctx).AddMessages(messages)
}
type contextKey int
const (
ctxI18nBundle contextKey = iota
ctxI18nCatalog
)
type MessageCatalog struct {
messages map[string]*i18n.Message
logger *log.Logger
}
func (m *MessageCatalog) AddMessages(messages map[string]*i18n.Message) {
for key, value := range messages {
m.messages[key] = value
}
}
func (m *MessageCatalog) LookupErrorMessage(tag string, field string, value interface{}, localizer *i18n.Localizer) string {
var message *i18n.Message
message, ok := m.messages[fmt.Sprintf("%s-%s", field, tag)]
if !ok {
m.logger.Infof("no specific error message %s-%s", field, tag)
message, ok = m.messages[tag]
if !ok {
m.logger.Infof("no specific error message %s", tag)
message, ok = m.messages["unknown"]
if !ok {
m.logger.Warnf("no default translation found")
return tag
}
}
}
translation, err := localizer.Localize(&i18n.LocalizeConfig{
DefaultMessage: message,
TemplateData: map[string]interface{}{
"Value": value,
},
})
if err != nil {
m.logger.Error(err)
return tag
}
return translation
}
func (m *MessageCatalog) LookupMessage(id string, templateData map[string]interface{}, localizer *i18n.Localizer) string {
if message, ok := m.messages[id]; ok {
translation, err := localizer.Localize(&i18n.LocalizeConfig{
DefaultMessage: message,
TemplateData: templateData,
})
if err != nil {
switch err.(type) {
case *i18n.MessageNotFoundErr:
m.logger.Warnf("message %s not found: %v", id, err)
if translation != "" {
return translation
}
break
default:
m.logger.Error(err)
}
return id
}
return translation
} else {
m.logger.Warnf("no translation found for %s", id)
return id
}
}
func (m *MessageCatalog) LookupMessagePlural(id string, templateData map[string]interface{}, localizer *i18n.Localizer, count int) string {
if message, ok := m.messages[id]; ok {
translation, err := localizer.Localize(&i18n.LocalizeConfig{
DefaultMessage: message,
TemplateData: templateData,
PluralCount: count,
})
if err != nil {
switch err.(type) {
case *i18n.MessageNotFoundErr:
m.logger.Warnf("message %s not found: %v", id, err)
if translation != "" {
return translation
}
break
default:
m.logger.Error(err)
}
return id
}
return translation
} else {
m.logger.Warnf("no translation found for %s", id)
return id
}
}
func InitI18n(ctx context.Context, logger *log.Logger, languages []string) context.Context {
bundle := i18n.NewBundle(language.English)
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
for _, lang := range languages {
_, err := bundle.LoadMessageFile(fmt.Sprintf("active.%s.toml", lang))
if err != nil {
logger.Warnln("message bundle de.toml not found")
}
}
catalog := initMessageCatalog(logger)
ctx = context.WithValue(ctx, ctxI18nBundle, bundle)
ctx = context.WithValue(ctx, ctxI18nCatalog, catalog)
return ctx
}
func initMessageCatalog(logger *log.Logger) *MessageCatalog {
messages := make(map[string]*i18n.Message)
messages["ErrorTitle"] = &i18n.Message{
ID: "ErrorTitle",
Other: "An error has occurred",
}
return &MessageCatalog{messages: messages, logger: logger}
}
func GetI18nBundle(ctx context.Context) *i18n.Bundle {
return ctx.Value(ctxI18nBundle).(*i18n.Bundle)
}
func GetMessageCatalog(ctx context.Context) *MessageCatalog {
return ctx.Value(ctxI18nCatalog).(*MessageCatalog)
}

41
templates/base.gohtml Normal file
View file

@ -0,0 +1,41 @@
{{ define "base" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- generics -->
<link rel="icon" href="/images/favicon-32.png" sizes="32x32">
<link rel="icon" href="/images/favicon-57.png" sizes="57x57">
<link rel="icon" href="/images/favicon-76.png" sizes="76x76">
<link rel="icon" href="/images/favicon-96.png" sizes="96x96">
<link rel="icon" href="/images/favicon-128.png" sizes="128x128">
<link rel="icon" href="/images/favicon-192.png" sizes="192x192">
<link rel="icon" href="/images/favicon-228.png" sizes="228x228">
<link rel="icon" href="/images/favicon.ico">
<!-- Android -->
<link rel="shortcut icon" sizes="196x196" href="/images/favicon-196.png">
<!-- iOS -->
<link rel="apple-touch-icon" href="/images/favicon-120.png" sizes="120x120">
<link rel="apple-touch-icon" href="/images/favicon-152.png" sizes="152x152">
<link rel="apple-touch-icon" href="/images/favicon-180.png" sizes="180x180">
<link rel="stylesheet" href="/css/cacert.bundle.css">
<meta name="theme-color" content="#11568c">
<title>{{ .Title }}</title>
</head>
<body class="text-center idp d-flex flex-column h-100">
<main role="main" class="flex-shrink-0">
{{ template "content" . }}
</main>
<footer class="footer mt-auto py-3">
<div class="container">
<span class="text-muted small">© 2020, 2021 <a href="https://www.cacert.org/">CAcert</a></span>
</div>
</footer>
<script type="text/javascript" src="/js/cacert.bundle.js"></script>
</body>
</html>
{{ end }}

View file

@ -0,0 +1,30 @@
{{ define "content" }}
<form class="form-signin" method="post">
<img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4">
<h1 class="h3 mb-3">{{ .Title }}</h1>
<p class="text-left">{{ .IntroText }}</p>
<p class="text-left">{{ .EmailChoiceText }}</p>
<div class="mb-3">
{{ if eq (len .emails) 1 }}
{{ $email_address := index .emails 0 }}
<input type="hidden" name="email" value="{{ $email_address }}" id="email_0">
<label for="email_0">{{ $email_address }}</label>
{{ else }}
{{ range $index, $element := .emails }}
<input type="radio" name="email" value="{{ $element }}" id="email_{{ $index }}"><label
for="email_{{ $index }}">{{ $element }}</label>
{{ end }}
{{ end }}
{{ .csrfField }}
</div>
<p class="text-left">{{ .RequestText }}</p>
<div class="mb-2">
<button class="btn btn-lg btn-primary" type="submit" name="use-identity"
value="accept">{{ .AcceptLabel }}</button>
</div>
<div class="mb-2">
<button class="btn btn-outline-secondary" type="submit" name="use-identity"
value="reject">{{ .RejectLabel }}</button>
</div>
</form>
{{ end }}

40
templates/consent.gohtml Normal file
View file

@ -0,0 +1,40 @@
{{ define "content" }}
<form class="form-consent" method="post">
<img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4">
<h1 class="h3 mb-3">{{ .Title }}</h1>
{{ if .client.LogoURI }}
<p>
<img src="{{ .client.LogoURI }}" alt="{{ .client.ClientName }}"/>
</p>
{{ end }}
<p class="text-left">{{ .IntroConsentRequested }}</p>
<ul class="list-group text-left small mb-3">
{{ range $i, $scope := .requestedScope }}
<li class="list-group-item">
<input type="hidden" name="scope[{{ $i }}]" value="{{ $scope.Name }}">
{{ $scope.Label }}</li>
{{ end }}
</ul>
{{ if .requestedClaims }}
<p class="text-left">{{ .ClaimsInformation }}</p>
<ul class="list-group text-left small mb-3">
{{ range $i, $claim := .requestedClaims }}
<li class="list-group-item">
<input type="hidden" name="claims[{{ $i }}]" value="{{ $claim.Name }}">
{{ $claim.Label }}{{ if $claim.Essential }} *{{ end}}
</li>
{{ end}}
</ul>
{{ end }}
<p class="text-left">{{ .IntroMoreInformation }}</p>
{{ .csrfField }}
<div class="checkbox mb-3">
<label>
<input type="checkbox" name="consent" id="consent" value="true"/>
{{ .LabelConsent }}</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">{{ .LabelSubmit }}</button>
</form>
{{ end }}

13
templates/errors.gohtml Normal file
View file

@ -0,0 +1,13 @@
{{ define "content" }}
<div class="container">
<img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4">
<h1>{{ .Title }}</h1>
<h2>{{ if .details.ErrorCode }}
<strong>{{ .details.ErrorCode }}</strong> {{ end }}{{ .details.ErrorMessage }}</h2>
{{ if .details.ErrorDetails }}
{{ range .details.ErrorDetails }}
<p>{{ . }}</p>
{{ end }}
{{ end }}
</div>
{{ end }}

View file

@ -0,0 +1,7 @@
{{ define "content" }}
<form class="form-signin" method="post">
<img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4">
<h1 class="h3 mb-3">{{ .Title }}</h1>
<p class="text-left">{{ .Explanation }}</p>
</form>
{{ end }}