Compare commits

..

No commits in common. 'main' and '0.1.1' have entirely different histories.
main ... 0.1.1

5
.gitignore vendored

@ -1,11 +1,10 @@
*.pem *.pem
/.idea/ .idea/
/cacert-idp /cacert-idp
/certs/
/dist/ /dist/
/idp.toml /idp.toml
/static /static
/translations/translate.*.toml
/ui/css/ /ui/css/
/ui/images/ /ui/images/
/ui/js/ /ui/js/
certs/

@ -8,7 +8,7 @@ linters-settings:
const: const:
ORGANIZATION: CAcert Inc. ORGANIZATION: CAcert Inc.
template: |- template: |-
Copyright {{ ORGANIZATION }} Copyright {{ YEAR-RANGE }} {{ ORGANIZATION }}
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -23,7 +23,7 @@ linters-settings:
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
goimports: goimports:
local-prefixes: code.cacert.org/cacert/oidc-idp local-prefixes: code.cacert.org/cacert/oidc_idp
misspell: misspell:
locale: "US" locale: "US"
ignore-words: ignore-words:
@ -47,7 +47,7 @@ linters:
- gofmt - gofmt
- goheader - goheader
- goimports - goimports
- mnd - gomnd
- gosec - gosec
- lll - lll
- makezero - makezero

@ -18,12 +18,13 @@ go.sum: go.mod
go mod tidy go mod tidy
translations: $(TRANSLATIONS) $(GOFILES) translations: $(TRANSLATIONS) $(GOFILES)
if [ ! -z "$(wildcard translations/translate.*.toml)" ]; then \ cd translations ; \
goi18n extract .. ; \
goi18n merge active.*.toml ; \
if translate.*.toml 2>/dev/null; then \
echo "missing translations"; \ echo "missing translations"; \
goi18n merge -outdir translations translations/active.*.toml translations/translate.*.toml; \ goi18n merge active.*.toml translate.*.toml; \
fi ; \ fi
goi18n extract -outdir translations . ; \
goi18n merge -outdir translations translations/active.*.toml
lint: $(GOFILES) lint: $(GOFILES)
golangci-lint run --verbose golangci-lint run --verbose

@ -8,30 +8,45 @@ UI components that are required by Hydra to allow login and consent.
The code in this repository is licensed under the terms of the Apache License The code in this repository is licensed under the terms of the Apache License
Version 2.0. Version 2.0.
Copyright © 2020-2023 Jan Dittberner Copyright © 2020-2022 Jan Dittberner
## Setup ## Setup
### Certificates ### Certificates
You need a set of certificate and private key and a PEM file with CA You need a set of certificates for the IDP. You can use the Test CA created by
certificates used to verify client certificates to run `cacert-idp`. the ``setup_test_ca.sh`` script from the [CAcert developer
setup](https://git.dittberner.info/jan/cacert-devsetup) repository like this:
An easy way to generate server certificate and key for local testing is 1. create signing requests
[`mkcert`](https://github.com/FiloSottile/mkcert/releases).
1. Run `mkcert` to generate `idp.cacert.localhost+1.pem` and ```
`idp.cacert.localhost+1-key.pem`: mkdir certs
cd certs
openssl req -new -newkey rsa:3072 -nodes \
-keyout idp.cacert.localhost.key \
-out idp.cacert.localhost.csr.pem \
-subj /CN=idp.cacert.localhost \
-addext subjectAltName=DNS:idp.cacert.localhost,DNS:login.cacert.localhost
cp *.csr.pem $PATH_TO_DEVSETUP_TESTCA/
```
2. Use the CA to sign the certificates
```shell ```
mkcert -cert-file idp.cacert.localhost login.cacert.localhost pushd $PATH_TO_DEVSETUP_TESTCA/
openssl ca -config ca.cnf -name class3_ca -extensions server_ext \
-in idp.cacert.localhost.csr.pem \
-out idp.cacert.localhost.crt.pem -days 365
popd
cp $PATH_TO_DEVSETUP_TESTCA/idp.cacert.localhost.crt.pem .
``` ```
2. Copy CA certificate for client certificates 3. Copy CA certificate for client certificates
```shell ```
(curl -s http://www.cacert.org/certs/CAcert_Class3Root_x14E228.crt ; \ openssl x509 -in $PATH_TO_DEVSETUP_TESTCA/class3/ca.crt.pem \
curl -s http://www.cacert.org/certs/root_X0F.crt ) > client_ca.pem -out client_ca.pem
``` ```
### Configure IDP ### Configure IDP
@ -66,26 +81,26 @@ internationalization (i18n) support.
The translation workflow needs the `go18n` binary which can be installed via The translation workflow needs the `go18n` binary which can be installed via
```shell ```
go install github.com/nicksnyder/go-i18n/v2/goi18n go install github.com/nicksnyder/go-i18n/v2/goi18n
``` ```
To extract new messages from the code run To extract new messages from the code run
```shell ```
goi18n extract -outdir translations . goi18n extract .
``` ```
Then use Then use
```shell ```
goi18n merge -outdir translations translations/active.*.toml goi18n merge active.*.toml
``` ```
to create TOML files for translation as `translate.<locale>.toml`. After to create TOML files for translation as `translate.<locale>.toml`. After
translating the messages run translating the messages run
```shell ```
goi18n merge active.*.toml translate.*.toml goi18n merge active.*.toml translate.*.toml
``` ```

@ -5,63 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### [0.4.0] - 2024-05-20
- HTML for client logo image is only rendered if a client application has a logo URL
- link to client information is only rendered if a client application has a client URL
- use Consent and Deny buttons instead of a checkbox when asking for consent
- update dependencies
- switch from logrus to slog
### Fixed
- fix subject handling for login requests
## [0.3.0] - 2023-08-07
### Changed
- use a session to transport data from the login to the consent screens
- implement skip of consent screen for existing consent
- adapt to Hydra 2.x
- introduce a central template cache
- move common page header to templates/base.gohtml
### Added
- add management of consent to allow users to check and revoke consent
## [0.2.1] - 2023-08-03
### Changed
- improve formatting and german translation of login page
- enforce selection of an email address for multi-address client certificates
## [0.2.0] - 2023-07-29
### Added
- implement logout-successful handler
- implement rendering of authorization server errors
### Changed
- recommend `mkcert` to generate certificates for local development
- change default configuration to use files recommended in README.md
- separate server name and bind address to allow binding to a local address
but publish a different public name
- move default configuration to internal/services/configuration.go
- use structured logging
- support JSON logging and make it the default
- support log level configuration
- update dependencies
- re-order configuration precedence
1. default config file idp.toml
2. config files given via the `--conf` command line argument
3. environment variables with the `IDP_` prefix
- rename module to match Git repository URL
### Fixed
- run deb-systemd-helper in Debian package `postinst` script
## [0.1.3] - 2023-07-24
### Fixed
- fix conf parameter name
## [0.1.2] - 2023-07-24
### Fixed
- fix path to cacert-idp binary
## [0.1.1] - 2023-07-24 ## [0.1.1] - 2023-07-24
### Fixed
- fix ExecStart entry in systemd service unit - fix ExecStart entry in systemd service unit
## [0.1.0] - 2023-07-24 ## [0.1.0] - 2023-07-24

@ -1,5 +1,5 @@
/* /*
Copyright CAcert Inc. Copyright 2020-2023 CAcert Inc.
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -22,9 +22,7 @@ import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"log/slog"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@ -32,31 +30,23 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/go-openapi/runtime/client"
"github.com/gorilla/csrf" "github.com/gorilla/csrf"
"github.com/knadh/koanf" "github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/toml" hydra "github.com/ory/hydra-client-go/client"
"github.com/knadh/koanf/providers/confmap" log "github.com/sirupsen/logrus"
hydra "github.com/ory/hydra-client-go/v2"
"code.cacert.org/cacert/oidc-idp/internal/handlers" "code.cacert.org/cacert/oidc_idp/ui"
"code.cacert.org/cacert/oidc-idp/internal/services"
"code.cacert.org/cacert/oidc-idp/ui" "code.cacert.org/cacert/oidc_idp/internal/handlers"
"code.cacert.org/cacert/oidc_idp/internal/services"
) )
const ( const (
IdleTimeout = 30 * time.Second TimeoutThirty = 30 * time.Second
ShutdownTimeout = 30 * time.Second TimeoutTwenty = 20 * time.Second
ReadTimeOut = 20 * time.Second
WriteTimeOut = 20 * time.Second
DefaultCSRFMaxAge = 600 DefaultCSRFMaxAge = 600
DefaultServerPort = 3000
httpsDefaultPort = 443
sessionKeyLength = 32
sessionAuthKeyLength = 64
minCSRFKeyLength = 32
) )
var ( var (
@ -66,83 +56,73 @@ var (
) )
func main() { func main() {
var ( logger := log.New()
logLevel = new(slog.LevelVar)
logHandler slog.Handler
logger *slog.Logger
)
logHandler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})
logger = slog.New(logHandler)
slog.SetDefault(logger)
config, err := services.ConfigureApplication(logger, "IDP", services.DefaultConfig) config, err := services.ConfigureApplication(
logger,
"IDP",
map[string]interface{}{
"server.port": DefaultServerPort,
"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 { if err != nil {
logger.Error("error loading configuration", "err", err) log.Fatalf("error loading configuration: %v", err)
os.Exit(1)
} }
if level := config.Bytes("log.level"); level != nil { logger.WithFields(log.Fields{
err := logLevel.UnmarshalText(level) "version": version, "commit": commit, "date": date,
if err != nil { }).Info("Starting CAcert OpenID Connect Identity Provider")
logger.Error("could not parse log level", "error", err) logger.Infoln("Server is starting")
os.Exit(1) bundle, catalog := services.InitI18n(logger, config.Strings("i18n.languages"))
}
slog.SetLogLoggerLevel(logLevel.Level()) if err = services.AddMessages(catalog); err != nil {
logger.Fatalf("could not add messages for i18n: %v", err)
} }
if config.Bool("log.json") { adminURL, err := url.Parse(config.MustString("admin.url"))
logHandler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel}) if err != nil {
logger = slog.New(logHandler) logger.Fatalf("error parsing admin URL: %v", err)
slog.SetDefault(logger)
} }
logger.Info("Starting CAcert OpenID Connect Identity Provider", tlsClientConfig := &tls.Config{MinVersion: tls.VersionTLS12}
"version", version, "commit", commit, "date", date,
)
logger.Info("Server is starting")
i18nService := services.InitI18n(logger, config.Strings("i18n.languages"))
if err = i18nService.AddMessages(); err != nil { if config.Exists("api-client.rootCAs") {
logger.Error("could not add messages for i18n", "error", err) rootCAFile := config.MustString("api-client.rootCAs")
os.Exit(1) caCertPool := x509.NewCertPool()
}
sessionAuthKey, sessionEncKey, csrfKey, err := configureSessionParameters(config) pemBytes, err := os.ReadFile(rootCAFile)
if err != nil { if err != nil {
logger.Error("could not configure session parameters", "error", err) log.Fatalf("could not read CA certificate file: %v", err)
os.Exit(1)
} }
services.InitSessionStore(sessionAuthKey, sessionEncKey) caCertPool.AppendCertsFromPEM(pemBytes)
tlsClientConfig.RootCAs = caCertPool
clientTransport, err := configureAdminClient(config)
if err != nil {
logger.Error("could not configure Hydra admin client", "error", err)
os.Exit(1)
} }
tc := handlers.PopulateTemplateCache() tlsClientTransport := &http.Transport{TLSClientConfig: tlsClientConfig}
httpClient := &http.Client{Transport: tlsClientTransport}
needsAuth := handlers.NewAuthMiddleware(logger, tc, i18nService).NeedsAuth clientTransport := client.NewWithClient(
adminURL.Host,
indexHandler := handlers.NewIndex(logger, tc, i18nService) adminURL.Path,
manageConsentHandler := handlers.NewManageConsent(logger, tc, i18nService, clientTransport.OAuth2API) []string{adminURL.Scheme},
revokeConsentHandler := handlers.NewRevokeConsent(logger, tc, i18nService, clientTransport.OAuth2API) httpClient,
)
adminClient := hydra.New(clientTransport, nil)
loginHandler := handlers.NewLoginHandler(logger, tc, i18nService, clientTransport.OAuth2API) loginHandler := handlers.NewLoginHandler(logger, bundle, catalog, adminClient.Admin)
consentHandler := handlers.NewConsentHandler(logger, tc, i18nService, clientTransport.OAuth2API) consentHandler := handlers.NewConsentHandler(logger, bundle, catalog, adminClient.Admin)
logoutHandler := handlers.NewLogout(logger, clientTransport.OAuth2API) logoutHandler := handlers.NewLogoutHandler(logger, adminClient.Admin)
logoutSuccessHandler := handlers.NewLogoutSuccess(logger, tc, i18nService) logoutSuccessHandler := handlers.NewLogoutSuccessHandler()
errorHandler := handlers.NewErrorHandler(logger, i18nService) errorHandler := handlers.NewErrorHandler()
staticFiles := http.FileServer(http.FS(ui.Static)) staticFiles := http.FileServer(http.FS(ui.Static))
router := http.NewServeMux() router := http.NewServeMux()
router.HandleFunc("/", needsAuth(indexHandler))
router.Handle("/manage-consent", needsAuth(manageConsentHandler))
router.Handle("/revoke-consent/", needsAuth(revokeConsentHandler))
router.Handle("/login", loginHandler) router.Handle("/login", loginHandler)
router.Handle("/consent", consentHandler) router.Handle("/consent", consentHandler)
router.Handle("/logout", logoutHandler) router.Handle("/logout", logoutHandler)
@ -153,6 +133,15 @@ func main() {
router.Handle("/css/", staticFiles) router.Handle("/css/", staticFiles)
router.Handle("/js/", 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 { nextRequestID := func() string {
return fmt.Sprintf("%d", time.Now().UnixNano()) return fmt.Sprintf("%d", time.Now().UnixNano())
} }
@ -166,61 +155,23 @@ func main() {
csrf.SameSite(csrf.SameSiteStrictMode), csrf.SameSite(csrf.SameSiteStrictMode),
csrf.MaxAge(DefaultCSRFMaxAge)) csrf.MaxAge(DefaultCSRFMaxAge))
errorMiddleware, err := handlers.ErrorHandling(logger, tc, i18nService) errorMiddleware, err := handlers.ErrorHandling(
logger,
ui.Templates,
bundle,
catalog,
)
if err != nil { if err != nil {
logger.Error("could not initialize request error handling", "error", err) logger.Fatalf("could not initialize request error handling: %v", err)
os.Exit(1)
} }
handlerChain := tracing(logging(hsts(errorMiddleware(csrfProtect(router))))) handlerChain := tracing(logging(hsts(errorMiddleware(csrfProtect(router)))))
err = startServer(logger, config, handlerChain) startServer(context.Background(), handlerChain, logger, config)
if err != nil {
logger.Error("server start failed", "error", err)
os.Exit(1)
}
} }
func configureAdminClient(config *koanf.Koanf) (*hydra.APIClient, error) { func startServer(ctx context.Context, handlerChain http.Handler, logger *log.Logger, config *koanf.Koanf) {
adminURL, err := url.Parse(config.MustString("admin.url"))
if err != nil {
return nil, fmt.Errorf("error parsing admin URL: %w", err)
}
configuration := hydra.NewConfiguration()
configuration.Servers = []hydra.ServerConfiguration{
{
URL: adminURL.String(),
},
}
tlsClientConfig := &tls.Config{MinVersion: tls.VersionTLS12, ServerName: adminURL.Hostname()}
if config.Exists("api-client.rootCAs") {
rootCAFile := config.MustString("api-client.rootCAs")
caCertPool := x509.NewCertPool()
pemBytes, err := os.ReadFile(rootCAFile)
if err != nil {
return nil, fmt.Errorf("could not read CA certificate file: %w", err)
}
caCertPool.AppendCertsFromPEM(pemBytes)
tlsClientConfig.RootCAs = caCertPool
}
tlsClientTransport := &http.Transport{TLSClientConfig: tlsClientConfig}
c := hydra.NewAPIClient(configuration)
configuration.HTTPClient.Transport = tlsClientTransport
return c, nil
}
func startServer(logger *slog.Logger, config *koanf.Koanf, handlerChain http.Handler) error {
clientCertificateCAFile := config.MustString("security.client.ca-file") clientCertificateCAFile := config.MustString("security.client.ca-file")
serverBindAddress := config.String("server.bind_address")
serverName := config.String("server.name") serverName := config.String("server.name")
serverPort := config.Int("server.port") serverPort := config.Int("server.port")
@ -228,7 +179,7 @@ func startServer(logger *slog.Logger, config *koanf.Koanf, handlerChain http.Han
pemBytes, err := os.ReadFile(clientCertificateCAFile) pemBytes, err := os.ReadFile(clientCertificateCAFile)
if err != nil { if err != nil {
return fmt.Errorf("could not load client CA certificates: %w", err) logger.Fatalf("could not load client CA certificates: %v", err)
} }
clientCertPool.AppendCertsFromPEM(pemBytes) clientCertPool.AppendCertsFromPEM(pemBytes)
@ -240,11 +191,11 @@ func startServer(logger *slog.Logger, config *koanf.Koanf, handlerChain http.Han
ClientCAs: clientCertPool, ClientCAs: clientCertPool,
} }
server := &http.Server{ server := &http.Server{
Addr: fmt.Sprintf("%s:%d", serverBindAddress, serverPort), Addr: fmt.Sprintf("%s:%d", serverName, serverPort),
Handler: handlerChain, Handler: handlerChain,
ReadTimeout: ReadTimeOut, ReadTimeout: TimeoutTwenty,
WriteTimeout: WriteTimeOut, WriteTimeout: TimeoutTwenty,
IdleTimeout: IdleTimeout, IdleTimeout: TimeoutThirty,
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
} }
@ -254,109 +205,30 @@ func startServer(logger *slog.Logger, config *koanf.Koanf, handlerChain http.Han
go func() { go func() {
<-quit <-quit
logger.Info("Server is shutting down...") logger.Infoln("Server is shutting down...")
atomic.StoreInt32(&handlers.Healthy, 0) atomic.StoreInt32(&handlers.Healthy, 0)
ctx, cancel := context.WithTimeout(context.Background(), ShutdownTimeout) ctx, cancel := context.WithTimeout(ctx, TimeoutThirty)
defer cancel() defer cancel()
server.SetKeepAlivesEnabled(false) server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil { if err := server.Shutdown(ctx); err != nil {
logger.Error("could not shutdown server gracefully", "error", err) logger.Fatalf("Could not gracefully shutdown the server: %v\n", err)
} }
close(done) close(done)
}() }()
logger.Info( logger.Infof("Server is ready to handle requests at https://%s/", server.Addr)
"Server is ready to handle requests",
"address", server.Addr, "url", publicAddress(serverName, serverPort),
)
atomic.StoreInt32(&handlers.Healthy, 1) atomic.StoreInt32(&handlers.Healthy, 1)
if err := server.ListenAndServeTLS( if err := server.ListenAndServeTLS(
config.String("server.certificate"), config.String("server.key"), config.String("server.certificate"), config.String("server.key"),
); err != nil && !errors.Is(err, http.ErrServerClosed) { ); err != nil && err != http.ErrServerClosed {
logger.Error("could not listen on configured server address", "server_addr", server.Addr) logger.Fatalf("Could not listen on %s: %v\n", server.Addr, err)
return fmt.Errorf("could not create listener: %w", err)
} }
<-done <-done
logger.Info("Server stopped") logger.Infoln("Server stopped")
return nil
}
func publicAddress(serverName string, serverPort int) string {
if serverPort != httpsDefaultPort {
return fmt.Sprintf("https://%s:%d/", serverName, serverPort)
}
return fmt.Sprintf("https://%s/", serverName)
}
func configureSessionParameters(config *koanf.Koanf) ([]byte, []byte, []byte, error) { //nolint:cyclop
sessionAuthKey, err := base64.StdEncoding.DecodeString(config.String("session.auth-key"))
if err != nil {
return nil, nil, nil, fmt.Errorf("could not decode session auth key: %w", err)
}
sessionEncKey, err := base64.StdEncoding.DecodeString(config.String("session.enc-key"))
if err != nil {
return nil, nil, nil, fmt.Errorf("could not decode session encryption key: %w", err)
}
csrfKey, err := base64.StdEncoding.DecodeString(config.String("security.csrf.key"))
if err != nil {
return nil, nil, nil, fmt.Errorf("could not decode CSRF key bytes: %w", err)
}
generated := false
if len(sessionAuthKey) != sessionAuthKeyLength {
sessionAuthKey, err = services.GenerateKey(sessionAuthKeyLength)
if err != nil {
return nil, nil, nil, fmt.Errorf("could not generate session authentication key: %w", err)
}
generated = true
}
if len(sessionEncKey) != sessionKeyLength {
sessionEncKey, err = services.GenerateKey(sessionKeyLength)
if err != nil {
return nil, nil, nil, fmt.Errorf("could not generate session encryption key: %w", err)
}
generated = true
}
if len(csrfKey) < minCSRFKeyLength {
csrfKey, err = services.GenerateKey(minCSRFKeyLength)
if err != nil {
return nil, nil, nil, fmt.Errorf("could not generate CSRF key: %w", err)
}
generated = true
}
if generated {
_ = config.Load(confmap.Provider(map[string]interface{}{
"session.auth-key": base64.StdEncoding.EncodeToString(sessionAuthKey),
"session.enc-key": base64.StdEncoding.EncodeToString(sessionEncKey),
"security.csrf.key": base64.StdEncoding.EncodeToString(csrfKey),
}, "."), nil)
tomlData, err := config.Marshal(toml.Parser())
if err != nil {
return nil, nil, nil, fmt.Errorf("could not encode session config: %w", err)
}
slog.Info("generated configuration values, put the following into your idp.toml")
fmt.Printf("------\n%s------\n", string(tomlData)) //nolint:forbidigo
}
return sessionAuthKey, sessionEncKey, csrfKey, nil
} }

22
debian/postinst vendored

@ -44,25 +44,3 @@ case "$1" in
fi fi
;; ;;
esac esac
UNIT="cacert-oidc-idp.service"
case "$1" in
'configure' | 'abort-upgrade' | 'abort-deconfigure' | 'abort-remove')
# systemctl daemon-reload
# systemctl --global enable $UNIT
# This will only remove masks created by d-s-h on package removal.
deb-systemd-helper unmask $UNIT >/dev/null || true
# was-enabled defaults to true, so new installations run enable.
if deb-systemd-helper --quiet was-enabled $UNIT ; then
# Enables the unit on first installation, creates new
# symlinks on upgrades if the unit file has changed.
deb-systemd-helper enable $UNIT >/dev/null || true
else
# Update the statefile to add new symlinks (if any), which need to be
# cleaned up on purge. Also remove old symlinks.
deb-systemd-helper update-state $UNIT >/dev/null || true
fi
;;
esac

@ -4,7 +4,7 @@ Description=CAcert OpenID Connect Identity Provider for ORY Hydra
[Service] [Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE
ExecCondition=/bin/sh -c 'test -f /etc/cacert-oidc-idp/idp.toml' ExecCondition=/bin/sh -c 'test -f /etc/cacert-oidc-idp/idp.toml'
ExecStart=/usr/bin/cacert-idp --conf /etc/cacert-oidc-idp/idp.toml ExecStart=/usr/bin/cacert-oidc-idp --config /etc/cacert-oidc-idp/idp.toml
StateDirectory=cacert-oidc-idp StateDirectory=cacert-oidc-idp
User=cacert-oidc-idp User=cacert-oidc-idp
Group=cacert-oidc-idp Group=cacert-oidc-idp

@ -5,10 +5,8 @@ csrf.key = "32-byte-long-random-base64-encoded-key"
client.ca-file = "client.cas.pem" client.ca-file = "client.cas.pem"
[server] [server]
# server name
name = "idp.cacert.org"
# server IP address # server IP address
bind_address = "127.0.0.1" name = "127.0.0.1"
# server port # server port
port = 3443 port = 3443
# server TLS X.509 certificate in PEM format # server TLS X.509 certificate in PEM format

@ -1,38 +1,60 @@
module code.cacert.org/cacert/oidc-idp module code.cacert.org/cacert/oidc_idp
go 1.22 go 1.19
require ( require (
github.com/BurntSushi/toml v1.3.2 github.com/BurntSushi/toml v1.2.1
github.com/dustin/go-humanize v1.0.1 github.com/go-openapi/runtime v0.26.0
github.com/go-playground/form/v4 v4.2.1 github.com/go-playground/form/v4 v4.2.0
github.com/gorilla/csrf v1.7.2 github.com/gorilla/csrf v1.7.1
github.com/gorilla/sessions v1.2.2
github.com/knadh/koanf v1.5.0 github.com/knadh/koanf v1.5.0
github.com/lestrrat-go/jwx v1.2.29 github.com/lestrrat-go/jwx v1.2.25
github.com/nicksnyder/go-i18n/v2 v2.4.0 github.com/nicksnyder/go-i18n/v2 v2.2.1
github.com/ory/hydra-client-go/v2 v2.2.0 github.com/ory/hydra-client-go v1.10.6
github.com/sirupsen/logrus v1.9.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/yuin/goldmark v1.7.1 github.com/yuin/goldmark v1.5.4
golang.org/x/text v0.15.0 golang.org/x/text v0.9.0
) )
require ( require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.20.3 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/loads v0.21.2 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/strfmt v0.21.7 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-openapi/validate v0.22.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/gorilla/securecookie v1.1.2 // 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/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/blackmagic v1.0.1 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/option v1.0.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // 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.9.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
golang.org/x/crypto v0.23.0 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect
golang.org/x/oauth2 v0.20.0 // indirect go.mongodb.org/mongo-driver v1.11.6 // indirect
golang.org/x/sys v0.20.0 // indirect go.opentelemetry.io/otel v1.15.1 // indirect
go.opentelemetry.io/otel/trace v1.15.1 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/sys v0.8.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

250
go.sum

@ -1,8 +1,11 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -13,6 +16,9 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw=
github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ=
@ -34,16 +40,15 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 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/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -53,8 +58,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -63,12 +68,79 @@ github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc=
github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo=
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.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc=
github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g=
github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro=
github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw=
github.com/go-openapi/runtime v0.26.0 h1:HYOFtG00FM1UvqrcxbEJg/SwvDRvYLQKGhw2zaQjTcc=
github.com/go-openapi/runtime v0.26.0/go.mod h1:QgRGeZwrUcSHdeh4Ka9Glvo0ug1LC5WyE+EV88plZrQ=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg=
github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k=
github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU=
github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 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/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/form/v4 v4.2.1 h1:HjdRDKO0fftVMU5epjPW2SOREcZ6/wLUzEobqUGJuPw= github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic=
github.com/go-playground/form/v4 v4.2.1/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U= github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 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/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.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@ -97,22 +169,20 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
@ -151,40 +221,61 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs= github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs=
github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E= github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
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 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/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 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
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.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs= github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs= github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
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.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx v1.2.29 h1:QT0utmUJ4/12rmsVQrJ3u55bycPkKqGYuGT4tyRhxSQ= github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0otA=
github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtXSNFeZuB8= github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
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.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/markbates/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-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@ -207,6 +298,8 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
@ -216,20 +309,27 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk= github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/ory/hydra-client-go/v2 v2.2.0 h1:g8hw0YQD5Us1aAgZj7OyBmBGSDwlnY9/2Pb/pQQq8YE= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/ory/hydra-client-go/v2 v2.2.0/go.mod h1:h0DSI2kQA3S2fN7HyD8DNWcvbgDmYRSxfhwu/mSBhH8= 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.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 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.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -255,20 +355,31 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
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 v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 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/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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.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.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -277,31 +388,50 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
go.mongodb.org/mongo-driver v1.11.6 h1:XM7G6PjiGAO5betLF13BIa5TlLUUE3uJ/2Ox3Lz1K+o=
go.mongodb.org/mongo-driver v1.11.6/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY=
go.opentelemetry.io/otel v1.15.1 h1:3Iwq3lfRByPaws0f6bU3naAqOR1n5IeDWd9390kWHa8=
go.opentelemetry.io/otel v1.15.1/go.mod h1:mHHGEHVDLal6YrKMmk9LqC4a3sF5g+fHfrttQIB1NTc=
go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
go.opentelemetry.io/otel/trace v1.15.1 h1:uXLo6iHJEzDfrNC0L0mNjItIp06SyaBQxu5t3xMlngY=
go.opentelemetry.io/otel/trace v1.15.1/go.mod h1:IWdQG/5N1x7f6YUlmdLeJvH9yxtuJAfc4VW5Agv9r/8=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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-20190308221718-c2843e01d9a2/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-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -312,7 +442,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -330,26 +459,23 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-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-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-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-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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/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-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-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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -359,7 +485,9 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/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-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-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-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -378,23 +506,19 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -402,17 +526,19 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20180917221912-90fa682c2a6e/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-20190114222345-bf090417da8b/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-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-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-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-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@ -420,7 +546,6 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -455,7 +580,12 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= 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 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/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.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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@ -467,6 +597,8 @@ 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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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-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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -1,187 +0,0 @@
/*
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
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
https://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"
"fmt"
"html/template"
"log/slog"
"net/http"
"github.com/dustin/go-humanize"
"github.com/gorilla/sessions"
"github.com/nicksnyder/go-i18n/v2/i18n"
"code.cacert.org/cacert/oidc-idp/ui"
"code.cacert.org/cacert/oidc-idp/internal/services"
)
const sessionName = "idp_session"
func GetSession(r *http.Request) (*sessions.Session, error) {
session, err := services.GetSessionStore().Get(r, sessionName)
if err != nil {
return nil, fmt.Errorf("could not get session")
}
return session, nil
}
type AuthMiddleware struct {
logger *slog.Logger
trans *services.I18NService
templates TemplateCache
}
func getLocalizer(trans *services.I18NService, r *http.Request) *i18n.Localizer {
accept := r.Header.Get("Accept-Language")
return trans.Localizer(accept)
}
type authContextKey int
const ctxKeyAddresses authContextKey = iota
func (a *AuthMiddleware) NeedsAuth(handler http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
_, addresses := getDataFromClientCert(a.logger, r)
if len(addresses) < 1 {
renderNoEmailsInClientCertificate(a.logger, a.templates, a.trans, w, getLocalizer(a.trans, r))
return
}
handler.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ctxKeyAddresses, addresses)))
}
}
func GetAuthenticatedAddresses(r *http.Request) []string {
if addresses, ok := r.Context().Value(ctxKeyAddresses).([]string); ok {
return addresses
}
return nil
}
func NewAuthMiddleware(logger *slog.Logger, tc TemplateCache, trans *services.I18NService) *AuthMiddleware {
return &AuthMiddleware{logger: logger, trans: trans, templates: tc}
}
type templateName string
const (
CertificateLogin templateName = "cert"
ConfirmRevoke templateName = "confirm_revoke"
Consent templateName = "consent"
Error templateName = "error"
Index templateName = "index"
LogoutSuccessful templateName = "logout_successful"
ManageConsent templateName = "manage_consent"
NoChallengeInRequest templateName = "no_challenge"
NoEmailsInClientCertificate templateName = "no_emails"
)
type TemplateCache map[templateName]*template.Template
func (c TemplateCache) render(
logger *slog.Logger, w http.ResponseWriter, name templateName, params map[string]interface{},
) {
rendered := bytes.NewBuffer(make([]byte, 0))
err := c[name].Lookup("base").Execute(rendered, params)
if err != nil {
logger.Error("template rendering failed", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
_, _ = w.Write(rendered.Bytes())
}
func PopulateTemplateCache() TemplateCache {
funcMap := map[string]any{"humantime": humanize.Time}
cache := TemplateCache{
CertificateLogin: template.Must(template.New("").Funcs(funcMap).ParseFS(
ui.Templates,
"templates/base.gohtml", "templates/client_certificate.gohtml",
)),
ConfirmRevoke: template.Must(template.New("").Funcs(funcMap).ParseFS(
ui.Templates,
"templates/base.gohtml", "templates/confirm_revoke.gohtml",
)),
NoEmailsInClientCertificate: template.Must(template.New("").Funcs(funcMap).ParseFS(
ui.Templates,
"templates/base.gohtml", "templates/no_email_in_client_certificate.gohtml",
)),
NoChallengeInRequest: template.Must(template.New("").Funcs(funcMap).ParseFS(
ui.Templates,
"templates/base.gohtml", "templates/no_challenge_in_request.gohtml",
)),
Consent: template.Must(template.New("").Funcs(funcMap).ParseFS(
ui.Templates,
"templates/base.gohtml", "templates/consent.gohtml",
)),
LogoutSuccessful: template.Must(template.New("").Funcs(funcMap).ParseFS(
ui.Templates,
"templates/base.gohtml", "templates/logout_successful.gohtml",
)),
Index: template.Must(template.New("").Funcs(funcMap).ParseFS(
ui.Templates,
"templates/base.gohtml", "templates/index.gohtml",
)),
ManageConsent: template.Must(template.New("").Funcs(funcMap).ParseFS(
ui.Templates,
"templates/base.gohtml", "templates/manage_consent.gohtml",
)),
Error: template.Must(template.ParseFS(
ui.Templates,
"templates/base.gohtml",
"templates/errors.gohtml",
)),
}
return cache
}
func renderNoEmailsInClientCertificate(
logger *slog.Logger,
templates TemplateCache,
trans *services.I18NService,
w http.ResponseWriter,
localizer *i18n.Localizer,
) {
msg := trans.LookupMessage
err := templates[NoEmailsInClientCertificate].Lookup("base").Execute(w, map[string]interface{}{
"Title": msg("NoEmailsInClientCertificateTitle", nil, localizer),
"Explanation": msg("NoEmailsInClientCertificateExplanation", nil, localizer),
})
if err != nil {
logger.Error("template rendering failed", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
}

@ -1,5 +1,5 @@
/* /*
Copyright CAcert Inc. Copyright 2020-2023 CAcert Inc.
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -22,7 +22,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"html/template" "html/template"
"log/slog"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@ -30,26 +29,30 @@ import (
"github.com/go-playground/form/v4" "github.com/go-playground/form/v4"
"github.com/gorilla/csrf" "github.com/gorilla/csrf"
"github.com/gorilla/sessions"
"github.com/lestrrat-go/jwx/jwt/openid" "github.com/lestrrat-go/jwx/jwt/openid"
"github.com/nicksnyder/go-i18n/v2/i18n" "github.com/nicksnyder/go-i18n/v2/i18n"
client "github.com/ory/hydra-client-go/v2" "github.com/ory/hydra-client-go/client/admin"
"github.com/ory/hydra-client-go/models"
log "github.com/sirupsen/logrus"
"code.cacert.org/cacert/oidc-idp/internal/models" commonModels "code.cacert.org/cacert/oidc_idp/internal/models"
"code.cacert.org/cacert/oidc-idp/internal/services" "code.cacert.org/cacert/oidc_idp/ui"
"code.cacert.org/cacert/oidc_idp/internal/services"
) )
type ConsentHandler struct { type ConsentHandler struct {
logger *slog.Logger adminClient admin.ClientService
trans *services.I18NService bundle *i18n.Bundle
adminClient client.OAuth2API consentTemplate *template.Template
templates TemplateCache logger *log.Logger
messageCatalog *services.MessageCatalog
} }
type ConsentInformation struct { type ConsentInformation struct {
GrantedScopes []string `form:"scope"` GrantedScopes []string `form:"scope"`
SelectedClaims []string `form:"claims"` SelectedClaims []string `form:"claims"`
ConsentAction string `form:"consent"` ConsentChecked bool `form:"consent"`
} }
type UserInfo struct { type UserInfo struct {
@ -68,6 +71,8 @@ const (
ScopeEmail = "email" ScopeEmail = "email"
) )
const OneDayInSeconds = 86400
func init() { func init() {
supportedScopes = make(map[string]*i18n.Message) supportedScopes = make(map[string]*i18n.Message)
supportedScopes[ScopeOpenID] = &i18n.Message{ supportedScopes[ScopeOpenID] = &i18n.Message{
@ -108,9 +113,10 @@ func (i *UserInfo) GetFullName() string {
func (h *ConsentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *ConsentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
challenge := r.URL.Query().Get("consent_challenge") challenge := r.URL.Query().Get("consent_challenge")
h.logger.Debug("received consent challenge", "consent_challenge", challenge) h.logger.Debugf("received consent challenge %s", challenge)
localizer := getLocalizer(h.trans, r) accept := r.Header.Get("Accept-Language")
localizer := i18n.NewLocalizer(h.bundle, accept)
// retrieve consent information // retrieve consent information
consentData, requestedClaims, err := h.getRequestedConsentInformation(challenge, r) consentData, requestedClaims, err := h.getRequestedConsentInformation(challenge, r)
@ -119,195 +125,82 @@ func (h *ConsentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
session, err := GetSession(r)
if err != nil {
h.logger.Error("could get session for request", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
switch r.Method { switch r.Method {
case http.MethodGet: case http.MethodGet:
h.handleGet(w, r, consentData, requestedClaims, session, challenge, localizer) if err := h.renderConsentForm(w, r, consentData, requestedClaims, localizer); err != nil {
case http.MethodPost: h.logger.Error(err)
h.handlePost(w, r, consentData, requestedClaims, session, challenge)
}
}
func (h *ConsentHandler) handleGet(
w http.ResponseWriter,
r *http.Request,
consentData *client.OAuth2ConsentRequest,
requestedClaims *models.OIDCClaimsRequest,
session *sessions.Session,
challenge string,
localizer *i18n.Localizer,
) {
// Hydra has a previous session for this user and client
if consentData.GetSkip() {
consentRequest, err := h.handleExistingConsent(consentData, requestedClaims, session)
if err != nil {
h.logger.Error("could not handle existing consent", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
err = h.acceptConsent(w, r, challenge, consentRequest)
if err != nil {
h.logger.Error("could not accept consent", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
return return
} }
case http.MethodPost:
h.renderConsentForm(w, r, consentData, requestedClaims, localizer)
}
func (h *ConsentHandler) handlePost(
w http.ResponseWriter,
r *http.Request,
consentData *client.OAuth2ConsentRequest,
requestedClaims *models.OIDCClaimsRequest,
session *sessions.Session,
challenge string,
) {
var consentInfo ConsentInformation var consentInfo ConsentInformation
// validate input // validate input
decoder := form.NewDecoder() decoder := form.NewDecoder()
if err := decoder.Decode(&consentInfo, r.Form); err != nil { if err := decoder.Decode(&consentInfo, r.Form); err != nil {
h.logger.Error("could not decode consent form", "error", err) h.logger.Error(err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
if consentInfo.ConsentAction == "consent" { if consentInfo.ConsentChecked {
consentRequest, err := h.rememberNewConsent(consentData, consentInfo, requestedClaims, session) sessionData, err := h.getSessionData(r, consentInfo, requestedClaims, consentData.Payload)
if err != nil {
h.logger.Error("could not accept consent", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
err = h.acceptConsent(w, r, challenge, consentRequest)
if err != nil { if err != nil {
h.logger.Error("could not accept consent", "error", err) h.logger.Errorf("could not get session data: %v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
return return
} }
consentRequest, response, err := h.adminClient.RejectOAuth2ConsentRequest( consentRequest, err := h.adminClient.AcceptConsentRequest(
r.Context(), admin.NewAcceptConsentRequestParams().WithConsentChallenge(challenge).WithBody(
).ConsentChallenge(challenge).Execute() &models.AcceptConsentRequest{
GrantAccessTokenAudience: nil,
GrantScope: consentInfo.GrantedScopes,
HandledAt: models.NullTime(time.Now()),
Remember: true,
RememberFor: OneDayInSeconds,
Session: sessionData,
}).WithTimeout(TimeoutTen))
if err != nil { if err != nil {
h.logger.Error("reject consent request failed", "error", err) h.logger.Error(err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
defer func() { _ = response.Body.Close() }() w.Header().Add("Location", *consentRequest.GetPayload().RedirectTo)
h.logger.Debug(
"received response for RejectOAuth2ConsentRequest",
"response", response.Status, "reject_consent_request", consentRequest,
)
w.Header().Add("Location", consentRequest.GetRedirectTo())
w.WriteHeader(http.StatusFound) w.WriteHeader(http.StatusFound)
}
func (h *ConsentHandler) rememberNewConsent( return
consentData *client.OAuth2ConsentRequest,
consentInfo ConsentInformation,
requestedClaims *models.OIDCClaimsRequest,
session *sessions.Session,
) (*client.AcceptOAuth2ConsentRequest, error) {
sessionData, err := h.getSessionData(
consentData.GetRequestedScope(),
consentInfo.GrantedScopes,
consentInfo.SelectedClaims,
requestedClaims,
session,
)
if err != nil {
return nil, fmt.Errorf("could not get session data: %w", err)
} }
request := client.NewAcceptOAuth2ConsentRequestWithDefaults() consentRequest, err := h.adminClient.RejectConsentRequest(
request.SetRemember(true) admin.NewRejectConsentRequestParams().WithConsentChallenge(challenge).WithBody(
request.SetHandledAt(time.Now()) &models.RejectRequest{}))
request.SetGrantAccessTokenAudience(consentData.RequestedAccessTokenAudience)
request.SetGrantScope(consentInfo.GrantedScopes)
request.SetRememberFor(0)
request.SetSession(*sessionData)
return request, nil
}
func (h *ConsentHandler) acceptConsent(
w http.ResponseWriter, r *http.Request, challenge string, request *client.AcceptOAuth2ConsentRequest,
) error {
oAuth2RedirectTo, response, err := h.adminClient.AcceptOAuth2ConsentRequest(
r.Context(),
).ConsentChallenge(challenge).AcceptOAuth2ConsentRequest(*request).Execute()
if err != nil { if err != nil {
return fmt.Errorf("accept consent request failed: %w", err) h.logger.Error(err)
} http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
defer func() { _ = response.Body.Close() }()
h.logger.Debug( return
"received response for AcceptOAuth2ConsentRequest", }
"response", response.Status, "redirect_to", oAuth2RedirectTo,
)
w.Header().Add("Location", oAuth2RedirectTo.GetRedirectTo()) w.Header().Add("Location", *consentRequest.GetPayload().RedirectTo)
w.WriteHeader(http.StatusFound) w.WriteHeader(http.StatusFound)
return nil
}
func (h *ConsentHandler) handleExistingConsent(
data *client.OAuth2ConsentRequest,
claims *models.OIDCClaimsRequest,
session *sessions.Session,
) (*client.AcceptOAuth2ConsentRequest, error) {
sessionData, err := h.getSessionData(data.GetRequestedScope(),
data.GetRequestedScope(), []string{}, claims, session)
if err != nil {
return nil, fmt.Errorf("could not get session data: %w", err)
} }
request := client.NewAcceptOAuth2ConsentRequestWithDefaults()
request.SetGrantScope(data.RequestedScope)
request.SetHandledAt(time.Now())
request.SetGrantAccessTokenAudience(data.RequestedAccessTokenAudience)
request.SetSession(*sessionData)
return request, nil
} }
func (h *ConsentHandler) getRequestedConsentInformation(challenge string, r *http.Request) ( func (h *ConsentHandler) getRequestedConsentInformation(challenge string, r *http.Request) (
*client.OAuth2ConsentRequest, *admin.GetConsentRequestOK,
*models.OIDCClaimsRequest, *commonModels.OIDCClaimsRequest,
error, error,
) { ) {
consentRequest, response, err := h.adminClient.GetOAuth2ConsentRequest( consentData, err := h.adminClient.GetConsentRequest(
r.Context(), admin.NewGetConsentRequestParams().WithConsentChallenge(challenge))
).ConsentChallenge(challenge).Execute()
if err != nil { if err != nil {
h.logger.Error("error getting consent information", "error", err) h.logger.Errorf("error getting consent information: %v", err)
if errorBucket := GetErrorBucket(r); errorBucket != nil { if errorBucket := GetErrorBucket(r); errorBucket != nil {
errorDetails := &ErrorDetails{ errorDetails := &ErrorDetails{
@ -321,85 +214,73 @@ func (h *ConsentHandler) getRequestedConsentInformation(challenge string, r *htt
return nil, nil, fmt.Errorf("error getting consent information: %w", err) return nil, nil, fmt.Errorf("error getting consent information: %w", err)
} }
defer func() { _ = response.Body.Close() }() var requestedClaims commonModels.OIDCClaimsRequest
h.logger.Debug(
"response for GetOAuth2ConsentRequest",
"response", response.Status, "consent_request", consentRequest,
)
var requestedClaims models.OIDCClaimsRequest
requestURLStr := consentRequest.GetRequestUrl() requestURL, err := url.Parse(consentData.Payload.RequestURL)
requestURL, err := url.Parse(requestURLStr)
if err != nil { if err != nil {
h.logger.Warn( h.logger.Warnf("could not parse original request URL %s: %v", consentData.Payload.RequestURL, err)
"could not parse original request URL",
"error", err, "request_url", requestURLStr,
)
} else { } else {
claimsParameter := requestURL.Query().Get("claims") claimsParameter := requestURL.Query().Get("claims")
if claimsParameter != "" { if claimsParameter != "" {
decoder := json.NewDecoder(strings.NewReader(claimsParameter)) decoder := json.NewDecoder(strings.NewReader(claimsParameter))
err := decoder.Decode(&requestedClaims) err := decoder.Decode(&requestedClaims)
if err != nil { if err != nil {
h.logger.Warn( h.logger.Warnf(
"ignoring claims request parameter that could not be decoded", "ignoring claims request parameter %s that could not be decoded: %v",
"error", err, "claims_parameter", claimsParameter, claimsParameter,
err,
) )
} }
} }
} }
return consentRequest, &requestedClaims, nil return consentData, &requestedClaims, nil
} }
func (h *ConsentHandler) renderConsentForm( func (h *ConsentHandler) renderConsentForm(
w http.ResponseWriter, w http.ResponseWriter,
r *http.Request, r *http.Request,
consentRequest *client.OAuth2ConsentRequest, consentData *admin.GetConsentRequestOK,
claims *models.OIDCClaimsRequest, claims *commonModels.OIDCClaimsRequest,
localizer *i18n.Localizer, localizer *i18n.Localizer,
) { ) error {
trans := h.trans.LookupMessage trans := func(id string, values ...map[string]interface{}) string {
transMarkdown := func(id string, params map[string]interface{}, localizer *i18n.Localizer) template.HTML { if len(values) > 0 {
return template.HTML( //nolint:gosec return h.messageCatalog.LookupMessage(id, values[0], localizer)
h.trans.LookupMarkdownMessage(id, params, localizer),
)
} }
// render consent form return h.messageCatalog.LookupMessage(id, nil, localizer)
oAuth2Client := consentRequest.Client }
clientLogoURI := oAuth2Client.GetLogoUri()
clientName := template.HTMLEscaper(oAuth2Client.GetClientName())
clientURI := oAuth2Client.GetClientUri()
h.templates.render(h.logger, w, Consent, map[string]interface{}{ // render consent form
"Title": trans("TitleRequestConsent", nil, localizer), client := consentData.GetPayload().Client
err := h.consentTemplate.Lookup("base").Execute(w, map[string]interface{}{
"Title": trans("TitleRequestConsent"),
csrf.TemplateTag: csrf.TemplateField(r), csrf.TemplateTag: csrf.TemplateField(r),
"errors": map[string]string{}, "errors": map[string]string{},
"LogoURI": clientLogoURI, "client": client,
"ClientName": clientName, "requestedScope": h.mapRequestedScope(consentData.GetPayload().RequestedScope, localizer),
"requestedScope": h.mapRequestedScope(consentRequest.RequestedScope, localizer),
"requestedClaims": h.mapRequestedClaims(claims, localizer), "requestedClaims": h.mapRequestedClaims(claims, localizer),
"ButtonTitleConsent": trans("ButtonTitleConsent", nil, localizer), "LabelSubmit": trans("LabelSubmit"),
"ButtonTitleDeny": trans("ButtonTitleDeny", nil, localizer), "LabelConsent": trans("LabelConsent"),
"HasMoreInformation": clientURI != "", "IntroMoreInformation": template.HTML( //nolint:gosec
"IntroMoreInformation": transMarkdown( trans("IntroConsentMoreInformation", map[string]interface{}{
"IntroConsentMoreInformation", map[string]interface{}{ "client": client.ClientName,
"client": clientName, "clientLink": client.ClientURI,
"clientLink": clientURI, })),
}, localizer), "ClaimsInformation": template.HTML( //nolint:gosec
"LabelConsent": transMarkdown("LabelConsent", nil, localizer), trans("ClaimsInformation", nil)),
"ClaimsInformation": transMarkdown( "IntroConsentRequested": template.HTML( //nolint:gosec
"ClaimsInformation", nil, localizer), trans("IntroConsentRequested", map[string]interface{}{
"IntroConsentRequested": transMarkdown( "client": client.ClientName,
"IntroConsentRequested", map[string]interface{}{ })),
"client": clientName,
}, localizer),
}) })
if err != nil {
return fmt.Errorf("rendering failed: %w", err)
}
return nil
} }
type scopeWithLabel struct { type scopeWithLabel struct {
@ -408,14 +289,14 @@ type scopeWithLabel struct {
} }
func (h *ConsentHandler) mapRequestedScope( func (h *ConsentHandler) mapRequestedScope(
scope []string, scope models.StringSlicePipeDelimiter,
localizer *i18n.Localizer, localizer *i18n.Localizer,
) []*scopeWithLabel { ) []*scopeWithLabel {
result := make([]*scopeWithLabel, 0) result := make([]*scopeWithLabel, 0)
for _, scopeName := range scope { for _, scopeName := range scope {
if _, ok := supportedScopes[scopeName]; !ok { if _, ok := supportedScopes[scopeName]; !ok {
h.logger.Warn("ignoring unsupported scope", "scope", scopeName) h.logger.Warnf("unsupported scope %s ignored", scopeName)
continue continue
} }
@ -424,7 +305,7 @@ func (h *ConsentHandler) mapRequestedScope(
DefaultMessage: supportedScopes[scopeName], DefaultMessage: supportedScopes[scopeName],
}) })
if err != nil { if err != nil {
h.logger.Warn("could not localize scope label", "error", err, "scope", scopeName) h.logger.Warnf("could not localize label for scope %s: %v", scopeName, err)
label = scopeName label = scopeName
} }
@ -441,23 +322,17 @@ type claimWithLabel struct {
} }
func (h *ConsentHandler) mapRequestedClaims( func (h *ConsentHandler) mapRequestedClaims(
claims *models.OIDCClaimsRequest, claims *commonModels.OIDCClaimsRequest,
localizer *i18n.Localizer, localizer *i18n.Localizer,
) []*claimWithLabel { ) []*claimWithLabel {
result := make([]*claimWithLabel, 0) result := make([]*claimWithLabel, 0)
known := make(map[string]bool) known := make(map[string]bool)
for _, claimElement := range []*models.ClaimElement{claims.GetUserInfo(), claims.GetIDToken()} { for _, claimElement := range []*commonModels.ClaimElement{claims.GetUserInfo(), claims.GetIDToken()} {
if claimElement != nil { if claimElement != nil {
for k, v := range *claimElement { for k, v := range *claimElement {
if v == nil {
h.logger.Warn("claim element is nil", "key", k)
continue
}
if _, ok := supportedClaims[k]; !ok { if _, ok := supportedClaims[k]; !ok {
h.logger.Warn("ignoring unsupported claim", "claim", k) h.logger.Warnf("unsupported claim %s ignored", k)
continue continue
} }
@ -466,7 +341,7 @@ func (h *ConsentHandler) mapRequestedClaims(
DefaultMessage: supportedClaims[k], DefaultMessage: supportedClaims[k],
}) })
if err != nil { if err != nil {
h.logger.Warn("could not localize claim label", "error", err, "claim", k) h.logger.Warnf("could not localize label for claim %s: %v", k, err)
label = k label = k
} }
@ -486,43 +361,41 @@ func (h *ConsentHandler) mapRequestedClaims(
} }
func (h *ConsentHandler) getSessionData( func (h *ConsentHandler) getSessionData(
requestedScopes, r *http.Request,
grantedScopes, info ConsentInformation,
selectedClaims []string, claims *commonModels.OIDCClaimsRequest,
claims *models.OIDCClaimsRequest, payload *models.ConsentRequest,
session *sessions.Session, ) (*models.ConsentRequestSession, error) {
) (*client.AcceptOAuth2ConsentRequestSession, error) { idTokenData := make(map[string]interface{}, 0)
idTokenData := make(map[string]interface{}) accessTokenData := make(map[string]interface{}, 0)
if err := h.fillTokenData( userInfo := h.GetUserInfoFromClientCertificate(r, payload.Subject)
idTokenData,
requestedScopes, if err := h.fillTokenData(accessTokenData, payload.RequestedScope, claims, info, userInfo); err != nil {
grantedScopes,
claims,
selectedClaims,
session.Values,
); err != nil {
return nil, err return nil, err
} }
consentSession := client.NewAcceptOAuth2ConsentRequestSession() if err := h.fillTokenData(idTokenData, payload.RequestedScope, claims, info, userInfo); err != nil {
consentSession.SetIdToken(idTokenData) return nil, err
}
return consentSession, nil return &models.ConsentRequestSession{
AccessToken: accessTokenData,
IDToken: idTokenData,
}, nil
} }
func (h *ConsentHandler) fillTokenData( func (h *ConsentHandler) fillTokenData(
m map[string]interface{}, m map[string]interface{},
requestedScopes []string, requestedScope models.StringSlicePipeDelimiter,
grantedScopes []string, claimsRequest *commonModels.OIDCClaimsRequest,
claimsRequest *models.OIDCClaimsRequest, consentInformation ConsentInformation,
selectedClaims []string, userInfo *UserInfo,
sessionData map[interface{}]interface{},
) error { ) error {
for _, scope := range requestedScopes { for _, scope := range requestedScope {
granted := false granted := false
for _, k := range grantedScopes { for _, k := range consentInformation.GrantedScopes {
if k == scope { if k == scope {
granted = true granted = true
@ -539,8 +412,8 @@ func (h *ConsentHandler) fillTokenData(
// email // email
// OPTIONAL. This scope value requests access to the email and // OPTIONAL. This scope value requests access to the email and
// email_verified Claims. // email_verified Claims.
m[openid.EmailKey] = sessionData[services.SessionEmail] m[openid.EmailKey] = userInfo.Email
m[openid.EmailVerifiedKey] = true m[openid.EmailVerifiedKey] = userInfo.EmailVerified
case ScopeProfile: case ScopeProfile:
// profile // profile
// OPTIONAL. This scope value requests access to the // OPTIONAL. This scope value requests access to the
@ -548,12 +421,12 @@ func (h *ConsentHandler) fillTokenData(
// family_name, given_name, middle_name, nickname, // family_name, given_name, middle_name, nickname,
// preferred_username, profile, picture, website, gender, // preferred_username, profile, picture, website, gender,
// birthdate, zoneinfo, locale, and updated_at. // birthdate, zoneinfo, locale, and updated_at.
m[openid.NameKey] = sessionData[services.SessionFullName] m[openid.NameKey] = userInfo.GetFullName()
} }
} }
if userInfoClaims := claimsRequest.GetUserInfo(); userInfoClaims != nil { if userInfoClaims := claimsRequest.GetUserInfo(); userInfoClaims != nil {
err := h.parseUserInfoClaims(m, userInfoClaims, selectedClaims) err := h.parseUserInfoClaims(m, userInfoClaims, consentInformation)
if err != nil { if err != nil {
return err return err
} }
@ -564,13 +437,13 @@ func (h *ConsentHandler) fillTokenData(
func (h *ConsentHandler) parseUserInfoClaims( func (h *ConsentHandler) parseUserInfoClaims(
m map[string]interface{}, m map[string]interface{},
userInfoClaims *models.ClaimElement, userInfoClaims *commonModels.ClaimElement,
selectedClaims []string, consentInformation ConsentInformation,
) error { ) error {
for claimName, claim := range *userInfoClaims { for claimName, claim := range *userInfoClaims {
granted := false granted := false
for _, k := range selectedClaims { for _, k := range consentInformation.SelectedClaims {
if k == claimName { if k == claimName {
granted = true granted = true
@ -584,7 +457,7 @@ func (h *ConsentHandler) parseUserInfoClaims(
wantedValue, err := claim.WantedValue() wantedValue, err := claim.WantedValue()
if err != nil { if err != nil {
if !errors.Is(err, models.ErrNoValue) { if !errors.Is(err, commonModels.ErrNoValue) {
return fmt.Errorf("error handling claim: %w", err) return fmt.Errorf("error handling claim: %w", err)
} }
} }
@ -596,9 +469,48 @@ func (h *ConsentHandler) parseUserInfoClaims(
} }
if claim.IsEssential() { if claim.IsEssential() {
h.logger.Warn("handling for essential claim not implemented", "claim", claimName) h.logger.Warnf(
"handling for essential claim name %s not implemented",
claimName,
)
} else { } else {
h.logger.Warn("handling for claim not implemented", "claim", claimName) h.logger.Warnf(
"handling for claim name %s not implemented",
claimName,
)
}
}
return nil
}
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,
} }
} }
@ -606,12 +518,23 @@ func (h *ConsentHandler) parseUserInfoClaims(
} }
func NewConsentHandler( func NewConsentHandler(
logger *slog.Logger, templateCache TemplateCache, trans *services.I18NService, adminClient client.OAuth2API, logger *log.Logger,
bundle *i18n.Bundle,
messageCatalog *services.MessageCatalog,
adminClient admin.ClientService,
) *ConsentHandler { ) *ConsentHandler {
consentTemplate := template.Must(
template.ParseFS(
ui.Templates,
"templates/base.gohtml",
"templates/consent.gohtml",
))
return &ConsentHandler{ return &ConsentHandler{
logger: logger,
trans: trans,
adminClient: adminClient, adminClient: adminClient,
templates: templateCache, bundle: bundle,
consentTemplate: consentTemplate,
logger: logger,
messageCatalog: messageCatalog,
} }
} }

@ -1,5 +1,5 @@
/* /*
Copyright CAcert Inc. Copyright 2020-2023 CAcert Inc.
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");

@ -1,5 +1,5 @@
/* /*
Copyright CAcert Inc. Copyright 2020-2023 CAcert Inc.
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -18,15 +18,16 @@ limitations under the License.
package handlers package handlers
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"html/template" "html/template"
"log/slog" "io/fs"
"net/http" "net/http"
"code.cacert.org/cacert/oidc-idp/internal/services" "github.com/nicksnyder/go-i18n/v2/i18n"
"code.cacert.org/cacert/oidc-idp/ui" log "github.com/sirupsen/logrus"
"code.cacert.org/cacert/oidc_idp/internal/services"
) )
type errorKey int type errorKey int
@ -43,24 +44,34 @@ type ErrorDetails struct {
} }
type ErrorBucket struct { type ErrorBucket struct {
logger *slog.Logger
trans *services.I18NService
errorDetails *ErrorDetails errorDetails *ErrorDetails
templates TemplateCache templates *template.Template
logger *log.Logger
bundle *i18n.Bundle
messageCatalog *services.MessageCatalog
} }
func (b *ErrorBucket) serveHTTP(w http.ResponseWriter, r *http.Request) { func (b *ErrorBucket) serveHTTP(w http.ResponseWriter, r *http.Request) {
if b.errorDetails != nil { if b.errorDetails != nil {
localizer := getLocalizer(b.trans, r) accept := r.Header.Get("Accept-Language")
localizer := i18n.NewLocalizer(b.bundle, accept)
b.templates.render(b.logger, w, Error, map[string]interface{}{ err := b.templates.Lookup("base").Execute(w, map[string]interface{}{
"Title": b.trans.LookupMessage( "Title": b.messageCatalog.LookupMessage(
"ErrorTitle", "ErrorTitle",
nil, nil,
localizer, localizer,
), ),
"details": b.errorDetails, "details": b.errorDetails,
}) })
if err != nil {
log.Errorf("error rendering error template: %v", err)
http.Error(
w,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError,
)
}
} }
} }
@ -89,7 +100,9 @@ func (w *errorResponseWriter) WriteHeader(code int) {
if code >= http.StatusBadRequest { if code >= http.StatusBadRequest {
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.errorBucket.AddError(&ErrorDetails{ErrorCode: "HTTP error"}) w.errorBucket.AddError(&ErrorDetails{
ErrorMessage: http.StatusText(code),
})
} }
w.ResponseWriter.WriteHeader(code) w.ResponseWriter.WriteHeader(code)
@ -117,14 +130,27 @@ func (w *errorResponseWriter) Write(content []byte) (int, error) {
} }
func ErrorHandling( func ErrorHandling(
logger *slog.Logger, templateCache TemplateCache, trans *services.I18NService, logger *log.Logger,
templateFS fs.FS,
bundle *i18n.Bundle,
messageCatalog *services.MessageCatalog,
) (func(http.Handler) http.Handler, error) { ) (func(http.Handler) http.Handler, error) {
errorTemplates, err := template.ParseFS(
templateFS,
"templates/base.gohtml",
"templates/errors.gohtml",
)
if err != nil {
return nil, fmt.Errorf("could not parse templates: %w", err)
}
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
errorBucket := &ErrorBucket{ errorBucket := &ErrorBucket{
templates: errorTemplates,
logger: logger, logger: logger,
trans: trans, bundle: bundle,
templates: templateCache, messageCatalog: messageCatalog,
} }
next.ServeHTTP( next.ServeHTTP(
&errorResponseWriter{w, errorBucket, http.StatusOK}, &errorResponseWriter{w, errorBucket, http.StatusOK},
@ -136,61 +162,15 @@ func ErrorHandling(
} }
type ErrorHandler struct { type ErrorHandler struct {
logger *slog.Logger
trans *services.I18NService
template *template.Template
} }
func (h *ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (e *ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet { w.Header().Set("Content-Type", "text/plain; charset=utf-8")
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) _, _ = fmt.Fprintf(w, `
didumm %#v
return `, r.URL.Query())
}
localizer := getLocalizer(h.trans, r)
errorName := r.URL.Query().Get("error")
errorDescription := r.URL.Query().Get("error_description")
h.logger.Debug(
"error from Hydra",
"error_name", errorName, "error_description", errorDescription,
)
rendered := bytes.NewBuffer(make([]byte, 0))
msg := h.trans.LookupMessage
msgMarkdown := h.trans.LookupMarkdownMessage
err := h.template.Lookup("base").Execute(rendered, map[string]interface{}{
"Title": msg("AuthServerErrorTitle", nil, localizer),
"Explanation": template.HTML( //nolint:gosec
msgMarkdown("AuthServerErrorExplanation", nil, localizer),
),
"ErrorMessage": errorDescription,
})
if err != nil {
h.logger.Error("template rendering failed", "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 NewErrorHandler(logger *slog.Logger, trans *services.I18NService) *ErrorHandler { func NewErrorHandler() *ErrorHandler {
return &ErrorHandler{ return &ErrorHandler{}
logger: logger,
trans: trans,
template: template.Must(template.ParseFS(
ui.Templates,
"templates/base.gohtml",
"templates/hydra_error.gohtml"),
),
}
} }

@ -1,5 +1,5 @@
/* /*
Copyright CAcert Inc. Copyright 2020-2023 CAcert Inc.
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -19,40 +19,49 @@ package handlers
import ( import (
"bytes" "bytes"
"context" "crypto/x509"
"errors"
"fmt" "fmt"
"html/template" "html/template"
"io"
"log/slog"
"net/http" "net/http"
"strconv"
"time"
"github.com/gorilla/csrf" "github.com/gorilla/csrf"
"github.com/nicksnyder/go-i18n/v2/i18n" "github.com/nicksnyder/go-i18n/v2/i18n"
client "github.com/ory/hydra-client-go/v2" "github.com/ory/hydra-client-go/client/admin"
"github.com/ory/hydra-client-go/models"
log "github.com/sirupsen/logrus"
"code.cacert.org/cacert/oidc-idp/internal/services" "code.cacert.org/cacert/oidc_idp/ui"
"code.cacert.org/cacert/oidc_idp/internal/services"
) )
type acrType string type acrType string
const ( const (
// ClientCertificate represents a client certificate login ClientCertificate acrType = "cert" // client certificate login
ClientCertificate acrType = "urn:cacert:1fa:cert" // ClientCertificateOTP acrType = "cert+otp"
// ClientCertificateOTP acrType = "urn:cacert:2fa:cert+otp" // ClientCertificateToken acrType = "cert+token"
// ClientCertificateToken acrType = "urn:cacert:2fa:cert+token"
) )
type contextKey int type templateName string
const ( const (
ctxKeyMessage contextKey = iota CertificateLogin templateName = "cert"
NoEmailsInClientCertificate templateName = "no_emails"
NoChallengeInRequest templateName = "no_challenge"
) )
const TimeoutTen = 10 * time.Second
type LoginHandler struct { type LoginHandler struct {
logger *slog.Logger adminClient admin.ClientService
trans *services.I18NService bundle *i18n.Bundle
adminClient client.OAuth2API logger *log.Logger
templates TemplateCache templates map[templateName]*template.Template
messageCatalog *services.MessageCatalog
} }
func (h *LoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *LoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@ -62,7 +71,8 @@ func (h *LoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
localizer := getLocalizer(h.trans, r) accept := r.Header.Get("Accept-Language")
localizer := i18n.NewLocalizer(h.bundle, accept)
challenge := r.URL.Query().Get("login_challenge") challenge := r.URL.Query().Get("login_challenge")
@ -72,12 +82,12 @@ func (h *LoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
h.logger.Debug("received login challenge", "challenge", challenge) h.logger.Debugf("received login challenge %s\n", challenge)
certFullName, certEmails := getDataFromClientCert(h.logger, r) certEmails := h.getEmailAddressesFromClientCertificate(r)
if certEmails == nil { if certEmails == nil {
renderNoEmailsInClientCertificate(h.logger, h.templates, h.trans, w, localizer) h.renderNoEmailsInClientCertificate(w, localizer)
return return
} }
@ -85,7 +95,7 @@ func (h *LoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet { if r.Method == http.MethodGet {
h.handleGet(w, r, challenge, certEmails, localizer) h.handleGet(w, r, challenge, certEmails, localizer)
} else { } else {
h.handlePost(w, r, challenge, certFullName, certEmails, localizer) h.handlePost(w, r, challenge, certEmails, localizer)
} }
} }
@ -96,217 +106,159 @@ func (h *LoginHandler) handleGet(
certEmails []string, certEmails []string,
localizer *i18n.Localizer, localizer *i18n.Localizer,
) { ) {
oAuth2LoginRequest, response, err := h.adminClient.GetOAuth2LoginRequest( loginRequest, err := h.adminClient.GetLoginRequest(admin.NewGetLoginRequestParams().WithLoginChallenge(challenge))
r.Context(),
).LoginChallenge(challenge).Execute()
if err != nil { if err != nil {
h.logger.Warn( h.logger.WithError(err).WithField(
"could not get login request for challenge", "challenge", challenge,
"error", err, "challenge", challenge, ).Warn("could not get login request for challenge")
)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) var notFound *admin.GetLoginRequestNotFound
return if errors.As(err, &notFound) {
} w.WriteHeader(http.StatusNotFound)
usableEmails := certEmails http.Error(w, notFound.GetPayload().ErrorDescription, http.StatusNotFound)
defer func() { _ = response.Body.Close() }() return
}
h.logger.Debug(
"got response for GetOAuth2LoginRequest",
"response", response.Status, "login_request", oAuth2LoginRequest,
)
if subject, ok := oAuth2LoginRequest.GetSubjectOk(); ok && *subject != "" {
h.logger.Info("oauth2LoginRequest expects subject", "subject", *subject)
subjectInCert := false var gone *admin.GetLoginRequestGone
for _, email := range certEmails { if errors.As(err, &gone) {
if *subject == email { w.Header().Set("Location", *gone.GetPayload().RedirectTo)
subjectInCert = true w.WriteHeader(http.StatusGone)
break return
}
} }
if !subjectInCert { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
h.rejectLoginMissingSubject(w, r, challenge, localizer, *subject)
return return
} }
usableEmails = []string{*subject} h.renderRequestForClientCert(w, r, certEmails, localizer, loginRequest)
}
h.renderRequestForClientCert(w, r, usableEmails, localizer, oAuth2LoginRequest)
}
type FlashMessage struct {
Type string
Message string
} }
func (h *LoginHandler) handlePost( func (h *LoginHandler) handlePost(
w http.ResponseWriter, w http.ResponseWriter,
r *http.Request, r *http.Request,
challenge string, challenge string,
certFullName string,
certEmails []string, certEmails []string,
localizer *i18n.Localizer, localizer *i18n.Localizer,
) { ) {
if r.FormValue("use-identity") != "accept" { if r.FormValue("use-identity") != "accept" {
h.rejectLogin(w, r, challenge, localizer) h.rejectLogin(w, challenge, localizer)
return
}
if r.FormValue("email") == "" {
h.handleGet(w, r.WithContext(context.WithValue(
r.Context(),
ctxKeyMessage,
FlashMessage{
Type: "warning",
Message: h.trans.LookupMessage("NoEmailAddressSelected", nil, localizer),
},
)), challenge, certEmails, localizer)
return return
} }
// perform certificate auth // perform certificate auth
h.logger.Info( h.logger.Infof("would perform certificate authentication with: %+v", certEmails)
"will perform certificate authentication",
"emails", certEmails, "full_name", certFullName,
)
userID, err := h.performCertificateLogin(certEmails, r) userID, err := h.performCertificateLogin(certEmails, r)
if err != nil { if err != nil {
h.logger.Error("could not perform certificate login", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
session, err := GetSession(r)
if err != nil {
h.logger.Error("could not perform certificate login", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
session.Values[services.SessionFullName] = certFullName
session.Values[services.SessionEmail] = userID
session.Options.HttpOnly = true
session.Options.Secure = true
if err = session.Save(r, w); err != nil {
h.logger.Error("could not save session", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
// finish login and redirect to target // finish login and redirect to target
acceptRequest := client.NewAcceptOAuth2LoginRequest(userID) loginRequest, err := h.adminClient.AcceptLoginRequest(
acceptRequest.SetRemember(true) admin.NewAcceptLoginRequestParams().WithLoginChallenge(challenge).WithBody(
acceptRequest.SetRememberFor(0) &models.AcceptLoginRequest{
acceptRequest.SetAcr(string(ClientCertificate)) Acr: string(ClientCertificate),
Remember: true,
loginRequest, response, err := h.adminClient.AcceptOAuth2LoginRequest( RememberFor: 0,
r.Context(), Subject: &userID,
).LoginChallenge(challenge).AcceptOAuth2LoginRequest(*acceptRequest).Execute() }).WithTimeout(TimeoutTen))
if err != nil { if err != nil {
h.logger.Error("error getting login request", "error", err) h.logger.Errorf("error getting login request: %#v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) h.fillAcceptLoginRequestErrorBucket(r, err)
return return
} }
defer func() { _ = response.Body.Close() }() w.Header().Add("Location", *loginRequest.GetPayload().RedirectTo)
w.WriteHeader(http.StatusFound)
}
h.logger.Debug("got response for AcceptOAuth2LoginRequest", func (h *LoginHandler) fillAcceptLoginRequestErrorBucket(r *http.Request, err error) {
"response", response.Status, "accept_login_request", loginRequest, if errorBucket := GetErrorBucket(r); errorBucket != nil {
var (
errorDetails *ErrorDetails
acceptLoginRequestNotFound *admin.AcceptLoginRequestNotFound
) )
if h.logger.Enabled(r.Context(), slog.LevelDebug) { if errors.As(err, &acceptLoginRequestNotFound) {
if rb, err := io.ReadAll(response.Body); err == nil { payload := acceptLoginRequestNotFound.GetPayload()
h.logger.Debug("response body from Hydra", "response_body", rb) errorDetails = &ErrorDetails{
ErrorMessage: payload.Error,
ErrorDetails: []string{payload.ErrorDescription},
}
if acceptLoginRequestNotFound.Payload.StatusCode != 0 {
errorDetails.ErrorCode = strconv.Itoa(int(payload.StatusCode))
}
} else {
errorDetails = &ErrorDetails{
ErrorMessage: "could not accept login",
ErrorDetails: []string{err.Error()},
} }
} }
w.Header().Add("Location", loginRequest.GetRedirectTo()) errorBucket.AddError(errorDetails)
w.WriteHeader(http.StatusFound) }
} }
func (h *LoginHandler) rejectLogin( func (h *LoginHandler) rejectLogin(w http.ResponseWriter, challenge string, localizer *i18n.Localizer) {
w http.ResponseWriter, const Ten = 10 * time.Second
r *http.Request,
challenge string, rejectLoginRequest, err := h.adminClient.RejectLoginRequest(
localizer *i18n.Localizer, admin.NewRejectLoginRequestParams().WithLoginChallenge(challenge).WithBody(
) { &models.RejectRequest{
rejectRequest := client.NewRejectOAuth2RequestWithDefaults() ErrorDescription: h.messageCatalog.LookupMessage("LoginDeniedByUser", nil, localizer),
rejectRequest.SetErrorDescription(h.trans.LookupMessage("LoginDeniedByUser", nil, localizer)) ErrorHint: h.messageCatalog.LookupMessage("HintChooseAnIdentityForAuthentication", nil, localizer),
rejectRequest.SetErrorHint(h.trans.LookupMessage("HintChooseAnIdentityForAuthentication", nil, localizer)) StatusCode: http.StatusForbidden,
rejectRequest.SetStatusCode(http.StatusForbidden) },
).WithTimeout(Ten))
rejectLoginRequest, response, err := h.adminClient.RejectOAuth2LoginRequest(
r.Context(),
).LoginChallenge(challenge).RejectOAuth2Request(*rejectRequest).Execute()
if err != nil { if err != nil {
h.logger.Error("error sending reject login request", "error", err) h.logger.Errorf("error getting reject login request: %#v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
defer func() { _ = response.Body.Close() }() w.Header().Set("Location", *rejectLoginRequest.GetPayload().RedirectTo)
h.logger.DebugContext(
r.Context(),
"got response for RejectOAuth2LoginRequest",
"response", response.Status, "reject_login_request", rejectLoginRequest,
)
w.Header().Set("Location", rejectLoginRequest.GetRedirectTo())
w.WriteHeader(http.StatusFound) w.WriteHeader(http.StatusFound)
} }
func (h *LoginHandler) rejectLoginMissingSubject( func (h *LoginHandler) getEmailAddressesFromClientCertificate(r *http.Request) []string {
w http.ResponseWriter, r *http.Request, challenge string, localizer *i18n.Localizer, subject string, if r.TLS != nil && r.TLS.PeerCertificates != nil && len(r.TLS.PeerCertificates) > 0 {
) { firstCert := r.TLS.PeerCertificates[0]
rejectRequest := client.NewRejectOAuth2RequestWithDefaults()
rejectRequest.SetErrorDescription(h.trans.LookupMessage(
"LoginDeniedSubjectMissing", map[string]interface{}{"Subject": subject}, localizer),
)
rejectRequest.SetErrorHint(h.trans.LookupMessage("HintChooseDifferentClientCertificate", nil, localizer))
rejectRequest.SetStatusCode(http.StatusForbidden)
rejectLoginRequest, response, err := h.adminClient.RejectOAuth2LoginRequest( if !isClientCertificate(firstCert) {
r.Context()).LoginChallenge(challenge).RejectOAuth2Request( return nil
*rejectRequest, }
).Execute()
if err != nil {
h.logger.Error("error sending reject login request", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return for _, email := range firstCert.EmailAddresses {
h.logger.Infof("authenticated with a client certificate for email address %s", email)
} }
defer func() { _ = response.Body.Close() }() return firstCert.EmailAddresses
}
h.logger.DebugContext( return nil
r.Context(), }
"got response for RejectOAuth2LoginRequest",
"response", response.Status, "reject_login_request", rejectLoginRequest,
)
w.Header().Set("Location", rejectLoginRequest.GetRedirectTo()) func isClientCertificate(cert *x509.Certificate) bool {
w.WriteHeader(http.StatusFound) for _, ext := range cert.ExtKeyUsage {
if ext == x509.ExtKeyUsageClientAuth {
return true
}
}
return false
} }
func (h *LoginHandler) renderRequestForClientCert( func (h *LoginHandler) renderRequestForClientCert(
@ -314,31 +266,30 @@ func (h *LoginHandler) renderRequestForClientCert(
r *http.Request, r *http.Request,
emails []string, emails []string,
localizer *i18n.Localizer, localizer *i18n.Localizer,
loginRequest *client.OAuth2LoginRequest, loginRequest *admin.GetLoginRequestOK,
) { ) {
msg := h.trans.LookupMessage trans := func(label string) string {
msgPlural := h.trans.LookupMessagePlural return h.messageCatalog.LookupMessage(label, nil, localizer)
msgMarkdown := h.trans.LookupMarkdownMessage }
rendered := bytes.NewBuffer(make([]byte, 0)) rendered := bytes.NewBuffer(make([]byte, 0))
err := h.templates[CertificateLogin].Lookup("base").Execute(rendered, map[string]interface{}{ err := h.templates[CertificateLogin].Lookup("base").Execute(rendered, map[string]interface{}{
"Title": msg("LoginTitle", nil, localizer), "Title": trans("LoginTitle"),
csrf.TemplateTag: csrf.TemplateField(r), csrf.TemplateTag: csrf.TemplateField(r),
"IntroText": template.HTML(msgMarkdown( //nolint:gosec "IntroText": template.HTML(h.messageCatalog.LookupMessage( //nolint:gosec
"CertLoginIntroText", "CertLoginIntroText",
map[string]interface{}{"ClientName": loginRequest.Client.ClientName}, map[string]interface{}{"ClientName": loginRequest.GetPayload().Client.ClientName},
localizer, localizer,
)), )),
"EmailChoiceText": msgPlural("EmailChoiceText", nil, localizer, len(emails)), "EmailChoiceText": h.messageCatalog.LookupMessagePlural("EmailChoiceText", nil, localizer, len(emails)),
"emails": emails, "emails": emails,
"RequestText": msg("CertLoginRequestText", nil, localizer), "RequestText": trans("CertLoginRequestText"),
"AcceptLabel": msg("LabelAcceptCertLogin", nil, localizer), "AcceptLabel": trans("LabelAcceptCertLogin"),
"RejectLabel": msg("LabelRejectCertLogin", nil, localizer), "RejectLabel": trans("LabelRejectCertLogin"),
"FlashMessage": r.Context().Value(ctxKeyMessage),
}) })
if err != nil { if err != nil {
h.logger.Error("template rendering failed", "error", err) h.logger.Error(err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
@ -360,17 +311,34 @@ func (h *LoginHandler) performCertificateLogin(emails []string, r *http.Request)
return "", fmt.Errorf("no user found") 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.WithError(err).Error("template rendering failed")
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
}
func (h *LoginHandler) renderNoChallengeInRequest(w http.ResponseWriter, localizer *i18n.Localizer) { func (h *LoginHandler) renderNoChallengeInRequest(w http.ResponseWriter, localizer *i18n.Localizer) {
err := h.templates[NoChallengeInRequest].Lookup("base").Execute(w, map[string]interface{}{ err := h.templates[NoChallengeInRequest].Lookup("base").Execute(w, map[string]interface{}{
"Title": h.trans.LookupMessage("NoChallengeInRequestTitle", nil, localizer), "Title": h.messageCatalog.LookupMessage("NoChallengeInRequestTitle", nil, localizer),
"Explanation": template.HTML(h.trans.LookupMarkdownMessage( //nolint:gosec "Explanation": template.HTML(h.messageCatalog.LookupMarkdownMessage( //nolint:gosec
"NoChallengeInRequestExplanation", "NoChallengeInRequestExplanation",
nil, nil,
localizer, localizer,
)), )),
}) })
if err != nil { if err != nil {
h.logger.Error("template rendering failed", "error", err) h.logger.WithError(err).Error("template rendering failed")
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
@ -378,12 +346,32 @@ func (h *LoginHandler) renderNoChallengeInRequest(w http.ResponseWriter, localiz
} }
func NewLoginHandler( func NewLoginHandler(
logger *slog.Logger, tc TemplateCache, trans *services.I18NService, adminClient client.OAuth2API, logger *log.Logger,
bundle *i18n.Bundle,
messageCatalog *services.MessageCatalog,
adminClient admin.ClientService,
) *LoginHandler { ) *LoginHandler {
return &LoginHandler{ return &LoginHandler{
logger: logger,
trans: trans,
adminClient: adminClient, adminClient: adminClient,
templates: tc, bundle: bundle,
logger: logger,
templates: map[templateName]*template.Template{
CertificateLogin: template.Must(template.ParseFS(
ui.Templates,
"templates/base.gohtml",
"templates/client_certificate.gohtml",
)),
NoEmailsInClientCertificate: template.Must(template.ParseFS(
ui.Templates,
"templates/base.gohtml",
"templates/no_email_in_client_certificate.gohtml",
)),
NoChallengeInRequest: template.Must(template.ParseFS(
ui.Templates,
"templates/base.gohtml",
"templates/no_challenge_in_request.gohtml",
)),
},
messageCatalog: messageCatalog,
} }
} }

@ -1,5 +1,5 @@
/* /*
Copyright CAcert Inc. Copyright 2020-2023 CAcert Inc.
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -18,61 +18,47 @@ limitations under the License.
package handlers package handlers
import ( import (
"html/template"
"log/slog"
"net/http" "net/http"
"time"
client "github.com/ory/hydra-client-go/v2" "github.com/ory/hydra-client-go/client/admin"
log "github.com/sirupsen/logrus"
"code.cacert.org/cacert/oidc-idp/internal/services"
) )
type LogoutHandler struct { type LogoutHandler struct {
adminClient client.OAuth2API adminClient admin.ClientService
logger *slog.Logger logger *log.Logger
} }
func (h *LogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *LogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
const Ten = 10 * time.Second
challenge := r.URL.Query().Get("logout_challenge") challenge := r.URL.Query().Get("logout_challenge")
h.logger.Debug("received logout challenge", "challenge", challenge) h.logger.Debugf("received challenge %s\n", challenge)
logoutRequest, response, err := h.adminClient.GetOAuth2LogoutRequest( logoutRequest, err := h.adminClient.GetLogoutRequest(
r.Context(), admin.NewGetLogoutRequestParams().WithLogoutChallenge(challenge).WithTimeout(Ten))
).LogoutChallenge(challenge).Execute()
if err != nil { if err != nil {
h.logger.Error("error getting logout requests", "error", err) h.logger.Errorf("error getting logout requests: %v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
defer func() { _ = response.Body.Close() }() h.logger.Debugf("received logout request: %#v", logoutRequest.Payload)
h.logger.Debug(
"got response for GetOAuth2LogoutRequest",
"response", response.Status, "logout_request", logoutRequest,
)
acceptLogoutRequest, response, err := h.adminClient.AcceptOAuth2LogoutRequest( acceptLogoutRequest, err := h.adminClient.AcceptLogoutRequest(
r.Context(), admin.NewAcceptLogoutRequestParams().WithLogoutChallenge(challenge))
).LogoutChallenge(challenge).Execute()
if err != nil { if err != nil {
h.logger.Error("accept logout request failed", "error", err) h.logger.Errorf("error accepting logout: %v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
} }
defer func() { _ = response.Body.Close() }() w.Header().Set("Location", *acceptLogoutRequest.GetPayload().RedirectTo)
h.logger.Debug(
"got response for AcceptOAuth2LogoutRequest",
"response", response.Status, "accept_logout_request", acceptLogoutRequest,
)
w.Header().Set("Location", acceptLogoutRequest.GetRedirectTo())
w.WriteHeader(http.StatusFound) w.WriteHeader(http.StatusFound)
} }
func NewLogout(logger *slog.Logger, adminClient client.OAuth2API) *LogoutHandler { func NewLogoutHandler(logger *log.Logger, adminClient admin.ClientService) *LogoutHandler {
return &LogoutHandler{ return &LogoutHandler{
logger: logger, logger: logger,
adminClient: adminClient, adminClient: adminClient,
@ -80,32 +66,12 @@ func NewLogout(logger *slog.Logger, adminClient client.OAuth2API) *LogoutHandler
} }
type LogoutSuccessHandler struct { type LogoutSuccessHandler struct {
logger *slog.Logger
trans *services.I18NService
templates TemplateCache
} }
func (h *LogoutSuccessHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (l *LogoutSuccessHandler) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
if r.Method != http.MethodGet { panic("implement me")
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
localizer := getLocalizer(h.trans, r)
h.templates.render(h.logger, w, LogoutSuccessful, map[string]interface{}{
"Title": h.trans.LookupMessage("LogoutSuccessfulTitle", nil, localizer),
"Explanation": template.HTML(h.trans.LookupMarkdownMessage( //nolint:gosec
"LogoutSuccessfulText",
nil,
localizer,
)),
})
} }
func NewLogoutSuccess( func NewLogoutSuccessHandler() *LogoutSuccessHandler {
logger *slog.Logger, templateCache TemplateCache, trans *services.I18NService, return &LogoutSuccessHandler{}
) *LogoutSuccessHandler {
return &LogoutSuccessHandler{logger: logger, trans: trans, templates: templateCache}
} }

@ -1,372 +0,0 @@
/*
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
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
https://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"
"io"
"log/slog"
"net/http"
"sort"
"strings"
"time"
"github.com/gorilla/csrf"
"github.com/nicksnyder/go-i18n/v2/i18n"
client "github.com/ory/hydra-client-go/v2"
"code.cacert.org/cacert/oidc-idp/internal/services"
)
type IndexHandler struct {
logger *slog.Logger
trans *services.I18NService
templates TemplateCache
}
func (h *IndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
localizer := getLocalizer(h.trans, r)
if r.URL.Path != "/" {
http.Error(w, h.trans.LookupHTTPErrorMessage(http.StatusNotFound, localizer), http.StatusNotFound)
return
}
h.templates.render(h.logger, w, Index, map[string]interface{}{
"Title": h.trans.LookupMessage("IndexTitle", nil, localizer),
"WelcomeMessage": template.HTML(h.trans.LookupMarkdownMessage( //nolint:gosec
"IndexWelcomeMessage",
map[string]interface{}{"ManageConsentHRef": "/manage-consent"},
localizer,
)),
})
}
func NewIndex(logger *slog.Logger, templateCache TemplateCache, trans *services.I18NService) *IndexHandler {
return &IndexHandler{logger: logger, trans: trans, templates: templateCache}
}
type ManageConsentHandler struct {
logger *slog.Logger
trans *services.I18NService
adminAPI client.OAuth2API
templates TemplateCache
}
func (h *ManageConsentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
localizer := getLocalizer(h.trans, r)
allSessions := make(ConsentSessions, 0)
var (
sessions []client.OAuth2ConsentSession
ok bool
)
for _, s := range GetAuthenticatedAddresses(r) {
if sessions, ok = h.getConsentSessions(w, r, s); !ok {
return
}
for _, session := range sessions {
allSessions = append(allSessions, ConsentSession{Subject: s, Session: session})
}
}
sort.Sort(allSessions)
requestSession, err := GetSession(r)
if err != nil {
h.logger.Error("could not get session for request", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
h.logger.Debug("got session for request", "new_session", requestSession.IsNew)
h.templates.render(h.logger, w, ManageConsent, map[string]interface{}{
"Title": h.trans.LookupMessage("ManageConsentTitle", nil, localizer),
"Description": template.HTML(h.trans.LookupMarkdownMessage( //nolint:gosec
"ManageConsentDescription",
nil,
localizer,
)),
"NoConsentGiven": h.trans.LookupMessage("NoConsentGiven", nil, localizer),
"Flashes": requestSession.Flashes("messages"),
"ConsentSessions": allSessions,
"ButtonTitleRevoke": h.trans.LookupMessage("ButtonTitleRevoke", nil, localizer),
"ApplicationTitle": h.trans.LookupMessage("ColumnNameApplication", nil, localizer),
"ActionsTitle": h.trans.LookupMessage("ColumnNameActions", nil, localizer),
"SubjectTitle": h.trans.LookupMessage("ColumnNameSubject", nil, localizer),
"GrantedTitle": h.trans.LookupMessage("ColumnNameGranted", nil, localizer),
"ExpiresTitle": h.trans.LookupMessage("ColumnNameExpires", nil, localizer),
"LabelUnknown": h.trans.LookupMessage("LabelUnknown", nil, localizer),
"LabelNever": h.trans.LookupMessage("LabelNever", nil, localizer),
})
}
type ConsentSession struct {
Subject string
Session client.OAuth2ConsentSession
}
func (s ConsentSession) GetClientName() string {
if !s.Session.HasConsentRequest() {
return ""
}
request := s.Session.GetConsentRequest()
if !request.HasClient() {
return ""
}
consentClient := request.GetClient()
return consentClient.GetClientName()
}
func (s ConsentSession) GetID() string {
if consent, ok := s.Session.GetConsentRequestOk(); ok {
if app, ok := consent.GetClientOk(); ok {
return app.GetClientId()
}
}
return ""
}
func (s ConsentSession) GrantedAt() *time.Time {
if grantedAt, ok := s.Session.GetHandledAtOk(); ok {
return grantedAt
}
return nil
}
func (s ConsentSession) Expires() *time.Time {
if expiresAt, ok := s.Session.GetExpiresAtOk(); ok {
if grantedAt := s.GrantedAt(); grantedAt != nil {
return expiresAt.AccessToken
}
}
return nil
}
type ConsentSessions []ConsentSession
func (c ConsentSessions) Len() int {
return len(c)
}
func (c ConsentSessions) Less(i, j int) bool {
return c[i].Subject < c[j].Subject && c[i].GetClientName() < c[j].GetClientName()
}
func (c ConsentSessions) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
func (h *ManageConsentHandler) getConsentSessions(
w http.ResponseWriter, r *http.Request, subject string,
) ([]client.OAuth2ConsentSession, bool) {
sessions, response, err := h.adminAPI.ListOAuth2ConsentSessions(r.Context()).Subject(subject).Execute()
if err != nil {
h.logger.Error("error getting consent session list", "error", err)
// h.fillAcceptLoginRequestErrorBucket(r, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return nil, false
}
defer func(response *http.Response) { _ = response.Body.Close() }(response)
h.logger.Debug(
"got response for AcceptOAuth2LoginRequest",
"response", response.Status, "consent_sessions", sessions,
)
if h.logger.Enabled(r.Context(), slog.LevelDebug) {
if rb, err := io.ReadAll(response.Body); err == nil {
h.logger.Debug("response body from Hydra", "response_body", rb)
}
}
return sessions, true
}
func NewManageConsent(
logger *slog.Logger, tc TemplateCache, trans *services.I18NService, adminAPI client.OAuth2API,
) *ManageConsentHandler {
return &ManageConsentHandler{logger: logger, trans: trans, adminAPI: adminAPI, templates: tc}
}
type RevokeConsentHandler struct {
logger *slog.Logger
trans *services.I18NService
adminAPI client.OAuth2API
templates TemplateCache
}
func (h *RevokeConsentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
localizer := getLocalizer(h.trans, r)
if r.Method != http.MethodGet && r.Method != http.MethodPost {
http.Error(
w, h.trans.LookupHTTPErrorMessage(http.StatusMethodNotAllowed, localizer), http.StatusMethodNotAllowed,
)
return
}
clientID, _ := strings.CutPrefix(r.URL.Path, "/revoke-consent/")
subject := r.URL.Query().Get("subject")
if clientID == "" || subject == "" {
http.Error(w, h.trans.LookupHTTPErrorMessage(http.StatusNotFound, localizer), http.StatusNotFound)
return
}
if r.Method == http.MethodGet {
h.handleGet(w, r, clientID, localizer, subject)
return
}
h.handlePost(w, r, clientID, subject, localizer)
}
func (h *RevokeConsentHandler) handleGet(
w http.ResponseWriter, r *http.Request, clientID string, localizer *i18n.Localizer, subject string,
) {
clientApp, found, err := h.getClient(r.Context(), clientID)
if err != nil {
h.logger.Error("could not get client", "error", err)
http.Error(
w, h.trans.LookupHTTPErrorMessage(http.StatusInternalServerError, localizer),
http.StatusInternalServerError,
)
return
}
if !found {
http.Error(w, h.trans.LookupHTTPErrorMessage(http.StatusNotFound, localizer), http.StatusNotFound)
return
}
h.templates.render(h.logger, w, ConfirmRevoke, map[string]interface{}{
"Title": h.trans.LookupMessage("ConfirmRevokeTitle", nil, localizer),
"ButtonTitleRevoke": h.trans.LookupMessage("ButtonTitleConfirmRevoke", nil, localizer),
"ButtonTitleCancel": h.trans.LookupMessage("ButtonTitleCancel", nil, localizer),
"CancelLink": "/manage-consent",
csrf.TemplateTag: csrf.TemplateField(r),
"Explanation": template.HTML(h.trans.LookupMarkdownMessage( //nolint:gosec
"ConfirmRevokeExplanation", map[string]interface{}{
"ApplicationID": template.URLQueryEscaper(clientApp.GetClientId()),
"Application": template.HTMLEscapeString(clientApp.GetClientName()),
"Subject": subject,
}, localizer)),
})
}
func (h *RevokeConsentHandler) handlePost(
w http.ResponseWriter, r *http.Request, clientID string, subject string, localizer *i18n.Localizer,
) {
err := h.revokeConsent(r.Context(), clientID, subject)
if err != nil {
h.logger.Error("could not revoke consent", "error", err)
http.Error(
w, h.trans.LookupHTTPErrorMessage(http.StatusInternalServerError, localizer),
http.StatusInternalServerError,
)
return
}
http.Redirect(w, r, "/manage-consent", http.StatusFound)
}
func (h *RevokeConsentHandler) getClient(ctx context.Context, clientID string) (*client.OAuth2Client, bool, error) {
clientApp, response, err := h.adminAPI.GetOAuth2Client(ctx, clientID).Execute()
if err != nil {
return nil, false, fmt.Errorf("error getting client application: %w", err)
}
defer func(response *http.Response) { _ = response.Body.Close() }(response)
h.logger.Debug(
"got response for GetOAuth2Client",
"response", response.Status, "client_app", clientApp,
)
if h.logger.Enabled(ctx, slog.LevelDebug) {
if rb, err := io.ReadAll(response.Body); err == nil {
h.logger.Debug("response body from Hydra", "response_body", rb)
}
}
return clientApp, true, nil
}
func (h *RevokeConsentHandler) revokeConsent(ctx context.Context, clientID, subject string) error {
response, err := h.adminAPI.RevokeOAuth2ConsentSessions(ctx).Client(clientID).Subject(subject).Execute()
if err != nil {
return fmt.Errorf("could not revoke consent: %w", err)
}
defer func(response *http.Response) { _ = response.Body.Close() }(response)
h.logger.Debug("got response for RevokeOAuth2ConsentSessions", "response", response.Status)
if h.logger.Enabled(ctx, slog.LevelDebug) {
if rb, err := io.ReadAll(response.Body); err == nil {
h.logger.Debug("response body from Hydra", "response_body", rb)
}
}
return nil
}
func NewRevokeConsent(
logger *slog.Logger, tc TemplateCache, trans *services.I18NService, adminAPI client.OAuth2API,
) *RevokeConsentHandler {
return &RevokeConsentHandler{logger: logger, trans: trans, adminAPI: adminAPI, templates: tc}
}

@ -1,5 +1,5 @@
/* /*
Copyright CAcert Inc. Copyright 2020-2023 CAcert Inc.
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -20,9 +20,10 @@ package handlers
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"net/http" "net/http"
"sync/atomic" "sync/atomic"
log "github.com/sirupsen/logrus"
) )
type key int type key int
@ -53,7 +54,7 @@ func (sci *statusCodeInterceptor) Write(content []byte) (int, error) {
return count, nil return count, nil
} }
func Logging(logger *slog.Logger) func(http.Handler) http.Handler { func Logging(logger *log.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
interceptor := &statusCodeInterceptor{w, http.StatusOK, 0} interceptor := &statusCodeInterceptor{w, http.StatusOK, 0}
@ -62,10 +63,8 @@ func Logging(logger *slog.Logger) func(http.Handler) http.Handler {
if !ok { if !ok {
requestID = "unknown" requestID = "unknown"
} }
logger.Infof(
logger.Info( "%s %s \"%s %s\" %d %d \"%s\"",
fmt.Sprintf(
"[%s] %s \"%s %s\" %d %d \"%s\"",
requestID, requestID,
r.RemoteAddr, r.RemoteAddr,
r.Method, r.Method,
@ -73,7 +72,6 @@ func Logging(logger *slog.Logger) func(http.Handler) http.Handler {
interceptor.code, interceptor.code,
interceptor.count, interceptor.count,
r.UserAgent(), r.UserAgent(),
),
) )
}() }()
next.ServeHTTP(interceptor, r) next.ServeHTTP(interceptor, r)
@ -88,9 +86,7 @@ func Tracing(nextRequestID func() string) func(http.Handler) http.Handler {
if requestID == "" { if requestID == "" {
requestID = nextRequestID() requestID = nextRequestID()
} }
ctx := context.WithValue(r.Context(), requestIDKey, requestID) ctx := context.WithValue(r.Context(), requestIDKey, requestID)
w.Header().Set("X-Request-Id", requestID) w.Header().Set("X-Request-Id", requestID)
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })
@ -100,7 +96,7 @@ func Tracing(nextRequestID func() string) func(http.Handler) http.Handler {
var Healthy int32 var Healthy int32
func NewHealthHandler() http.Handler { func NewHealthHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if atomic.LoadInt32(&Healthy) == 1 { if atomic.LoadInt32(&Healthy) == 1 {
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)

@ -1,5 +1,5 @@
/* /*
Copyright CAcert Inc. Copyright 2020-2023 CAcert Inc.
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -18,9 +18,7 @@ limitations under the License.
package handlers package handlers
import ( import (
"crypto/x509"
"fmt" "fmt"
"log/slog"
"net/http" "net/http"
"time" "time"
) )
@ -29,37 +27,8 @@ func EnableHSTS() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
const Days180 = 180 const Days180 = 180
w.Header().Set("Strict-Transport-Security", fmt.Sprintf("max-age=%d", int((time.Hour*24*Days180).Seconds()))) w.Header().Set("Strict-Transport-Security", fmt.Sprintf("max-age=%d", int((time.Hour*24*Days180).Seconds())))
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
}) })
} }
} }
func getDataFromClientCert(logger *slog.Logger, r *http.Request) (string, []string) {
if r.TLS != nil && r.TLS.PeerCertificates != nil && len(r.TLS.PeerCertificates) > 0 {
firstCert := r.TLS.PeerCertificates[0]
if !isClientCertificate(firstCert) {
return "", nil
}
for _, email := range firstCert.EmailAddresses {
logger.Info("authenticated with a client certificate for email address", "email", email)
}
return firstCert.Subject.CommonName, firstCert.EmailAddresses
}
return "", nil
}
func isClientCertificate(cert *x509.Certificate) bool {
for _, ext := range cert.ExtKeyUsage {
if ext == x509.ExtKeyUsageClientAuth {
return true
}
}
return false
}

@ -1,5 +1,5 @@
/* /*
Copyright CAcert Inc. Copyright 2020-2023 CAcert Inc.
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");

@ -1,5 +1,5 @@
/* /*
Copyright CAcert Inc. Copyright 2020-2023 CAcert Inc.
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -19,7 +19,6 @@ package services
import ( import (
"fmt" "fmt"
"log/slog"
"os" "os"
"strings" "strings"
@ -29,79 +28,58 @@ import (
"github.com/knadh/koanf/providers/env" "github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file" "github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/posflag" "github.com/knadh/koanf/providers/posflag"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
const (
defaultServerPort = 3000
defaultFile = "idp.toml"
)
var DefaultConfig = map[string]interface{}{
"server.bind_address": "",
"server.name": "idp.cacert.localhost",
"server.port": defaultServerPort,
"server.key": "idp.cacert.localhost+1-key.pem",
"server.certificate": "idp.cacert.localhost+1.pem",
"security.client.ca-file": "client_ca.pem",
"admin.url": "https://hydra.cacert.localhost:4445/",
"i18n.languages": []string{"en", "de"},
"log.level": "info",
"log.json": true,
}
func ConfigureApplication( func ConfigureApplication(
logger *slog.Logger, logger *logrus.Logger,
appName string, appName string,
defaultConfig map[string]interface{}, defaultConfig map[string]interface{},
) (*koanf.Koanf, error) { ) (*koanf.Koanf, error) {
f := pflag.NewFlagSet("config", pflag.ContinueOnError) f := pflag.NewFlagSet("config", pflag.ContinueOnError)
f.Usage = func() { f.Usage = func() {
logger.Info(f.FlagUsages()) fmt.Println(f.FlagUsages()) //nolint:forbidigo
os.Exit(0) os.Exit(0)
} }
f.StringSlice( f.StringSlice(
"conf", "conf",
[]string{fmt.Sprintf("%s.toml", strings.ToLower(appName))}, []string{fmt.Sprintf("%s.toml", strings.ToLower(appName))},
"path to one or more .toml files", "path to one or more .toml files",
) )
var err error if err := f.Parse(os.Args[1:]); err != nil {
logger.Fatal(err)
if err = f.Parse(os.Args[1:]); err != nil {
return nil, fmt.Errorf("could not parse command line arguments: %w", err)
} }
config := koanf.New(".") config := koanf.New(".")
_ = config.Load(confmap.Provider(defaultConfig, "."), nil) _ = config.Load(confmap.Provider(defaultConfig, "."), nil)
if err = config.Load(file.Provider(defaultFile), toml.Parser()); err != nil && !os.IsNotExist(err) {
logger.Error("could not load configuration from file", "file", defaultFile)
return nil, fmt.Errorf("error loading configuration: %w", err)
}
cFiles, _ := f.GetStringSlice("conf") cFiles, _ := f.GetStringSlice("conf")
for _, c := range cFiles { for _, c := range cFiles {
if err = config.Load(file.Provider(c), toml.Parser()); err != nil { if err := config.Load(file.Provider(c), toml.Parser()); err != nil {
logger.Error("could not load configuration from file", "file", c) logger.Fatalf("error loading config file: %s", err)
return nil, fmt.Errorf("error loading configuration: %w", err)
} }
} }
if err = config.Load(posflag.Provider(f, ".", config), nil); err != nil { if err := config.Load(posflag.Provider(f, ".", config), nil); err != nil {
return nil, fmt.Errorf("error loading configuration from command line: %w", err) logger.Fatalf("error loading configuration: %s", err)
} }
prefix := fmt.Sprintf("%s_", strings.ToUpper(appName)) if err := config.Load(
file.Provider("resource_app.toml"),
toml.Parser(),
); err != nil && !os.IsNotExist(err) {
logger.Fatalf("error loading config: %v", err)
}
if err = config.Load(env.Provider(prefix, ".", func(s string) string { prefix := fmt.Sprintf("%s_", strings.ToUpper(appName))
if err := config.Load(env.Provider(prefix, ".", func(s string) string {
return strings.ReplaceAll(strings.ToLower(strings.TrimPrefix(s, prefix)), "_", ".") return strings.ReplaceAll(strings.ToLower(strings.TrimPrefix(s, prefix)), "_", ".")
}), nil); err != nil { }), nil); err != nil {
return nil, fmt.Errorf("error loading configuration from environment: %w", err) logger.Fatalf("error loading config: %v", err)
} }
return config, nil return config, nil

@ -1,5 +1,5 @@
/* /*
Copyright CAcert Inc. Copyright 2020-2023 CAcert Inc.
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -21,220 +21,23 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"net/http"
log "github.com/sirupsen/logrus"
"github.com/yuin/goldmark" "github.com/yuin/goldmark"
"code.cacert.org/cacert/oidc-idp/translations" "code.cacert.org/cacert/oidc_idp/translations"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/nicksnyder/go-i18n/v2/i18n" "github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language" "golang.org/x/text/language"
) )
type MessageCatalog struct { func AddMessages(catalog *MessageCatalog) error {
messages map[string]*i18n.Message
logger *slog.Logger
}
func (m *MessageCatalog) AddMessages(messages map[string]*i18n.Message) {
for key, value := range messages {
m.messages[key] = value
}
}
func (s *I18NService) LookupMessage(
id string,
templateData map[string]interface{},
localizer *i18n.Localizer,
) string {
if message, ok := s.catalog.messages[id]; ok {
translation, err := localizer.Localize(&i18n.LocalizeConfig{
DefaultMessage: message,
TemplateData: templateData,
})
if err != nil {
return s.catalog.handleLocalizeError(id, translation, err)
}
return translation
}
s.logger.Warn("no translation found for id", "id", id)
return id
}
func (s *I18NService) LookupHTTPErrorMessage(httpStatusCode int, localizer *i18n.Localizer) string {
id := fmt.Sprintf("http%d", httpStatusCode)
translation := s.LookupMessage(id, nil, localizer)
if translation != id {
return translation
}
return http.StatusText(httpStatusCode)
}
func (s *I18NService) LookupMarkdownMessage(
id string,
templateData map[string]interface{},
localizer *i18n.Localizer,
) string {
if message, ok := s.catalog.messages[id]; ok {
translation, err := localizer.Localize(&i18n.LocalizeConfig{
DefaultMessage: message,
TemplateData: templateData,
})
if err != nil {
s.logger.Warn("message localization failed", "error", err, "message", message)
}
if translation == "" {
return id
}
buf := &bytes.Buffer{}
err = goldmark.Convert([]byte(translation), buf)
if err != nil {
return s.catalog.handleLocalizeError(id, translation, fmt.Errorf("markdown conversion error: %w", err))
}
return buf.String()
}
s.logger.Warn("no translation found for id", "id", id)
return id
}
func (s *I18NService) LookupMessagePlural(
id string,
templateData map[string]interface{},
localizer *i18n.Localizer,
count int,
) string {
if message, ok := s.catalog.messages[id]; ok {
translation, err := localizer.Localize(&i18n.LocalizeConfig{
DefaultMessage: message,
TemplateData: templateData,
PluralCount: count,
})
if err != nil {
return s.catalog.handleLocalizeError(id, translation, err)
}
return translation
}
s.logger.Warn("no translation found for id", "id", id)
return id
}
func (m *MessageCatalog) handleLocalizeError(id string, translation string, err error) string {
var messageNotFound *i18n.MessageNotFoundErr
if errors.As(err, &messageNotFound) {
m.logger.Warn("message not found", "error", err, "message", id)
if translation != "" {
return translation
}
} else {
m.logger.Error("translation error", "error", err, "message", id)
}
return id
}
type I18NService struct {
logger *slog.Logger
bundle *i18n.Bundle
catalog *MessageCatalog
}
func (s *I18NService) AddMessages() error {
messages := make(map[string]*i18n.Message) messages := make(map[string]*i18n.Message)
messages["http404"] = &i18n.Message{
ID: "http404",
Description: "HTTP error 404 not found",
Other: "Not found",
}
messages["unknown"] = &i18n.Message{ messages["unknown"] = &i18n.Message{
ID: "ErrorUnknown", ID: "ErrorUnknown",
Other: "Unknown error", Other: "Unknown error",
} }
messages["AuthServerErrorTitle"] = &i18n.Message{
ID: "AuthServerErrorTitle",
Other: "Authorization server returned an error",
}
messages["AuthServerErrorExplanation"] = &i18n.Message{
ID: "AuthServerErrorExplanation",
Other: "A request that your browser sent to the authorization server caused an error." +
" The authorization server returned details about the error that are printed below.",
}
messages["ButtonTitleCancel"] = &i18n.Message{
ID: "ButtonTitleCancel",
Description: "Title for a button to cancel an action",
Other: "Cancel",
}
messages["ButtonTitleConsent"] = &i18n.Message{
ID: "ButtonTitleConsent",
Description: "Title for a button to give consent",
Other: "Consent",
}
messages["ButtonTitleDeny"] = &i18n.Message{
ID: "ButtonTitleDeny",
Description: "Title for a button to deny consent",
Other: "Deny",
}
messages["ButtonTitleRevoke"] = &i18n.Message{
ID: "ButtonTitleRevoke",
Description: "Title for a button to revoke consent",
Other: "Revoke",
}
messages["ButtonTitleConfirmRevoke"] = &i18n.Message{
ID: "ButtonTitleConfirmRevoke",
Description: "Title for a button to confirm consent revocation",
Other: "Yes, Revoke!",
}
messages["ColumnNameApplication"] = &i18n.Message{
ID: "ColumnNameApplication",
Description: "Title for a table column showing application names",
Other: "Application",
}
messages["ColumnNameActions"] = &i18n.Message{
ID: "ColumnNameActions",
Description: "Title for a table column showing available actions",
Other: "Actions",
}
messages["ColumnNameExpires"] = &i18n.Message{
ID: "ColumnNameExpires",
Description: "Title for a table column showing the expiry date for a consent",
Other: "Expires",
}
messages["ColumnNameGranted"] = &i18n.Message{
ID: "ColumnNameGranted",
Description: "Title for a table column showing the time when consent has been granted",
Other: "Granted at",
}
messages["ColumnNameSubject"] = &i18n.Message{
ID: "ColumnNameSubject",
Description: "Title for a table column showing the subject of a consent",
Other: "Subject",
}
messages["ConfirmRevokeTitle"] = &i18n.Message{
ID: "ConfirmRevokeTitle",
Other: "Revoke consent",
}
messages["ConfirmRevokeExplanation"] = &i18n.Message{
ID: "ConfirmRevokeExplanation",
Other: "Do you want to revoke your consent to allow **{{ .Application }}**" +
" access to identity data for **{{ .Subject }}**?",
}
messages["TitleRequestConsent"] = &i18n.Message{ messages["TitleRequestConsent"] = &i18n.Message{
ID: "TitleRequestConsent", ID: "TitleRequestConsent",
Other: "Application requests your consent", Other: "Application requests your consent",
@ -247,21 +50,15 @@ func (s *I18NService) AddMessages() error {
ID: "LabelConsent", ID: "LabelConsent",
Other: "I hereby agree that the application may get the requested permissions.", Other: "I hereby agree that the application may get the requested permissions.",
} }
messages["LabelNever"] = &i18n.Message{
ID: "LabelNever",
Other: "Never",
}
messages["LabelUnknown"] = &i18n.Message{
ID: "LabelUnknown",
Other: "Unknown",
}
messages["IntroConsentRequested"] = &i18n.Message{ messages["IntroConsentRequested"] = &i18n.Message{
ID: "IntroConsentRequested", ID: "IntroConsentRequested",
Other: "The **{{ .client }}** application requested your consent for the following set of permissions:", Other: "The <strong>{{ .client }}</strong> application requested your consent for the following set of " +
"permissions:",
} }
messages["IntroConsentMoreInformation"] = &i18n.Message{ messages["IntroConsentMoreInformation"] = &i18n.Message{
ID: "IntroConsentMoreInformation", ID: "IntroConsentMoreInformation",
Other: "You can find more information about **{{ .client }}** at [its description page]({{ .clientLink }}).", Other: "You can find more information about <strong>{{ .client }}</strong> at " +
"<a href=\"{{ .clientLink }}\">its description page</a>.",
} }
messages["ClaimsInformation"] = &i18n.Message{ messages["ClaimsInformation"] = &i18n.Message{
ID: "ClaimsInformation", ID: "ClaimsInformation",
@ -273,7 +70,7 @@ func (s *I18NService) AddMessages() error {
} }
messages["CertLoginIntroText"] = &i18n.Message{ messages["CertLoginIntroText"] = &i18n.Message{
ID: "CertLoginIntroText", ID: "CertLoginIntroText",
Other: "The application **{{ .ClientName }}** requests a login.", Other: "The application <strong>{{ .ClientName }}</strong> requests a login.",
} }
messages["EmailChoiceText"] = &i18n.Message{ messages["EmailChoiceText"] = &i18n.Message{
ID: "EmailChoiceText", ID: "EmailChoiceText",
@ -281,15 +78,6 @@ func (s *I18NService) AddMessages() error {
Other: "You have presented a valid client certificate for multiple email addresses. " + Other: "You have presented a valid client certificate for multiple email addresses. " +
"Please choose which one you want to present to the application:", "Please choose which one you want to present to the application:",
} }
messages["IndexTitle"] = &i18n.Message{
ID: "IndexTitle",
Other: "Welcome to your identity provider",
}
messages["IndexWelcomeMessage"] = &i18n.Message{
ID: "IndexWelcomeMessage",
Other: "Go to [manage consent]({{ .ManageConsentHRef }}) to show or revoke consent" +
" you have given to client applications.",
}
messages["LoginTitle"] = &i18n.Message{ messages["LoginTitle"] = &i18n.Message{
ID: "LoginTitle", ID: "LoginTitle",
Other: "Authenticate with a client certificate", Other: "Authenticate with a client certificate",
@ -312,42 +100,10 @@ func (s *I18NService) AddMessages() error {
ID: "LoginDeniedByUser", ID: "LoginDeniedByUser",
Other: "Login has been denied by the user.", Other: "Login has been denied by the user.",
} }
messages["LoginDeniedSubjectMissing"] = &i18n.Message{
ID: "LoginDeniedSubjectMissing",
Other: "Login has been denied because the requested subject {{ .Subject }} could not be authenticated.",
}
messages["LogoutSuccessfulTitle"] = &i18n.Message{
ID: "LogoutSuccessfulTitle",
Other: "Logout successful",
}
messages["LogoutSuccessfulText"] = &i18n.Message{
ID: "LogoutSuccessfulText",
Other: "You have been logged out successfully.",
}
messages["ManageConsentTitle"] = &i18n.Message{
ID: "ManageConsentTitle",
Other: "Manage consent",
}
messages["ManageConsentDescription"] = &i18n.Message{
ID: "ManageConsentDescription",
Other: "This page allows you to see consent that you have given to client applications in the past.",
}
messages["HintChooseAnIdentityForAuthentication"] = &i18n.Message{ messages["HintChooseAnIdentityForAuthentication"] = &i18n.Message{
ID: "HintChooseAnIdentityForAuthentication", ID: "HintChooseAnIdentityForAuthentication",
Other: "Choose an identity for authentication.", Other: "Choose an identity for authentication.",
} }
messages["HintChooseDifferentClientCertificate"] = &i18n.Message{
ID: "HintChooseDifferentClientCertificate",
Other: "Choose a different client certificate for authentication.",
}
messages["NoConsentGiven"] = &i18n.Message{
ID: "NoConsentGiven",
Other: "You have not given consent to use your data to any application yet.",
}
messages["NoEmailAddressSelected"] = &i18n.Message{
ID: "NoEmailAddressSelected",
Other: "You did not select an email address. Please select an email address to continue.",
}
messages["NoEmailsInClientCertificateTitle"] = &i18n.Message{ messages["NoEmailsInClientCertificateTitle"] = &i18n.Message{
ID: "NoEmailsInClientCertificateTitle", ID: "NoEmailsInClientCertificateTitle",
Other: "No email addresses in client certificate", Other: "No email addresses in client certificate",
@ -368,16 +124,149 @@ An email address is required to authenticate yourself.`,
" [the ORY Hydra documentation](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow).", " [the ORY Hydra documentation](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow).",
} }
s.catalog.AddMessages(messages) catalog.AddMessages(messages)
return nil return nil
} }
func (s *I18NService) Localizer(languages string) *i18n.Localizer { type MessageCatalog struct {
return i18n.NewLocalizer(s.bundle, languages) 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, 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 {
return m.handleLocalizeError(id, translation, err)
}
return translation
}
m.logger.Warnf("no translation found for %s", id)
return id
}
func (m *MessageCatalog) LookupMarkdownMessage(
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 {
return m.handleLocalizeError(id, translation, err)
}
buf := &bytes.Buffer{}
err = goldmark.Convert([]byte(translation), buf)
if err != nil {
return m.handleLocalizeError(id, translation, fmt.Errorf("markdown conversion error: %w", err))
}
return buf.String()
}
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 {
return m.handleLocalizeError(id, translation, err)
}
return translation
}
m.logger.Warnf("no translation found for %s", id)
return id
}
func (m *MessageCatalog) handleLocalizeError(id string, translation string, err error) string {
var messageNotFound *i18n.MessageNotFoundErr
if errors.As(err, &messageNotFound) {
m.logger.WithError(err).WithField("message", id).Warn("message not found")
if translation != "" {
return translation
}
} else {
m.logger.WithError(err).WithField("message", id).Error("translation error")
}
return id
} }
func InitI18n(logger *slog.Logger, languages []string) *I18NService { func InitI18n(logger *log.Logger, languages []string) (*i18n.Bundle, *MessageCatalog) {
bundle := i18n.NewBundle(language.English) bundle := i18n.NewBundle(language.English)
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
@ -386,7 +275,7 @@ func InitI18n(logger *slog.Logger, languages []string) *I18NService {
bundleBytes, err := translations.Bundles.ReadFile(bundleName) bundleBytes, err := translations.Bundles.ReadFile(bundleName)
if err != nil { if err != nil {
logger.Warn("message bundle not found", "bundle", bundleName) logger.Warnf("message bundle %s not found", bundleName)
continue continue
} }
@ -396,10 +285,10 @@ func InitI18n(logger *slog.Logger, languages []string) *I18NService {
catalog := initMessageCatalog(logger) catalog := initMessageCatalog(logger)
return &I18NService{logger: logger, bundle: bundle, catalog: catalog} return bundle, catalog
} }
func initMessageCatalog(logger *slog.Logger) *MessageCatalog { func initMessageCatalog(logger *log.Logger) *MessageCatalog {
messages := make(map[string]*i18n.Message) messages := make(map[string]*i18n.Message)
messages["ErrorTitle"] = &i18n.Message{ messages["ErrorTitle"] = &i18n.Message{
ID: "ErrorTitle", ID: "ErrorTitle",

@ -1,43 +0,0 @@
/*
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
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
https://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 (
"crypto/rand"
"errors"
"log/slog"
)
var errCouldNotGenerateKey = errors.New("could not generate key")
func GenerateKey(length int) ([]byte, error) {
key := make([]byte, length)
read, err := rand.Read(key)
if err != nil {
return nil, errCouldNotGenerateKey
}
if read != length {
slog.Error("read unexpected number of bytes", "read", read, "expected", length)
return nil, errCouldNotGenerateKey
}
return key, nil
}

@ -1,37 +0,0 @@
/*
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
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
https://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 (
"github.com/gorilla/sessions"
)
var store *sessions.CookieStore
const (
SessionFullName = iota
SessionEmail
)
func InitSessionStore(keys ...[]byte) {
store = sessions.NewCookieStore(keys...)
}
func GetSessionStore() *sessions.CookieStore {
return store
}

@ -1,39 +1,6 @@
[AuthServerErrorExplanation]
hash = "sha1-d0abcb1757c2a0ebf154112e0f768d41e2ad494c"
other = "Eine Anfrage, die dein Browser an den Authorization-Server geschickt hat, hat einen Fehler verursacht. Der Authorization-Server hat die unten ausgegebenen Details zum Fehler zurückgegeben."
[AuthServerErrorTitle]
hash = "sha1-fa3294b49220d6de6f68825d03195e3f33e88378"
other = "Fehlermeldung vom Authorization-Server"
[ButtonTitleCancel]
description = "Title for a button to cancel an action"
hash = "sha1-0ea9f9f23f1883bb6642b7ec2f083036497dd56b"
other = "Abbrechen"
[ButtonTitleConfirmRevoke]
description = "Title for a button to confirm consent revocation"
hash = "sha1-bb25839982a1fe044fc2ac39552903c65402ad48"
other = "Ja, Widerrufen!"
[ButtonTitleConsent]
description = "Title for a button to give consent"
hash = "sha1-7f5c5d105e7a9600c7a8d3d092c7e812cf344e95"
other = "Zustimmen"
[ButtonTitleDeny]
description = "Title for a button to deny consent"
hash = "sha1-d5245e4b19ed6f0af002aee7ab488b8f822d53c7"
other = "Ablehnen"
[ButtonTitleRevoke]
description = "Title for a button to revoke consent"
hash = "sha1-b4524373ff63f37e062bd5f496e8ba04ee5c678d"
other = "Widerrufen"
[CertLoginIntroText] [CertLoginIntroText]
hash = "sha1-02871569c8d36e522cdb0716081ef62b7c7a71ec" hash = "sha1-e9f7c0522e49ffacc49e3fc35c6ffd31e495baf6"
other = "Die Anwendung **{{ .ClientName }}** fragt nach einer Anmeldung." other = "Die Anwendung <strong>{{ .ClientName }}</strong> fragt nach einer Anmeldung."
[CertLoginRequestText] [CertLoginRequestText]
hash = "sha1-1b20eea0f6fbb4ff139ecfe6b7a93c98cb14b8d7" hash = "sha1-1b20eea0f6fbb4ff139ecfe6b7a93c98cb14b8d7"
@ -43,43 +10,10 @@ other = "Willst du die ausgewählte Identität für die Anmeldung verwenden?"
hash = "sha1-4a6721995b5d87c02be77695910af642ca30b18a" hash = "sha1-4a6721995b5d87c02be77695910af642ca30b18a"
other = "Zusätzlich möchte die Anwendung Zugriff auf folgende Informationen:" other = "Zusätzlich möchte die Anwendung Zugriff auf folgende Informationen:"
[ColumnNameActions]
description = "Title for a table column showing available actions"
hash = "sha1-b1b5e5530afa75d2f7c088486018266a1fbc3456"
other = "Aktionen"
[ColumnNameApplication]
description = "Title for a table column showing application names"
hash = "sha1-28a9f11a0be09f7d8df6e324f3222e105965d315"
other = "Applikation"
[ColumnNameExpires]
description = "Title for a table column showing the expiry date for a consent"
hash = "sha1-6c61015982d7748e6549cf56ee372a8bc130e83a"
other = "Läuft ab"
[ColumnNameGranted]
description = "Title for a table column showing the time when consent has been granted"
hash = "sha1-4ace00a57b59cf745b97f1434d5605ec75722909"
other = "Zugestimmt"
[ColumnNameSubject]
description = "Title for a table column showing the subject of a consent"
hash = "sha1-048ba3497a13fa58c474cf5a400240584b0ee5d5"
other = "Subjekt"
[ConfirmRevokeExplanation]
hash = "sha1-664fae4095fce4ff49ea3eed60293eaea99004a0"
other = "Willst du die Freigabe für den Zugriff auf deine Identitätsdaten für **{{ .Subject }}** für **{{ .Application }}** widerrufen?"
[ConfirmRevokeTitle]
hash = "sha1-13255b407052cfd4a359f6e8e175137b48388a31"
other = "Freigabe widerrufen"
[EmailChoiceText] [EmailChoiceText]
hash = "sha1-8bba8cd3a8724d8c5b75da9b7d2ac084b6e9df90" hash = "sha1-8bba8cd3a8724d8c5b75da9b7d2ac084b6e9df90"
one = "Du hast ein gültiges Client-Zertifikat für die folgende E-Mail-Adresse vorgelegt:" 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, welche davon Du der Anwendung zeigen möchtest:" 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] [ErrorTitle]
hash = "sha1-736aec25a98f5ec5b71400bb0163f891f509b566" hash = "sha1-736aec25a98f5ec5b71400bb0163f891f509b566"
@ -93,25 +27,13 @@ other = "Unbekannter Fehler"
hash = "sha1-7ee5b067009bbedc061274ee50a3027b50a06163" hash = "sha1-7ee5b067009bbedc061274ee50a3027b50a06163"
other = "Wähle eine Identität für die Anmeldung." other = "Wähle eine Identität für die Anmeldung."
[HintChooseDifferentClientCertificate]
hash = "sha1-22002121759ae58afccfccb88383dbe54f94f0a9"
other = "Wähle ein anderes Client-Zertifikat für die Anmeldung."
[IndexTitle]
hash = "sha1-c763022b69a8ad58ab42d8ea708192abd85fd8f6"
other = "Willkommen bei deinem Identitätsprovider"
[IndexWelcomeMessage]
hash = "sha1-00632c6562df53c62861c33e468e729887816419"
other = "Besuche [die Freigabeverwaltung]({{ .ManageConsentHRef }}), um deine Freigaben für Applikationen einzusehen oder zu widerrufen."
[IntroConsentMoreInformation] [IntroConsentMoreInformation]
hash = "sha1-836b2931b417e98db4102731604443ee2700123c" hash = "sha1-f58b8378238bd433deef3c3e6b0b70d0fd0dd59e"
other = "Auf der [Beschreibungsseite]({{ .clientLink }}) findest du mehr Informationen zu **{{ .client }}**." other = "Auf der <a href=\"{{ .clientLink }}\">Beschreibungsseite</a> findest du mehr Informationen zu <strong>{{ .client }}</strong>."
[IntroConsentRequested] [IntroConsentRequested]
hash = "sha1-db5e67a16c181c4d27baf5d0e3bf255224b0fffc" hash = "sha1-3ac6a3583d40b5e8930c57531f0be9706f1e0194"
other = "Die Anwendung **{{ .client }}** hat deine Zustimmung für die Erteilung der folgenden Berechtigungen angefragt:" other = "Die Anwendung <strong>{{ .client }}</strong> hat deine Zustimmung für die Erteilung der folgenden Berechtigungen angefragt:"
[LabelAcceptCertLogin] [LabelAcceptCertLogin]
description = "Label for a button to accept certificate login" description = "Label for a button to accept certificate login"
@ -122,10 +44,6 @@ other = "Ja, bitte"
hash = "sha1-5e56a367cf99015bbe98488845541db00b7e04f6" hash = "sha1-5e56a367cf99015bbe98488845541db00b7e04f6"
other = "Ich erteile hiermit meine Einwilligung, dass die Anwendung die angefragten Berechtigungen erhalten darf." other = "Ich erteile hiermit meine Einwilligung, dass die Anwendung die angefragten Berechtigungen erhalten darf."
[LabelNever]
hash = "sha1-80c3052d33ccdee15ffaaa110c5c39072495fe63"
other = "Nie"
[LabelRejectCertLogin] [LabelRejectCertLogin]
description = "Label for a button to reject certificate login" description = "Label for a button to reject certificate login"
hash = "sha1-49c4d0d1da1c0a7d7e9bf491b28a6e6825f4c2cd" hash = "sha1-49c4d0d1da1c0a7d7e9bf491b28a6e6825f4c2cd"
@ -135,38 +53,14 @@ other = "Nein, schick mich zurück"
hash = "sha1-2dacf65959849884a011f36f76a04eebea94c5ea" hash = "sha1-2dacf65959849884a011f36f76a04eebea94c5ea"
other = "Abschicken" other = "Abschicken"
[LabelUnknown]
hash = "sha1-bc7819b34ff87570745fbe461e36a16f80e562ce"
other = "Unbekannt"
[LoginDeniedByUser] [LoginDeniedByUser]
hash = "sha1-bbad650536bfb091ad55d576262bbe4358277c73" hash = "sha1-bbad650536bfb091ad55d576262bbe4358277c73"
other = "Die Anmeldung wurde durch den Nutzer abgelehnt." other = "Die Anmeldung wurde durch den Nutzer abgelehnt."
[LoginDeniedSubjectMissing]
hash = "sha1-c787e750515612fd695d6a6beeb3a07ec381f3e7"
other = "Die Anmeldung wurde abgelehnt, weil die angeforderte Identität {{ .Subject }} nicht bestätigt werden konnte."
[LoginTitle] [LoginTitle]
hash = "sha1-9a24c8b64e047edc13f3c41ef7785bb2044a6d69" hash = "sha1-9a24c8b64e047edc13f3c41ef7785bb2044a6d69"
other = "Anmelden mit einem Client-Zertifikat" other = "Anmelden mit einem Client-Zertifikat"
[LogoutSuccessfulText]
hash = "sha1-ff5492eab296ca3dd1a783512095be6f195e0acb"
other = "Du wurdest erfolreich abgemeldet."
[LogoutSuccessfulTitle]
hash = "sha1-c92ba3b5f47d0b37b43ba499793a09baa94e5b9d"
other = "Abmeldung erfolgreich"
[ManageConsentDescription]
hash = "sha1-c0f25f17d557ce029f826322a3b625905966e0ea"
other = "Diese Seite zeigt dir die Freigaben für Client-Applikationen, die du in der Vergangenheit erteilt hast und ermöglicht dir, diese zu widerrufen."
[ManageConsentTitle]
hash = "sha1-1c63dd057bea5bfc86befa82e041ab08122f2e52"
other = "Freigaben verwalten"
[NoChallengeInRequestExplanation] [NoChallengeInRequestExplanation]
hash = "sha1-b26a3ef99ecadfbceb62b62eb52e34d197c56c02" hash = "sha1-b26a3ef99ecadfbceb62b62eb52e34d197c56c02"
other = "In Deinem Anmelde-Request fehlt der notwendige `login_challenge`-Parameter. Mehr Informationen zu diesem Parameter findest du in [der ORY Hydra-Dokumentation](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow)." other = "In Deinem Anmelde-Request fehlt der notwendige `login_challenge`-Parameter. Mehr Informationen zu diesem Parameter findest du in [der ORY Hydra-Dokumentation](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow)."
@ -175,14 +69,6 @@ other = "In Deinem Anmelde-Request fehlt der notwendige `login_challenge`-Parame
hash = "sha1-b039c647fea0e42bcb0c877c58da499d082f5319" hash = "sha1-b039c647fea0e42bcb0c877c58da499d082f5319"
other = "Kein Challenge-Parameter im Anmelde-Request" other = "Kein Challenge-Parameter im Anmelde-Request"
[NoConsentGiven]
hash = "sha1-6843ba66b1103d3eed6403f7e276301b852e7ac0"
other = "Du hast noch keiner Applikation eine Freigabe erteilt."
[NoEmailAddressSelected]
hash = "sha1-09fdefe67eae9915e32b18c50baf985d5bd27d36"
other = "Du hast keine E-Mail-Adresse ausgewählt. Bitte wähle eine E-Mail-Adresse, um die Anmeldung fortzusetzen."
[NoEmailsInClientCertificateExplanation] [NoEmailsInClientCertificateExplanation]
hash = "sha1-ab76bd1bed709ebb19a1b9e48c611271551f3343" hash = "sha1-ab76bd1bed709ebb19a1b9e48c611271551f3343"
other = "Das genutzte Client-Zertifikate enthielt keine E-Mail-Adressen.\nEine E-Mail-Adresse ist erforderlich, um dich anzumelden." other = "Das genutzte Client-Zertifikate enthielt keine E-Mail-Adressen.\nEine E-Mail-Adresse ist erforderlich, um dich anzumelden."
@ -214,8 +100,3 @@ other = "Anwendung erbittet deine Zustimmung"
[WrongOrLockedUserOrInvalidPassword] [WrongOrLockedUserOrInvalidPassword]
hash = "sha1-87e0a0ac67c6c3a06bed184e10b22aae4d075b64" hash = "sha1-87e0a0ac67c6c3a06bed184e10b22aae4d075b64"
other = "Du hast einen ungültigen Nutzernamen oder ein ungültiges Passwort eingegeben oder dein Benutzerkonto wurde gesperrt." other = "Du hast einen ungültigen Nutzernamen oder ein ungültiges Passwort eingegeben oder dein Benutzerkonto wurde gesperrt."
[http404]
description = "HTTP error 404 not found"
hash = "sha1-8d7e56f17b2686bc10641795b8785c03b77581cf"
other = "Nicht gefunden"

@ -1,33 +1,17 @@
AuthServerErrorExplanation = "A request that your browser sent to the authorization server caused an error. The authorization server returned details about the error that are printed below." CertLoginIntroText = "The application <strong>{{ .ClientName }}</strong> requests a login."
AuthServerErrorTitle = "Authorization server returned an error"
CertLoginIntroText = "The application **{{ .ClientName }}** requests a login."
CertLoginRequestText = "Do you want to use the chosen identity from the certificate for authentication?" 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:" ClaimsInformation = "In addition the application wants access to the following information:"
ConfirmRevokeExplanation = "Do you want to revoke your consent to allow **{{ .Application }}** access to identity data for **{{ .Subject }}**?"
ConfirmRevokeTitle = "Revoke consent"
ErrorTitle = "An error has occurred" ErrorTitle = "An error has occurred"
ErrorUnknown = "Unknown error" ErrorUnknown = "Unknown error"
HintChooseAnIdentityForAuthentication = "Choose an identity for authentication." HintChooseAnIdentityForAuthentication = "Choose an identity for authentication."
HintChooseDifferentClientCertificate = "Choose a different client certificate for authentication." IntroConsentMoreInformation = "You can find more information about <strong>{{ .client }}</strong> at <a href=\"{{ .clientLink }}\">its description page</a>."
IndexTitle = "Welcome to your identity provider" IntroConsentRequested = "The <strong>{{ .client }}</strong> application requested your consent for the following set of permissions:"
IndexWelcomeMessage = "Go to [manage consent]({{ .ManageConsentHRef }}) to show or revoke consent you have given to client applications."
IntroConsentMoreInformation = "You can find more information about **{{ .client }}** at [its description page]({{ .clientLink }})."
IntroConsentRequested = "The **{{ .client }}** application requested your consent for the following set of permissions:"
LabelConsent = "I hereby agree that the application may get the requested permissions." LabelConsent = "I hereby agree that the application may get the requested permissions."
LabelNever = "Never"
LabelSubmit = "Submit" LabelSubmit = "Submit"
LabelUnknown = "Unknown"
LoginDeniedByUser = "Login has been denied by the user." LoginDeniedByUser = "Login has been denied by the user."
LoginDeniedSubjectMissing = "Login has been denied because the requested subject {{ .Subject }} could not be authenticated."
LoginTitle = "Authenticate with a client certificate" LoginTitle = "Authenticate with a client certificate"
LogoutSuccessfulText = "You have been logged out successfully."
LogoutSuccessfulTitle = "Logout successful"
ManageConsentDescription = "This page allows you to see consent that you have given to client applications in the past."
ManageConsentTitle = "Manage consent"
NoChallengeInRequestExplanation = "Your authentication request did not contain the necessary `login_challenge` parameter. You can find more information about this parameter in [the ORY Hydra documentation](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow)." NoChallengeInRequestExplanation = "Your authentication request did not contain the necessary `login_challenge` parameter. You can find more information about this parameter in [the ORY Hydra documentation](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow)."
NoChallengeInRequestTitle = "No challenge parameter in your authentication request" NoChallengeInRequestTitle = "No challenge parameter in your authentication request"
NoConsentGiven = "You have not given consent to use your data to any application yet."
NoEmailAddressSelected = "You did not select an email address. Please select an email address to continue."
NoEmailsInClientCertificateExplanation = "The presented client certificate does not contain any email address value.\nAn email address is required to authenticate yourself." NoEmailsInClientCertificateExplanation = "The presented client certificate does not contain any email address value.\nAn email address is required to authenticate yourself."
NoEmailsInClientCertificateTitle = "No email addresses in client certificate" NoEmailsInClientCertificateTitle = "No email addresses in client certificate"
Scope-email-Description = "Access your email address." Scope-email-Description = "Access your email address."
@ -37,46 +21,6 @@ Scope-profile-Description = "Access your user profile information (your name)."
TitleRequestConsent = "Application requests your consent" TitleRequestConsent = "Application requests your consent"
WrongOrLockedUserOrInvalidPassword = "You entered an invalid username or password or your account has been locked." WrongOrLockedUserOrInvalidPassword = "You entered an invalid username or password or your account has been locked."
[ButtonTitleCancel]
description = "Title for a button to cancel an action"
other = "Cancel"
[ButtonTitleConfirmRevoke]
description = "Title for a button to confirm consent revocation"
other = "Yes, Revoke!"
[ButtonTitleConsent]
description = "Title for a button to give consent"
other = "Consent"
[ButtonTitleDeny]
description = "Title for a button to deny consent"
other = "Deny"
[ButtonTitleRevoke]
description = "Title for a button to revoke consent"
other = "Revoke"
[ColumnNameActions]
description = "Title for a table column showing available actions"
other = "Actions"
[ColumnNameApplication]
description = "Title for a table column showing application names"
other = "Application"
[ColumnNameExpires]
description = "Title for a table column showing the expiry date for a consent"
other = "Expires"
[ColumnNameGranted]
description = "Title for a table column showing the time when consent has been granted"
other = "Granted at"
[ColumnNameSubject]
description = "Title for a table column showing the subject of a consent"
other = "Subject"
[EmailChoiceText] [EmailChoiceText]
one = "You have presented a valid client certificate for the following email address:" 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:" other = "You have presented a valid client certificate for multiple email addresses. Please choose which one you want to present to the application:"
@ -88,7 +32,3 @@ other = "Yes, please use this identity"
[LabelRejectCertLogin] [LabelRejectCertLogin]
description = "Label for a button to reject certificate login" description = "Label for a button to reject certificate login"
other = "No, please send me back" other = "No, please send me back"
[http404]
description = "HTTP error 404 not found"
other = "Not found"

@ -1,5 +1,5 @@
/* /*
Copyright CAcert Inc. Copyright 2020-2023 CAcert Inc.
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");

@ -26,14 +26,13 @@
<meta name="theme-color" content="#11568c"> <meta name="theme-color" content="#11568c">
<title>{{ .Title }}</title> <title>{{ .Title }}</title>
</head> </head>
<body class="idp d-flex flex-column h-100"> <body class="text-center idp d-flex flex-column h-100">
<header class="container flex-shrink-0"> <main role="main" class="flex-shrink-0">
<a href="/"><img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4"></a>
</header>
{{ template "content" . }} {{ template "content" . }}
</main>
<footer class="footer mt-auto py-3"> <footer class="footer mt-auto py-3">
<div class="container"> <div class="container">
<span class="text-muted small">© <a href="https://www.cacert.org/">CAcert</a></span> <span class="text-muted small">© 2020-2023 <a href="https://www.cacert.org/">CAcert</a></span>
</div> </div>
</footer> </footer>
<script type="text/javascript" src="/js/cacert.bundle.js"></script> <script type="text/javascript" src="/js/cacert.bundle.js"></script>

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

@ -1,11 +0,0 @@
{{ define "content" }}
<main role="main" class="container">
<h1 class="h3 mb-3">{{ .Title }}</h1>
<p class="text-left">{{ .Explanation }}</p>
<form method="post">
{{ .csrfField }}
<button class="btn btn-danger" type="submit">{{ .ButtonTitleRevoke }}</button>
<a class="btn btn-outline-secondary" role="button" href="{{ .CancelLink }}">{{ .ButtonTitleCancel }}</a>
</form>
</main>
{{ end }}

@ -1,13 +1,13 @@
{{ define "content" }} {{ define "content" }}
<main role="main" class="container"> <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> <h1 class="h3 mb-3">{{ .Title }}</h1>
{{ if .LogoURI }} {{ if .client.LogoURI }}
<p class="text-center"> <p>
<img src="{{ .LogoURI }}" alt="{{ .ClientName }}" width="240" height="240" /> <img src="{{ .client.LogoURI }}" alt="{{ .client.ClientName }}"/>
</p> </p>
{{ end }} {{ end }}
{{ .IntroConsentRequested }} <p class="text-left">{{ .IntroConsentRequested }}</p>
<form method="post">
<ul class="list-group text-left small mb-3"> <ul class="list-group text-left small mb-3">
{{ range $i, $scope := .requestedScope }} {{ range $i, $scope := .requestedScope }}
<li class="list-group-item"> <li class="list-group-item">
@ -26,16 +26,15 @@
{{ end}} {{ end}}
</ul> </ul>
{{ end }} {{ end }}
{{ if .HasMoreInformation }} <p class="text-left">{{ .IntroMoreInformation }}</p>
{{ .IntroMoreInformation }}
{{ end }}
{{ .csrfField }} {{ .csrfField }}
{{ .LabelConsent }} <div class="checkbox mb-3">
<label>
<input type="checkbox" name="consent" id="consent" value="true"/>
{{ .LabelConsent }}</label>
</div>
<button class="btn btn-primary" type="submit" name="consent" <button class="btn btn-lg btn-primary btn-block" type="submit">{{ .LabelSubmit }}</button>
value="consent">{{ .ButtonTitleConsent }}</button>
<button class="btn btn-outline-secondary" type="submit" name="consent" value="deny">{{ .ButtonTitleDeny }}</button>
</form> </form>
</main>
{{ end }} {{ end }}

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

@ -1,9 +0,0 @@
{{ define "content" }}
<main class="container">
<h1>{{ .Title }}</h1>
{{ .Explanation }}
{{ if .ErrorMessage }}
<div class="alert alert-danger">{{ .ErrorMessage }}</div>
{{ end }}
</main>
{{ end }}

@ -1,6 +0,0 @@
{{ define "content" }}
<main role="main" class="container">
<h1>{{ .Title }}</h1>
<p>{{ .WelcomeMessage }}</p>
</main>
{{ end }}

@ -1,7 +0,0 @@
{{ define "content" }}
<div class="container">
<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>
</div>
{{ end }}

@ -1,47 +0,0 @@
{{ define "content" }}
<main role="main" class="container">
<h1>{{ .Title }}</h1>
<p class="text-left">{{ .Description }}</p>
{{ range .Flashes}}
<div class="alert alert-{{ .Type }}" role="alert">
{{ .Message }}
</div>
{{ end }}
{{ $buttonTitleRevoke := .ButtonTitleRevoke }}
{{ $unknownLabel := .LabelUnknown }}
{{ $neverLabel := .LabelNever }}
{{ if .ConsentSessions }}
<table class="table table-striped">
<thead>
<tr>
<th>{{ .ApplicationTitle }}</th>
<th>{{ .SubjectTitle }}</th>
<th>{{ .GrantedTitle }}</th>
<th>{{ .ExpiresTitle }}</th>
<th>{{ .ActionsTitle }}</th>
</tr>
</thead>
<tbody>
{{ range .ConsentSessions }}
<tr>
<td>{{ .GetClientName }}</td>
<td>{{ .Subject }}</td>
<td>{{ if .GrantedAt }}<span
title="{{ .GrantedAt }}">{{ .GrantedAt | humantime }}</span>{{ else }}{{ $unknownLabel }}{{ end }}
</td>
<td>{{ if .Expires }}<span
title="{{ .Expires }}">{{ .Expires | humantime }}</span>{{ else }}{{ $neverLabel }}{{ end }}
</td>
<td><a href="/revoke-consent/{{ .GetID }}?subject={{ .Subject }}" class="btn btn-danger"
role="button">{{ $buttonTitleRevoke }}</a></td>
</tr>
{{ end }}
</tbody>
</table>
{{ else}}
<div class="alert alert-info">
{{ .NoConsentGiven }}
</div>
{{ end}}
</main>
{{ end }}

@ -1,6 +1,7 @@
{{ define "content" }} {{ define "content" }}
<main role="main" class="container"> <div class="container">
<h1>{{ .Title }}</h1> <img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4">
<p>{{ .Explanation }}</p> <h1 class="h3 mb-3">{{ .Title }}</h1>
</main> <p class="text-left">{{ .Explanation }}</p>
</div>
{{ end }} {{ end }}

@ -1,6 +1,7 @@
{{ define "content" }} {{ define "content" }}
<main role="main" class="container"> <div class="container">
<h1>{{ .Title }}</h1> <img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4">
<p>{{ .Explanation }}</p> <h1 class="h3 mb-3">{{ .Title }}</h1>
</main> <p class="text-left">{{ .Explanation }}</p>
</div>
{{ end }} {{ end }}

@ -1,20 +1,3 @@
/*
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
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
https://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 ui package ui
import "embed" import "embed"

Loading…
Cancel
Save