Compare commits

..

14 commits
0.3.0 ... main

Author SHA1 Message Date
3e92caf52f Add check to avoid crashes when a claim element's value is nil 2024-08-16 18:05:08 +02:00
cdaed2f4e4 Prepare changelog for 0.4.0 release 2024-05-20 11:47:00 +02:00
9f44a00c63 Fix linter warnings 2024-05-19 16:05:26 +02:00
7ef12da4fa Fix subject handling for login requests
This change implements handling for the case that a login request
retrieved from Hydra has a pre-defined subject. The login request is
rejected if the requested subject is not part of the presented client
certificate.
2024-05-19 08:12:39 +02:00
bdf37493d0 Debug logging for session 2024-05-12 12:16:54 +02:00
407e9acfcc Remove comment 2024-05-12 12:16:18 +02:00
e576d981f9 Reduce minimum CSRF key length to 256 bits 2024-05-12 12:15:58 +02:00
9e54bcabbe Remove copyright years from base template 2024-05-12 12:14:20 +02:00
1e676e8cf1 Switch logging to slog
This commit replaces logrus with slog from the Go standard library.
2024-05-12 01:07:34 +02:00
f22f8ff902 Update golangci-lint, fix warnings
- remove copyright years
- mark unused parameter with _
- add missing empty lines before expressions
2024-05-11 22:42:21 +02:00
f3dc4d71d1 Update dependencies 2024-05-11 22:27:22 +02:00
9aeca21faa Remove duplicate logo from hydra_error template 2023-08-08 15:20:42 +02:00
a5c583f1f6 Render client logo at specific size 2023-08-07 17:58:45 +02:00
56ff01600f Improve consent handling
- hide client logo if there is no logo URI
- hide client information link if there is no client URI
- use buttons instead of a checkbox for consent
- use Markdown for messages
2023-08-07 17:54:54 +02:00
28 changed files with 571 additions and 402 deletions

View file

@ -8,7 +8,7 @@ linters-settings:
const:
ORGANIZATION: CAcert Inc.
template: |-
Copyright {{ YEAR-RANGE }} {{ ORGANIZATION }}
Copyright {{ ORGANIZATION }}
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
@ -47,7 +47,7 @@ linters:
- gofmt
- goheader
- goimports
- gomnd
- mnd
- gosec
- lll
- makezero

View file

@ -66,26 +66,26 @@ internationalization (i18n) support.
The translation workflow needs the `go18n` binary which can be installed via
```
```shell
go install github.com/nicksnyder/go-i18n/v2/goi18n
```
To extract new messages from the code run
```
goi18n extract .
```shell
goi18n extract -outdir translations .
```
Then use
```
goi18n merge active.*.toml
```shell
goi18n merge -outdir translations translations/active.*.toml
```
to create TOML files for translation as `translate.<locale>.toml`. After
translating the messages run
```
```shell
goi18n merge active.*.toml translate.*.toml
```

View file

@ -5,6 +5,16 @@ 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/),
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

View file

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 CAcert Inc.
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
@ -24,6 +24,7 @@ import (
"encoding/base64"
"errors"
"fmt"
"log/slog"
"net/http"
"net/url"
"os"
@ -36,7 +37,6 @@ import (
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/confmap"
hydra "github.com/ory/hydra-client-go/v2"
log "github.com/sirupsen/logrus"
"code.cacert.org/cacert/oidc-idp/internal/handlers"
"code.cacert.org/cacert/oidc-idp/internal/services"
@ -55,6 +55,8 @@ const (
sessionKeyLength = 32
sessionAuthKeyLength = 64
minCSRFKeyLength = 32
)
var (
@ -64,42 +66,61 @@ var (
)
func main() {
logger := log.New()
var (
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)
if err != nil {
logger.WithError(err).Fatal("error loading configuration")
logger.Error("error loading configuration", "err", err)
os.Exit(1)
}
if level := config.String("log.level"); level != "" {
logLevel, err := log.ParseLevel(level)
if level := config.Bytes("log.level"); level != nil {
err := logLevel.UnmarshalText(level)
if err != nil {
logger.WithError(err).Fatal("could not parse log level")
logger.Error("could not parse log level", "error", err)
os.Exit(1)
}
logger.SetLevel(logLevel)
slog.SetLogLoggerLevel(logLevel.Level())
}
if config.Bool("log.json") {
logger.SetFormatter(&log.JSONFormatter{})
logHandler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})
logger = slog.New(logHandler)
slog.SetDefault(logger)
}
logger.WithFields(log.Fields{
"version": version, "commit": commit, "date": date,
}).Info("Starting CAcert OpenID Connect Identity Provider")
logger.Infoln("Server is starting")
logger.Info("Starting CAcert OpenID Connect Identity Provider",
"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 {
logger.WithError(err).Fatal("could not add messages for i18n")
logger.Error("could not add messages for i18n", "error", err)
os.Exit(1)
}
sessionAuthKey, sessionEncKey, csrfKey, err := configureSessionParameters(config)
if err != nil {
logger.Error("could not configure session parameters", "error", err)
os.Exit(1)
}
sessionAuthKey, sessionEncKey := configureSessionParameters(config)
services.InitSessionStore(sessionAuthKey, sessionEncKey)
clientTransport, err := configureAdminClient(config)
if err != nil {
logger.WithError(err).Fatal("could not configure Hydra admin client")
logger.Error("could not configure Hydra admin client", "error", err)
os.Exit(1)
}
tc := handlers.PopulateTemplateCache()
@ -107,12 +128,12 @@ func main() {
needsAuth := handlers.NewAuthMiddleware(logger, tc, i18nService).NeedsAuth
indexHandler := handlers.NewIndex(logger, tc, i18nService)
manageConsentHandler := handlers.NewManageConsent(logger, tc, i18nService, clientTransport.OAuth2Api)
revokeConsentHandler := handlers.NewRevokeConsent(logger, tc, i18nService, clientTransport.OAuth2Api)
manageConsentHandler := handlers.NewManageConsent(logger, tc, i18nService, clientTransport.OAuth2API)
revokeConsentHandler := handlers.NewRevokeConsent(logger, tc, i18nService, clientTransport.OAuth2API)
loginHandler := handlers.NewLoginHandler(logger, tc, i18nService, clientTransport.OAuth2Api)
consentHandler := handlers.NewConsentHandler(logger, tc, i18nService, clientTransport.OAuth2Api)
logoutHandler := handlers.NewLogout(logger, clientTransport.OAuth2Api)
loginHandler := handlers.NewLoginHandler(logger, tc, i18nService, clientTransport.OAuth2API)
consentHandler := handlers.NewConsentHandler(logger, tc, i18nService, clientTransport.OAuth2API)
logoutHandler := handlers.NewLogout(logger, clientTransport.OAuth2API)
logoutSuccessHandler := handlers.NewLogoutSuccess(logger, tc, i18nService)
errorHandler := handlers.NewErrorHandler(logger, i18nService)
@ -132,11 +153,6 @@ func main() {
router.Handle("/css/", staticFiles)
router.Handle("/js/", staticFiles)
csrfKey, err := base64.StdEncoding.DecodeString(config.MustString("security.csrf.key"))
if err != nil {
logger.WithError(err).Fatal("could not parse CSRF key bytes")
}
nextRequestID := func() string {
return fmt.Sprintf("%d", time.Now().UnixNano())
}
@ -152,12 +168,17 @@ func main() {
errorMiddleware, err := handlers.ErrorHandling(logger, tc, i18nService)
if err != nil {
logger.WithError(err).Fatal("could not initialize request error handling")
logger.Error("could not initialize request error handling", "error", err)
os.Exit(1)
}
handlerChain := tracing(logging(hsts(errorMiddleware(csrfProtect(router)))))
startServer(logger, config, handlerChain)
err = startServer(logger, config, handlerChain)
if err != nil {
logger.Error("server start failed", "error", err)
os.Exit(1)
}
}
func configureAdminClient(config *koanf.Koanf) (*hydra.APIClient, error) {
@ -197,7 +218,7 @@ func configureAdminClient(config *koanf.Koanf) (*hydra.APIClient, error) {
return c, nil
}
func startServer(logger *log.Logger, config *koanf.Koanf, handlerChain http.Handler) {
func startServer(logger *slog.Logger, config *koanf.Koanf, handlerChain http.Handler) error {
clientCertificateCAFile := config.MustString("security.client.ca-file")
serverBindAddress := config.String("server.bind_address")
serverName := config.String("server.name")
@ -207,7 +228,7 @@ func startServer(logger *log.Logger, config *koanf.Koanf, handlerChain http.Hand
pemBytes, err := os.ReadFile(clientCertificateCAFile)
if err != nil {
logger.WithError(err).Fatal("could not load client CA certificates")
return fmt.Errorf("could not load client CA certificates: %w", err)
}
clientCertPool.AppendCertsFromPEM(pemBytes)
@ -233,7 +254,7 @@ func startServer(logger *log.Logger, config *koanf.Koanf, handlerChain http.Hand
go func() {
<-quit
logger.Infoln("Server is shutting down...")
logger.Info("Server is shutting down...")
atomic.StoreInt32(&handlers.Healthy, 0)
ctx, cancel := context.WithTimeout(context.Background(), ShutdownTimeout)
@ -242,27 +263,30 @@ func startServer(logger *log.Logger, config *koanf.Koanf, handlerChain http.Hand
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil {
logger.WithError(err).Fatal("Could not gracefully shutdown the server")
logger.Error("could not shutdown server gracefully", "error", err)
}
close(done)
}()
logger.WithFields(log.Fields{
"address": server.Addr, "url": publicAddress(serverName, serverPort),
}).Info("Server is ready to handle requests")
logger.Info(
"Server is ready to handle requests",
"address", server.Addr, "url", publicAddress(serverName, serverPort),
)
atomic.StoreInt32(&handlers.Healthy, 1)
if err := server.ListenAndServeTLS(
config.String("server.certificate"), config.String("server.key"),
); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.WithError(err).WithField(
"server_addr", server.Addr,
).Fatal("Could not listen on configured server address")
logger.Error("could not listen on configured server address", "server_addr", server.Addr)
return fmt.Errorf("could not create listener: %w", err)
}
<-done
logger.Infoln("Server stopped")
logger.Info("Server stopped")
return nil
}
func publicAddress(serverName string, serverPort int) string {
@ -273,42 +297,66 @@ func publicAddress(serverName string, serverPort int) string {
return fmt.Sprintf("https://%s/", serverName)
}
func configureSessionParameters(config *koanf.Koanf) ([]byte, []byte) {
func configureSessionParameters(config *koanf.Koanf) ([]byte, []byte, []byte, error) { //nolint:cyclop
sessionAuthKey, err := base64.StdEncoding.DecodeString(config.String("session.auth-key"))
if err != nil {
log.WithError(err).Fatal("could not decode session auth key")
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 {
log.WithError(err).Fatal("could not decode session encryption key")
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 = services.GenerateKey(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 = services.GenerateKey(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": sessionAuthKey,
"session.enc-key": sessionEncKey,
"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 {
log.WithError(err).Fatal("could not encode session config")
return nil, nil, nil, fmt.Errorf("could not encode session config: %w", err)
}
log.Infof("put the following in your resource_app.toml:\n%s", string(tomlData))
slog.Info("generated configuration values, put the following into your idp.toml")
fmt.Printf("------\n%s------\n", string(tomlData)) //nolint:forbidigo
}
return sessionAuthKey, sessionEncKey
return sessionAuthKey, sessionEncKey, csrfKey, nil
}

37
go.mod
View file

@ -1,31 +1,29 @@
module code.cacert.org/cacert/oidc-idp
go 1.19
go 1.22
require (
github.com/BurntSushi/toml v1.3.2
github.com/dustin/go-humanize v1.0.0
github.com/dustin/go-humanize v1.0.1
github.com/go-playground/form/v4 v4.2.1
github.com/gorilla/csrf v1.7.1
github.com/gorilla/sessions v1.2.1
github.com/gorilla/csrf v1.7.2
github.com/gorilla/sessions v1.2.2
github.com/knadh/koanf v1.5.0
github.com/lestrrat-go/jwx v1.2.26
github.com/nicksnyder/go-i18n/v2 v2.2.1
github.com/ory/hydra-client-go/v2 v2.1.1
github.com/sirupsen/logrus v1.9.3
github.com/lestrrat-go/jwx v1.2.29
github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/ory/hydra-client-go/v2 v2.2.0
github.com/spf13/pflag v1.0.5
github.com/yuin/goldmark v1.5.5
golang.org/x/text v0.11.0
github.com/yuin/goldmark v1.7.1
golang.org/x/text v0.15.0
)
require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
@ -34,10 +32,7 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/net v0.13.0 // indirect
golang.org/x/oauth2 v0.10.0 // indirect
golang.org/x/sys v0.10.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/oauth2 v0.20.0 // indirect
golang.org/x/sys v0.20.0 // indirect
)

89
go.sum
View file

@ -1,7 +1,6 @@
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=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -39,10 +38,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
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/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
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.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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -52,8 +53,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/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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
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.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -89,8 +90,6 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -103,14 +102,17 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
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.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.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
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/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE=
github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
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/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
@ -172,14 +174,14 @@ 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/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
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/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx v1.2.26 h1:4iFo8FPRZGDYe1t19mQP0zTRqA7n8HnJ5lkIiDvJcB0=
github.com/lestrrat-go/jwx v1.2.26/go.mod h1:MaiCdGbn3/cckbOFSCluJlJMmp9dmZm5hDuIkx8ftpQ=
github.com/lestrrat-go/jwx v1.2.29 h1:QT0utmUJ4/12rmsVQrJ3u55bycPkKqGYuGT4tyRhxSQ=
github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtXSNFeZuB8=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
@ -216,12 +218,12 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
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/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
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/ory/hydra-client-go/v2 v2.1.1 h1:3JatU9uFbw5XhF3lgPCas1l1Kok2v5Mq1p26zZwGHNg=
github.com/ory/hydra-client-go/v2 v2.1.1/go.mod h1:IiIwChp/9wRvPoyFQblqPvg78uVishCCrV9+/M7Pl34=
github.com/ory/hydra-client-go/v2 v2.2.0 h1:g8hw0YQD5Us1aAgZj7OyBmBGSDwlnY9/2Pb/pQQq8YE=
github.com/ory/hydra-client-go/v2 v2.2.0/go.mod h1:h0DSI2kQA3S2fN7HyD8DNWcvbgDmYRSxfhwu/mSBhH8=
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/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
@ -260,14 +262,13 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
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.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.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.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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -276,14 +277,15 @@ 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.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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.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.5.5 h1:IJznPe8wOzfIKETmMkd06F8nXkmlhaHqFRM9l1hAGsU=
github.com/yuin/goldmark v1.5.5/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
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/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
@ -296,9 +298,10 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -317,7 +320,6 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -331,13 +333,12 @@ golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy
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.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -381,17 +382,19 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
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-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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.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-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.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=
@ -399,11 +402,11 @@ 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.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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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/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=
@ -424,8 +427,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
@ -451,8 +452,6 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View file

@ -1,5 +1,5 @@
/*
Copyright 2023 CAcert Inc.
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
@ -22,12 +22,12 @@ import (
"context"
"fmt"
"html/template"
"log/slog"
"net/http"
"github.com/dustin/go-humanize"
"github.com/gorilla/sessions"
"github.com/nicksnyder/go-i18n/v2/i18n"
log "github.com/sirupsen/logrus"
"code.cacert.org/cacert/oidc-idp/ui"
@ -46,7 +46,7 @@ func GetSession(r *http.Request) (*sessions.Session, error) {
}
type AuthMiddleware struct {
logger *log.Logger
logger *slog.Logger
trans *services.I18NService
templates TemplateCache
}
@ -83,7 +83,7 @@ func GetAuthenticatedAddresses(r *http.Request) []string {
return nil
}
func NewAuthMiddleware(logger *log.Logger, tc TemplateCache, trans *services.I18NService) *AuthMiddleware {
func NewAuthMiddleware(logger *slog.Logger, tc TemplateCache, trans *services.I18NService) *AuthMiddleware {
return &AuthMiddleware{logger: logger, trans: trans, templates: tc}
}
@ -104,13 +104,13 @@ const (
type TemplateCache map[templateName]*template.Template
func (c TemplateCache) render(
logger *log.Logger, w http.ResponseWriter, name templateName, params map[string]interface{},
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.WithError(err).Error("template rendering failed")
logger.Error("template rendering failed", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
@ -166,7 +166,7 @@ func PopulateTemplateCache() TemplateCache {
}
func renderNoEmailsInClientCertificate(
logger *log.Logger,
logger *slog.Logger,
templates TemplateCache,
trans *services.I18NService,
w http.ResponseWriter,
@ -179,7 +179,7 @@ func renderNoEmailsInClientCertificate(
"Explanation": msg("NoEmailsInClientCertificateExplanation", nil, localizer),
})
if err != nil {
logger.WithError(err).Error("template rendering failed")
logger.Error("template rendering failed", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return

View file

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 CAcert Inc.
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
@ -22,6 +22,7 @@ import (
"errors"
"fmt"
"html/template"
"log/slog"
"net/http"
"net/url"
"strings"
@ -33,23 +34,22 @@ import (
"github.com/lestrrat-go/jwx/jwt/openid"
"github.com/nicksnyder/go-i18n/v2/i18n"
client "github.com/ory/hydra-client-go/v2"
log "github.com/sirupsen/logrus"
"code.cacert.org/cacert/oidc-idp/internal/models"
"code.cacert.org/cacert/oidc-idp/internal/services"
)
type ConsentHandler struct {
logger *log.Logger
logger *slog.Logger
trans *services.I18NService
adminClient client.OAuth2Api
adminClient client.OAuth2API
templates TemplateCache
}
type ConsentInformation struct {
GrantedScopes []string `form:"scope"`
SelectedClaims []string `form:"claims"`
ConsentChecked bool `form:"consent"`
ConsentAction string `form:"consent"`
}
type UserInfo struct {
@ -108,7 +108,7 @@ func (i *UserInfo) GetFullName() string {
func (h *ConsentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
challenge := r.URL.Query().Get("consent_challenge")
h.logger.WithField("consent_challenge", challenge).Debug("received consent challenge")
h.logger.Debug("received consent challenge", "consent_challenge", challenge)
localizer := getLocalizer(h.trans, r)
@ -121,7 +121,7 @@ func (h *ConsentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(r)
if err != nil {
h.logger.WithError(err).Error("could get session for request")
h.logger.Error("could get session for request", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -149,7 +149,7 @@ func (h *ConsentHandler) handleGet(
if consentData.GetSkip() {
consentRequest, err := h.handleExistingConsent(consentData, requestedClaims, session)
if err != nil {
h.logger.WithError(err).Error("could not handle existing consent")
h.logger.Error("could not handle existing consent", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -158,7 +158,7 @@ func (h *ConsentHandler) handleGet(
err = h.acceptConsent(w, r, challenge, consentRequest)
if err != nil {
h.logger.WithError(err).Error("could not accept consent")
h.logger.Error("could not accept consent", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
@ -182,23 +182,23 @@ func (h *ConsentHandler) handlePost(
// validate input
decoder := form.NewDecoder()
if err := decoder.Decode(&consentInfo, r.Form); err != nil {
h.logger.WithError(err).Error("could not decode consent form")
h.logger.Error("could not decode consent form", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
if consentInfo.ConsentChecked {
if consentInfo.ConsentAction == "consent" {
consentRequest, err := h.rememberNewConsent(consentData, consentInfo, requestedClaims, session)
if err != nil {
h.logger.WithError(err).Error("could not accept consent")
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 {
h.logger.WithError(err).Error("could not accept consent")
h.logger.Error("could not accept consent", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
@ -210,7 +210,7 @@ func (h *ConsentHandler) handlePost(
r.Context(),
).ConsentChallenge(challenge).Execute()
if err != nil {
h.logger.WithError(err).Error("reject consent request failed")
h.logger.Error("reject consent request failed", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
@ -218,9 +218,10 @@ func (h *ConsentHandler) handlePost(
defer func() { _ = response.Body.Close() }()
h.logger.WithFields(
log.Fields{"response": response.Status, "reject_consent_request": consentRequest},
).Debug("received response for RejectOAuth2ConsentRequest")
h.logger.Debug(
"received response for RejectOAuth2ConsentRequest",
"response", response.Status, "reject_consent_request", consentRequest,
)
w.Header().Add("Location", consentRequest.GetRedirectTo())
w.WriteHeader(http.StatusFound)
@ -266,9 +267,10 @@ func (h *ConsentHandler) acceptConsent(
defer func() { _ = response.Body.Close() }()
h.logger.WithFields(
log.Fields{"response": response.Status, "redirect_to": oAuth2RedirectTo},
).Debug("received response for AcceptOAuth2ConsentRequest")
h.logger.Debug(
"received response for AcceptOAuth2ConsentRequest",
"response", response.Status, "redirect_to", oAuth2RedirectTo,
)
w.Header().Add("Location", oAuth2RedirectTo.GetRedirectTo())
w.WriteHeader(http.StatusFound)
@ -305,7 +307,7 @@ func (h *ConsentHandler) getRequestedConsentInformation(challenge string, r *htt
r.Context(),
).ConsentChallenge(challenge).Execute()
if err != nil {
h.logger.WithError(err).Error("error getting consent information")
h.logger.Error("error getting consent information", "error", err)
if errorBucket := GetErrorBucket(r); errorBucket != nil {
errorDetails := &ErrorDetails{
@ -321,9 +323,10 @@ func (h *ConsentHandler) getRequestedConsentInformation(challenge string, r *htt
defer func() { _ = response.Body.Close() }()
h.logger.WithFields(
log.Fields{"response": response.Status, "consent_request": consentRequest},
).Debug("response for GetOAuth2ConsentRequest")
h.logger.Debug(
"response for GetOAuth2ConsentRequest",
"response", response.Status, "consent_request", consentRequest,
)
var requestedClaims models.OIDCClaimsRequest
@ -331,18 +334,21 @@ func (h *ConsentHandler) getRequestedConsentInformation(challenge string, r *htt
requestURL, err := url.Parse(requestURLStr)
if err != nil {
h.logger.WithError(err).WithField(
"request_url", requestURLStr,
).Warn("could not parse original request URL")
h.logger.Warn(
"could not parse original request URL",
"error", err, "request_url", requestURLStr,
)
} else {
claimsParameter := requestURL.Query().Get("claims")
if claimsParameter != "" {
decoder := json.NewDecoder(strings.NewReader(claimsParameter))
err := decoder.Decode(&requestedClaims)
if err != nil {
h.logger.WithError(err).WithField(
"claims_parameter", claimsParameter,
).Warn("ignoring claims request parameter that could not be decoded")
h.logger.Warn(
"ignoring claims request parameter that could not be decoded",
"error", err, "claims_parameter", claimsParameter,
)
}
}
}
@ -357,36 +363,42 @@ func (h *ConsentHandler) renderConsentForm(
claims *models.OIDCClaimsRequest,
localizer *i18n.Localizer,
) {
trans := func(id string, values ...map[string]interface{}) string {
if len(values) > 0 {
return h.trans.LookupMessage(id, values[0], localizer)
}
return h.trans.LookupMessage(id, nil, localizer)
trans := h.trans.LookupMessage
transMarkdown := func(id string, params map[string]interface{}, localizer *i18n.Localizer) template.HTML {
return template.HTML( //nolint:gosec
h.trans.LookupMarkdownMessage(id, params, localizer),
)
}
// render consent form
oAuth2Client := consentRequest.Client
clientLogoURI := oAuth2Client.GetLogoUri()
clientName := template.HTMLEscaper(oAuth2Client.GetClientName())
clientURI := oAuth2Client.GetClientUri()
h.templates.render(h.logger, w, Consent, map[string]interface{}{
"Title": trans("TitleRequestConsent"),
"Title": trans("TitleRequestConsent", nil, localizer),
csrf.TemplateTag: csrf.TemplateField(r),
"errors": map[string]string{},
"client": oAuth2Client,
"LogoURI": clientLogoURI,
"ClientName": clientName,
"requestedScope": h.mapRequestedScope(consentRequest.RequestedScope, localizer),
"requestedClaims": h.mapRequestedClaims(claims, localizer),
"LabelSubmit": trans("LabelSubmit"),
"LabelConsent": trans("LabelConsent"),
"IntroMoreInformation": template.HTML( //nolint:gosec
trans("IntroConsentMoreInformation", map[string]interface{}{
"client": oAuth2Client.GetClientName(),
"clientLink": oAuth2Client.GetClientUri(),
})),
"ClaimsInformation": template.HTML( //nolint:gosec
trans("ClaimsInformation", nil)),
"IntroConsentRequested": template.HTML( //nolint:gosec
trans("IntroConsentRequested", map[string]interface{}{
"client": oAuth2Client.GetClientName(),
})),
"ButtonTitleConsent": trans("ButtonTitleConsent", nil, localizer),
"ButtonTitleDeny": trans("ButtonTitleDeny", nil, localizer),
"HasMoreInformation": clientURI != "",
"IntroMoreInformation": transMarkdown(
"IntroConsentMoreInformation", map[string]interface{}{
"client": clientName,
"clientLink": clientURI,
}, localizer),
"LabelConsent": transMarkdown("LabelConsent", nil, localizer),
"ClaimsInformation": transMarkdown(
"ClaimsInformation", nil, localizer),
"IntroConsentRequested": transMarkdown(
"IntroConsentRequested", map[string]interface{}{
"client": clientName,
}, localizer),
})
}
@ -403,7 +415,7 @@ func (h *ConsentHandler) mapRequestedScope(
for _, scopeName := range scope {
if _, ok := supportedScopes[scopeName]; !ok {
h.logger.WithField("scope", scopeName).Warn("ignoring unsupported scope")
h.logger.Warn("ignoring unsupported scope", "scope", scopeName)
continue
}
@ -412,7 +424,7 @@ func (h *ConsentHandler) mapRequestedScope(
DefaultMessage: supportedScopes[scopeName],
})
if err != nil {
h.logger.WithError(err).WithField("scope", scopeName).Warn("could not localize scope label")
h.logger.Warn("could not localize scope label", "error", err, "scope", scopeName)
label = scopeName
}
@ -438,8 +450,14 @@ func (h *ConsentHandler) mapRequestedClaims(
for _, claimElement := range []*models.ClaimElement{claims.GetUserInfo(), claims.GetIDToken()} {
if claimElement != nil {
for k, v := range *claimElement {
if v == nil {
h.logger.Warn("claim element is nil", "key", k)
continue
}
if _, ok := supportedClaims[k]; !ok {
h.logger.WithField("claim", k).Warn("ignoring unsupported claim")
h.logger.Warn("ignoring unsupported claim", "claim", k)
continue
}
@ -448,7 +466,7 @@ func (h *ConsentHandler) mapRequestedClaims(
DefaultMessage: supportedClaims[k],
})
if err != nil {
h.logger.WithError(err).WithField("claim", k).Warn("could not localize claim label")
h.logger.Warn("could not localize claim label", "error", err, "claim", k)
label = k
}
@ -578,9 +596,9 @@ func (h *ConsentHandler) parseUserInfoClaims(
}
if claim.IsEssential() {
h.logger.WithField("claim", claimName).Warn("handling for essential claim not implemented")
h.logger.Warn("handling for essential claim not implemented", "claim", claimName)
} else {
h.logger.WithField("claim", claimName).Warn("handling for claim not implemented")
h.logger.Warn("handling for claim not implemented", "claim", claimName)
}
}
@ -588,7 +606,7 @@ func (h *ConsentHandler) parseUserInfoClaims(
}
func NewConsentHandler(
logger *log.Logger, templateCache TemplateCache, trans *services.I18NService, adminClient client.OAuth2Api,
logger *slog.Logger, templateCache TemplateCache, trans *services.I18NService, adminClient client.OAuth2API,
) *ConsentHandler {
return &ConsentHandler{
logger: logger,

View file

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

View file

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 CAcert Inc.
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
@ -22,10 +22,9 @@ import (
"context"
"fmt"
"html/template"
"log/slog"
"net/http"
log "github.com/sirupsen/logrus"
"code.cacert.org/cacert/oidc-idp/internal/services"
"code.cacert.org/cacert/oidc-idp/ui"
)
@ -44,7 +43,7 @@ type ErrorDetails struct {
}
type ErrorBucket struct {
logger *log.Logger
logger *slog.Logger
trans *services.I18NService
errorDetails *ErrorDetails
templates TemplateCache
@ -118,9 +117,7 @@ func (w *errorResponseWriter) Write(content []byte) (int, error) {
}
func ErrorHandling(
logger *log.Logger,
templateCache TemplateCache,
trans *services.I18NService,
logger *slog.Logger, templateCache TemplateCache, trans *services.I18NService,
) (func(http.Handler) http.Handler, error) {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -139,7 +136,7 @@ func ErrorHandling(
}
type ErrorHandler struct {
logger *log.Logger
logger *slog.Logger
trans *services.I18NService
template *template.Template
}
@ -156,10 +153,10 @@ func (h *ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
errorName := r.URL.Query().Get("error")
errorDescription := r.URL.Query().Get("error_description")
h.logger.WithFields(log.Fields{
"error_name": errorName,
"error_description": errorDescription,
}).Debug("error from Hydra")
h.logger.Debug(
"error from Hydra",
"error_name", errorName, "error_description", errorDescription,
)
rendered := bytes.NewBuffer(make([]byte, 0))
@ -174,7 +171,7 @@ func (h *ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
"ErrorMessage": errorDescription,
})
if err != nil {
h.logger.WithError(err).Error("template rendering failed")
h.logger.Error("template rendering failed", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
@ -186,7 +183,7 @@ func (h *ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write(rendered.Bytes())
}
func NewErrorHandler(logger *log.Logger, trans *services.I18NService) *ErrorHandler {
func NewErrorHandler(logger *slog.Logger, trans *services.I18NService) *ErrorHandler {
return &ErrorHandler{
logger: logger,
trans: trans,

View file

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 CAcert Inc.
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
@ -23,12 +23,12 @@ import (
"fmt"
"html/template"
"io"
"log/slog"
"net/http"
"github.com/gorilla/csrf"
"github.com/nicksnyder/go-i18n/v2/i18n"
client "github.com/ory/hydra-client-go/v2"
log "github.com/sirupsen/logrus"
"code.cacert.org/cacert/oidc-idp/internal/services"
)
@ -49,9 +49,9 @@ const (
)
type LoginHandler struct {
logger *log.Logger
logger *slog.Logger
trans *services.I18NService
adminClient client.OAuth2Api
adminClient client.OAuth2API
templates TemplateCache
}
@ -72,7 +72,7 @@ func (h *LoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
h.logger.WithField("challenge", challenge).Debug("received login challenge")
h.logger.Debug("received login challenge", "challenge", challenge)
certFullName, certEmails := getDataFromClientCert(h.logger, r)
@ -100,22 +100,48 @@ func (h *LoginHandler) handleGet(
r.Context(),
).LoginChallenge(challenge).Execute()
if err != nil {
h.logger.WithError(err).WithField(
"challenge", challenge,
).Warn("could not get login request for challenge")
h.logger.Warn(
"could not get login request for challenge",
"error", err, "challenge", challenge,
)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
usableEmails := certEmails
defer func() { _ = response.Body.Close() }()
h.logger.WithFields(
log.Fields{"response": response.Status, "login_request": oAuth2LoginRequest},
).Debug("got response for GetOAuth2LoginRequest")
h.logger.Debug(
"got response for GetOAuth2LoginRequest",
"response", response.Status, "login_request", oAuth2LoginRequest,
)
h.renderRequestForClientCert(w, r, certEmails, localizer, oAuth2LoginRequest)
if subject, ok := oAuth2LoginRequest.GetSubjectOk(); ok && *subject != "" {
h.logger.Info("oauth2LoginRequest expects subject", "subject", *subject)
subjectInCert := false
for _, email := range certEmails {
if *subject == email {
subjectInCert = true
break
}
}
if !subjectInCert {
h.rejectLoginMissingSubject(w, r, challenge, localizer, *subject)
return
}
usableEmails = []string{*subject}
}
h.renderRequestForClientCert(w, r, usableEmails, localizer, oAuth2LoginRequest)
}
type FlashMessage struct {
@ -151,14 +177,14 @@ func (h *LoginHandler) handlePost(
}
// perform certificate auth
h.logger.WithFields(log.Fields{
"emails": certEmails,
"full_name": certFullName,
}).Info("will perform certificate authentication")
h.logger.Info(
"will perform certificate authentication",
"emails", certEmails, "full_name", certFullName,
)
userID, err := h.performCertificateLogin(certEmails, r)
if err != nil {
h.logger.WithError(err).Error("could not perform certificate login")
h.logger.Error("could not perform certificate login", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
@ -166,7 +192,7 @@ func (h *LoginHandler) handlePost(
session, err := GetSession(r)
if err != nil {
h.logger.WithError(err).Error("could not perform certificate login")
h.logger.Error("could not perform certificate login", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
@ -178,7 +204,7 @@ func (h *LoginHandler) handlePost(
session.Options.Secure = true
if err = session.Save(r, w); err != nil {
h.logger.WithError(err).Error("could not save session")
h.logger.Error("could not save session", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
@ -194,9 +220,8 @@ func (h *LoginHandler) handlePost(
r.Context(),
).LoginChallenge(challenge).AcceptOAuth2LoginRequest(*acceptRequest).Execute()
if err != nil {
h.logger.WithError(err).Error("error getting login request")
h.logger.Error("error getting login request", "error", err)
// h.fillAcceptLoginRequestErrorBucket(r, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
@ -204,13 +229,13 @@ func (h *LoginHandler) handlePost(
defer func() { _ = response.Body.Close() }()
h.logger.WithFields(
log.Fields{"response": response.Status, "accept_login_request": loginRequest},
).Debug("got response for AcceptOAuth2LoginRequest")
h.logger.Debug("got response for AcceptOAuth2LoginRequest",
"response", response.Status, "accept_login_request", loginRequest,
)
if h.logger.IsLevelEnabled(log.TraceLevel) {
if h.logger.Enabled(r.Context(), slog.LevelDebug) {
if rb, err := io.ReadAll(response.Body); err == nil {
h.logger.WithField("response_body", rb).Trace("response body from Hydra")
h.logger.Debug("response body from Hydra", "response_body", rb)
}
}
@ -218,36 +243,6 @@ func (h *LoginHandler) handlePost(
w.WriteHeader(http.StatusFound)
}
/*
func (h *LoginHandler) fillAcceptLoginRequestErrorBucket(r *http.Request, err error) {
if errorBucket := GetErrorBucket(r); errorBucket != nil {
var (
errorDetails *ErrorDetails
acceptLoginRequestNotFound *client.AcceptLoginRequestNotFound
)
if errors.As(err, &acceptLoginRequestNotFound) {
payload := acceptLoginRequestNotFound.GetPayload()
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()},
}
}
errorBucket.AddError(errorDetails)
}
}
*/
func (h *LoginHandler) rejectLogin(
w http.ResponseWriter,
r *http.Request,
@ -263,7 +258,7 @@ func (h *LoginHandler) rejectLogin(
r.Context(),
).LoginChallenge(challenge).RejectOAuth2Request(*rejectRequest).Execute()
if err != nil {
h.logger.WithError(err).Error("error getting reject login request")
h.logger.Error("error sending reject login request", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
@ -271,9 +266,44 @@ func (h *LoginHandler) rejectLogin(
defer func() { _ = response.Body.Close() }()
h.logger.WithFields(
log.Fields{"response": response.Status, "reject_login_request": rejectLoginRequest},
).Debug("go response for RejectOAuth2LoginRequest")
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)
}
func (h *LoginHandler) rejectLoginMissingSubject(
w http.ResponseWriter, r *http.Request, challenge string, localizer *i18n.Localizer, subject string,
) {
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(
r.Context()).LoginChallenge(challenge).RejectOAuth2Request(
*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
}
defer func() { _ = response.Body.Close() }()
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)
@ -308,7 +338,7 @@ func (h *LoginHandler) renderRequestForClientCert(
"FlashMessage": r.Context().Value(ctxKeyMessage),
})
if err != nil {
h.logger.WithError(err).Error("template rendering failed")
h.logger.Error("template rendering failed", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
@ -340,7 +370,7 @@ func (h *LoginHandler) renderNoChallengeInRequest(w http.ResponseWriter, localiz
)),
})
if err != nil {
h.logger.WithError(err).Error("template rendering failed")
h.logger.Error("template rendering failed", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
@ -348,7 +378,7 @@ func (h *LoginHandler) renderNoChallengeInRequest(w http.ResponseWriter, localiz
}
func NewLoginHandler(
logger *log.Logger, tc TemplateCache, trans *services.I18NService, adminClient client.OAuth2Api,
logger *slog.Logger, tc TemplateCache, trans *services.I18NService, adminClient client.OAuth2API,
) *LoginHandler {
return &LoginHandler{
logger: logger,

View file

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 CAcert Inc.
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
@ -19,28 +19,28 @@ package handlers
import (
"html/template"
"log/slog"
"net/http"
client "github.com/ory/hydra-client-go/v2"
log "github.com/sirupsen/logrus"
"code.cacert.org/cacert/oidc-idp/internal/services"
)
type LogoutHandler struct {
adminClient client.OAuth2Api
logger *log.Logger
adminClient client.OAuth2API
logger *slog.Logger
}
func (h *LogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
challenge := r.URL.Query().Get("logout_challenge")
h.logger.WithField("challenge", challenge).Debug("received logout challenge")
h.logger.Debug("received logout challenge", "challenge", challenge)
logoutRequest, response, err := h.adminClient.GetOAuth2LogoutRequest(
r.Context(),
).LogoutChallenge(challenge).Execute()
if err != nil {
h.logger.WithError(err).Error("error getting logout requests")
h.logger.Error("error getting logout requests", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
@ -48,29 +48,31 @@ func (h *LogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer func() { _ = response.Body.Close() }()
h.logger.WithFields(
log.Fields{"response": response.Status, "logout_request": logoutRequest},
).Debug("got response for GetOAuth2LogoutRequest")
h.logger.Debug(
"got response for GetOAuth2LogoutRequest",
"response", response.Status, "logout_request", logoutRequest,
)
acceptLogoutRequest, response, err := h.adminClient.AcceptOAuth2LogoutRequest(
r.Context(),
).LogoutChallenge(challenge).Execute()
if err != nil {
h.logger.WithError(err).Error("accept logout request failed")
h.logger.Error("accept logout request failed", "error", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
defer func() { _ = response.Body.Close() }()
h.logger.WithFields(
log.Fields{"response": response.Status, "accept_logout_request": acceptLogoutRequest},
).Debug("got response for AcceptOAuth2LogoutRequest")
h.logger.Debug(
"got response for AcceptOAuth2LogoutRequest",
"response", response.Status, "accept_logout_request", acceptLogoutRequest,
)
w.Header().Set("Location", acceptLogoutRequest.GetRedirectTo())
w.WriteHeader(http.StatusFound)
}
func NewLogout(logger *log.Logger, adminClient client.OAuth2Api) *LogoutHandler {
func NewLogout(logger *slog.Logger, adminClient client.OAuth2API) *LogoutHandler {
return &LogoutHandler{
logger: logger,
adminClient: adminClient,
@ -78,7 +80,7 @@ func NewLogout(logger *log.Logger, adminClient client.OAuth2Api) *LogoutHandler
}
type LogoutSuccessHandler struct {
logger *log.Logger
logger *slog.Logger
trans *services.I18NService
templates TemplateCache
}
@ -103,9 +105,7 @@ func (h *LogoutSuccessHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
}
func NewLogoutSuccess(
logger *log.Logger,
templateCache TemplateCache,
trans *services.I18NService,
logger *slog.Logger, templateCache TemplateCache, trans *services.I18NService,
) *LogoutSuccessHandler {
return &LogoutSuccessHandler{logger: logger, trans: trans, templates: templateCache}
}

View file

@ -1,5 +1,5 @@
/*
Copyright 2023 CAcert Inc.
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
@ -22,6 +22,7 @@ import (
"fmt"
"html/template"
"io"
"log/slog"
"net/http"
"sort"
"strings"
@ -30,13 +31,12 @@ import (
"github.com/gorilla/csrf"
"github.com/nicksnyder/go-i18n/v2/i18n"
client "github.com/ory/hydra-client-go/v2"
log "github.com/sirupsen/logrus"
"code.cacert.org/cacert/oidc-idp/internal/services"
)
type IndexHandler struct {
logger *log.Logger
logger *slog.Logger
trans *services.I18NService
templates TemplateCache
}
@ -66,14 +66,14 @@ func (h *IndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
})
}
func NewIndex(logger *log.Logger, templateCache TemplateCache, trans *services.I18NService) *IndexHandler {
func NewIndex(logger *slog.Logger, templateCache TemplateCache, trans *services.I18NService) *IndexHandler {
return &IndexHandler{logger: logger, trans: trans, templates: templateCache}
}
type ManageConsentHandler struct {
logger *log.Logger
logger *slog.Logger
trans *services.I18NService
adminAPI client.OAuth2Api
adminAPI client.OAuth2API
templates TemplateCache
}
@ -107,13 +107,15 @@ func (h *ManageConsentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
requestSession, err := GetSession(r)
if err != nil {
h.logger.WithError(err).Error("could not get session for request")
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
@ -202,7 +204,7 @@ func (h *ManageConsentHandler) getConsentSessions(
) ([]client.OAuth2ConsentSession, bool) {
sessions, response, err := h.adminAPI.ListOAuth2ConsentSessions(r.Context()).Subject(subject).Execute()
if err != nil {
h.logger.WithError(err).Error("error getting consent session list")
h.logger.Error("error getting consent session list", "error", err)
// h.fillAcceptLoginRequestErrorBucket(r, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -212,13 +214,14 @@ func (h *ManageConsentHandler) getConsentSessions(
defer func(response *http.Response) { _ = response.Body.Close() }(response)
h.logger.WithFields(
log.Fields{"response": response.Status, "consent_sessions": sessions},
).Debug("got response for AcceptOAuth2LoginRequest")
h.logger.Debug(
"got response for AcceptOAuth2LoginRequest",
"response", response.Status, "consent_sessions", sessions,
)
if h.logger.IsLevelEnabled(log.TraceLevel) {
if h.logger.Enabled(r.Context(), slog.LevelDebug) {
if rb, err := io.ReadAll(response.Body); err == nil {
h.logger.WithField("response_body", rb).Trace("response body from Hydra")
h.logger.Debug("response body from Hydra", "response_body", rb)
}
}
@ -226,15 +229,15 @@ func (h *ManageConsentHandler) getConsentSessions(
}
func NewManageConsent(
logger *log.Logger, tc TemplateCache, trans *services.I18NService, adminAPI client.OAuth2Api,
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 *log.Logger
logger *slog.Logger
trans *services.I18NService
adminAPI client.OAuth2Api
adminAPI client.OAuth2API
templates TemplateCache
}
@ -272,7 +275,7 @@ func (h *RevokeConsentHandler) handleGet(
) {
clientApp, found, err := h.getClient(r.Context(), clientID)
if err != nil {
h.logger.WithError(err).Error("could not get client")
h.logger.Error("could not get client", "error", err)
http.Error(
w, h.trans.LookupHTTPErrorMessage(http.StatusInternalServerError, localizer),
@ -308,7 +311,7 @@ func (h *RevokeConsentHandler) handlePost(
) {
err := h.revokeConsent(r.Context(), clientID, subject)
if err != nil {
h.logger.WithError(err).Error("could not revoke consent")
h.logger.Error("could not revoke consent", "error", err)
http.Error(
w, h.trans.LookupHTTPErrorMessage(http.StatusInternalServerError, localizer),
@ -329,13 +332,14 @@ func (h *RevokeConsentHandler) getClient(ctx context.Context, clientID string) (
defer func(response *http.Response) { _ = response.Body.Close() }(response)
h.logger.WithFields(
log.Fields{"response": response.Status, "client_app": clientApp},
).Debug("got response for GetOAuth2Client")
h.logger.Debug(
"got response for GetOAuth2Client",
"response", response.Status, "client_app", clientApp,
)
if h.logger.IsLevelEnabled(log.TraceLevel) {
if h.logger.Enabled(ctx, slog.LevelDebug) {
if rb, err := io.ReadAll(response.Body); err == nil {
h.logger.WithField("response_body", rb).Trace("response body from Hydra")
h.logger.Debug("response body from Hydra", "response_body", rb)
}
}
@ -350,13 +354,11 @@ func (h *RevokeConsentHandler) revokeConsent(ctx context.Context, clientID, subj
defer func(response *http.Response) { _ = response.Body.Close() }(response)
h.logger.WithFields(
log.Fields{"response": response.Status},
).Debug("got response for RevokeOAuth2ConsentSessions")
h.logger.Debug("got response for RevokeOAuth2ConsentSessions", "response", response.Status)
if h.logger.IsLevelEnabled(log.TraceLevel) {
if h.logger.Enabled(ctx, slog.LevelDebug) {
if rb, err := io.ReadAll(response.Body); err == nil {
h.logger.WithField("response_body", rb).Trace("response body from Hydra")
h.logger.Debug("response body from Hydra", "response_body", rb)
}
}
@ -364,7 +366,7 @@ func (h *RevokeConsentHandler) revokeConsent(ctx context.Context, clientID, subj
}
func NewRevokeConsent(
logger *log.Logger, tc TemplateCache, trans *services.I18NService, adminAPI client.OAuth2Api,
logger *slog.Logger, tc TemplateCache, trans *services.I18NService, adminAPI client.OAuth2API,
) *RevokeConsentHandler {
return &RevokeConsentHandler{logger: logger, trans: trans, adminAPI: adminAPI, templates: tc}
}

View file

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

View file

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 CAcert Inc.
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
@ -20,23 +20,23 @@ package handlers
import (
"crypto/x509"
"fmt"
"log/slog"
"net/http"
"time"
log "github.com/sirupsen/logrus"
)
func EnableHSTS() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
const Days180 = 180
w.Header().Set("Strict-Transport-Security", fmt.Sprintf("max-age=%d", int((time.Hour*24*Days180).Seconds())))
next.ServeHTTP(w, r)
})
}
}
func getDataFromClientCert(logger *log.Logger, r *http.Request) (string, []string) {
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]
@ -45,9 +45,7 @@ func getDataFromClientCert(logger *log.Logger, r *http.Request) (string, []strin
}
for _, email := range firstCert.EmailAddresses {
logger.WithField(
"email", email,
).Info("authenticated with a client certificate for email address")
logger.Info("authenticated with a client certificate for email address", "email", email)
}
return firstCert.Subject.CommonName, firstCert.EmailAddresses

View file

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

View file

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 CAcert Inc.
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
@ -19,6 +19,7 @@ package services
import (
"fmt"
"log/slog"
"os"
"strings"
@ -28,7 +29,6 @@ import (
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/posflag"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
)
@ -51,7 +51,7 @@ var DefaultConfig = map[string]interface{}{
}
func ConfigureApplication(
logger *logrus.Logger,
logger *slog.Logger,
appName string,
defaultConfig map[string]interface{},
) (*koanf.Koanf, error) {
@ -70,7 +70,7 @@ func ConfigureApplication(
var err error
if err = f.Parse(os.Args[1:]); err != nil {
logger.WithError(err).Fatal("could not parse command line arguments")
return nil, fmt.Errorf("could not parse command line arguments: %w", err)
}
config := koanf.New(".")
@ -78,18 +78,22 @@ func ConfigureApplication(
_ = config.Load(confmap.Provider(defaultConfig, "."), nil)
if err = config.Load(file.Provider(defaultFile), toml.Parser()); err != nil && !os.IsNotExist(err) {
logrus.WithError(err).WithField("file", defaultFile).Fatal("error loading configuration from file")
logger.Error("could not load configuration from file", "file", defaultFile)
return nil, fmt.Errorf("error loading configuration: %w", err)
}
cFiles, _ := f.GetStringSlice("conf")
for _, c := range cFiles {
if err = config.Load(file.Provider(c), toml.Parser()); err != nil {
logger.WithError(err).WithField("file", c).Fatal("error loading configuration from file")
logger.Error("could not load configuration from file", "file", c)
return nil, fmt.Errorf("error loading configuration: %w", err)
}
}
if err = config.Load(posflag.Provider(f, ".", config), nil); err != nil {
logger.WithError(err).Fatal("error loading configuration from command line")
return nil, fmt.Errorf("error loading configuration from command line: %w", err)
}
prefix := fmt.Sprintf("%s_", strings.ToUpper(appName))
@ -97,7 +101,7 @@ func ConfigureApplication(
if err = config.Load(env.Provider(prefix, ".", func(s string) string {
return strings.ReplaceAll(strings.ToLower(strings.TrimPrefix(s, prefix)), "_", ".")
}), nil); err != nil {
logrus.WithError(err).Fatal("error loading configuration from environment")
return nil, fmt.Errorf("error loading configuration from environment: %w", err)
}
return config, nil

View file

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 CAcert Inc.
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
@ -21,9 +21,9 @@ import (
"bytes"
"errors"
"fmt"
"log/slog"
"net/http"
log "github.com/sirupsen/logrus"
"github.com/yuin/goldmark"
"code.cacert.org/cacert/oidc-idp/translations"
@ -35,7 +35,7 @@ import (
type MessageCatalog struct {
messages map[string]*i18n.Message
logger *log.Logger
logger *slog.Logger
}
func (m *MessageCatalog) AddMessages(messages map[string]*i18n.Message) {
@ -61,7 +61,7 @@ func (s *I18NService) LookupMessage(
return translation
}
s.logger.WithField("id", id).Warn("no translation found for id")
s.logger.Warn("no translation found for id", "id", id)
return id
}
@ -88,7 +88,7 @@ func (s *I18NService) LookupMarkdownMessage(
TemplateData: templateData,
})
if err != nil {
s.logger.WithError(err).Warn(err)
s.logger.Warn("message localization failed", "error", err, "message", message)
}
if translation == "" {
@ -105,7 +105,7 @@ func (s *I18NService) LookupMarkdownMessage(
return buf.String()
}
s.logger.WithField("id", id).Warn("no translation found for id")
s.logger.Warn("no translation found for id", "id", id)
return id
}
@ -129,7 +129,7 @@ func (s *I18NService) LookupMessagePlural(
return translation
}
s.logger.WithField("id", id).Warn("no translation found for id")
s.logger.Warn("no translation found for id", "id", id)
return id
}
@ -138,20 +138,20 @@ func (m *MessageCatalog) handleLocalizeError(id string, translation string, err
var messageNotFound *i18n.MessageNotFoundErr
if errors.As(err, &messageNotFound) {
m.logger.WithError(err).WithField("message", id).Warn("message not found")
m.logger.Warn("message not found", "error", err, "message", id)
if translation != "" {
return translation
}
} else {
m.logger.WithError(err).WithField("message", id).Error("translation error")
m.logger.Error("translation error", "error", err, "message", id)
}
return id
}
type I18NService struct {
logger *log.Logger
logger *slog.Logger
bundle *i18n.Bundle
catalog *MessageCatalog
}
@ -181,6 +181,16 @@ func (s *I18NService) AddMessages() error {
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",
@ -247,13 +257,11 @@ func (s *I18NService) AddMessages() error {
}
messages["IntroConsentRequested"] = &i18n.Message{
ID: "IntroConsentRequested",
Other: "The <strong>{{ .client }}</strong> application requested your consent for the following set of " +
"permissions:",
Other: "The **{{ .client }}** application requested your consent for the following set of permissions:",
}
messages["IntroConsentMoreInformation"] = &i18n.Message{
ID: "IntroConsentMoreInformation",
Other: "You can find more information about <strong>{{ .client }}</strong> at " +
"<a href=\"{{ .clientLink }}\">its description page</a>.",
Other: "You can find more information about **{{ .client }}** at [its description page]({{ .clientLink }}).",
}
messages["ClaimsInformation"] = &i18n.Message{
ID: "ClaimsInformation",
@ -265,7 +273,7 @@ func (s *I18NService) AddMessages() error {
}
messages["CertLoginIntroText"] = &i18n.Message{
ID: "CertLoginIntroText",
Other: "The application <strong>{{ .ClientName }}</strong> requests a login.",
Other: "The application **{{ .ClientName }}** requests a login.",
}
messages["EmailChoiceText"] = &i18n.Message{
ID: "EmailChoiceText",
@ -304,6 +312,10 @@ func (s *I18NService) AddMessages() error {
ID: "LoginDeniedByUser",
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",
@ -324,6 +336,10 @@ func (s *I18NService) AddMessages() error {
ID: "HintChooseAnIdentityForAuthentication",
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.",
@ -361,7 +377,7 @@ func (s *I18NService) Localizer(languages string) *i18n.Localizer {
return i18n.NewLocalizer(s.bundle, languages)
}
func InitI18n(logger *log.Logger, languages []string) *I18NService {
func InitI18n(logger *slog.Logger, languages []string) *I18NService {
bundle := i18n.NewBundle(language.English)
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
@ -370,7 +386,7 @@ func InitI18n(logger *log.Logger, languages []string) *I18NService {
bundleBytes, err := translations.Bundles.ReadFile(bundleName)
if err != nil {
logger.WithField("bundle", bundleName).Warn("message bundle not found")
logger.Warn("message bundle not found", "bundle", bundleName)
continue
}
@ -383,7 +399,7 @@ func InitI18n(logger *log.Logger, languages []string) *I18NService {
return &I18NService{logger: logger, bundle: bundle, catalog: catalog}
}
func initMessageCatalog(logger *log.Logger) *MessageCatalog {
func initMessageCatalog(logger *slog.Logger) *MessageCatalog {
messages := make(map[string]*i18n.Message)
messages["ErrorTitle"] = &i18n.Message{
ID: "ErrorTitle",

View file

@ -1,5 +1,5 @@
/*
Copyright 2023 CAcert Inc.
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
@ -19,21 +19,25 @@ package services
import (
"crypto/rand"
log "github.com/sirupsen/logrus"
"errors"
"log/slog"
)
func GenerateKey(length int) []byte {
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 {
log.WithError(err).Fatal("could not generate key")
return nil, errCouldNotGenerateKey
}
if read != length {
log.WithFields(log.Fields{"read": read, "expected": length}).Fatal("read unexpected number of bytes")
slog.Error("read unexpected number of bytes", "read", read, "expected", length)
return nil, errCouldNotGenerateKey
}
return key
return key, nil
}

View file

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

View file

@ -16,14 +16,24 @@ 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]
hash = "sha1-e9f7c0522e49ffacc49e3fc35c6ffd31e495baf6"
other = "Die Anwendung <strong>{{ .ClientName }}</strong> fragt nach einer Anmeldung."
hash = "sha1-02871569c8d36e522cdb0716081ef62b7c7a71ec"
other = "Die Anwendung **{{ .ClientName }}** fragt nach einer Anmeldung."
[CertLoginRequestText]
hash = "sha1-1b20eea0f6fbb4ff139ecfe6b7a93c98cb14b8d7"
@ -83,6 +93,10 @@ other = "Unbekannter Fehler"
hash = "sha1-7ee5b067009bbedc061274ee50a3027b50a06163"
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"
@ -92,12 +106,12 @@ hash = "sha1-00632c6562df53c62861c33e468e729887816419"
other = "Besuche [die Freigabeverwaltung]({{ .ManageConsentHRef }}), um deine Freigaben für Applikationen einzusehen oder zu widerrufen."
[IntroConsentMoreInformation]
hash = "sha1-f58b8378238bd433deef3c3e6b0b70d0fd0dd59e"
other = "Auf der <a href=\"{{ .clientLink }}\">Beschreibungsseite</a> findest du mehr Informationen zu <strong>{{ .client }}</strong>."
hash = "sha1-836b2931b417e98db4102731604443ee2700123c"
other = "Auf der [Beschreibungsseite]({{ .clientLink }}) findest du mehr Informationen zu **{{ .client }}**."
[IntroConsentRequested]
hash = "sha1-3ac6a3583d40b5e8930c57531f0be9706f1e0194"
other = "Die Anwendung <strong>{{ .client }}</strong> hat deine Zustimmung für die Erteilung der folgenden Berechtigungen angefragt:"
hash = "sha1-db5e67a16c181c4d27baf5d0e3bf255224b0fffc"
other = "Die Anwendung **{{ .client }}** hat deine Zustimmung für die Erteilung der folgenden Berechtigungen angefragt:"
[LabelAcceptCertLogin]
description = "Label for a button to accept certificate login"
@ -129,6 +143,10 @@ other = "Unbekannt"
hash = "sha1-bbad650536bfb091ad55d576262bbe4358277c73"
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]
hash = "sha1-9a24c8b64e047edc13f3c41ef7785bb2044a6d69"
other = "Anmelden mit einem Client-Zertifikat"

View file

@ -1,6 +1,6 @@
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."
AuthServerErrorTitle = "Authorization server returned an error"
CertLoginIntroText = "The application <strong>{{ .ClientName }}</strong> requests a login."
CertLoginIntroText = "The application **{{ .ClientName }}** requests a login."
CertLoginRequestText = "Do you want to use the chosen identity from the certificate for authentication?"
ClaimsInformation = "In addition the application wants access to the following information:"
ConfirmRevokeExplanation = "Do you want to revoke your consent to allow **{{ .Application }}** access to identity data for **{{ .Subject }}**?"
@ -8,15 +8,17 @@ ConfirmRevokeTitle = "Revoke consent"
ErrorTitle = "An error has occurred"
ErrorUnknown = "Unknown error"
HintChooseAnIdentityForAuthentication = "Choose an identity for authentication."
HintChooseDifferentClientCertificate = "Choose a different client certificate for authentication."
IndexTitle = "Welcome to your identity provider"
IndexWelcomeMessage = "Go to [manage consent]({{ .ManageConsentHRef }}) to show or revoke consent you have given to client applications."
IntroConsentMoreInformation = "You can find more information about <strong>{{ .client }}</strong> at <a href=\"{{ .clientLink }}\">its description page</a>."
IntroConsentRequested = "The <strong>{{ .client }}</strong> application requested your consent for the following set of permissions:"
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."
LabelNever = "Never"
LabelSubmit = "Submit"
LabelUnknown = "Unknown"
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"
LogoutSuccessfulText = "You have been logged out successfully."
LogoutSuccessfulTitle = "Logout successful"
@ -43,6 +45,14 @@ other = "Cancel"
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"

View file

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

View file

@ -33,7 +33,7 @@
{{ template "content" . }}
<footer class="footer mt-auto py-3">
<div class="container">
<span class="text-muted small">© 2020-2023 <a href="https://www.cacert.org/">CAcert</a></span>
<span class="text-muted small">© <a href="https://www.cacert.org/">CAcert</a></span>
</div>
</footer>
<script type="text/javascript" src="/js/cacert.bundle.js"></script>

View file

@ -1,7 +1,7 @@
{{ define "content" }}
<main role="main" class="container">
<h1>{{ .Title }}</h1>
<p>{{ .IntroText }}</p>
{{ .IntroText }}
{{ with .FlashMessage }}
<div class="alert alert-{{ .Type }}" role="alert">
{{ .Message }}

View file

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

View file

@ -1,10 +1,9 @@
{{ define "content" }}
<div class="container">
<img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4">
<main class="container">
<h1>{{ .Title }}</h1>
{{ .Explanation }}
{{ if .ErrorMessage }}
<div class="alert alert-danger">{{ .ErrorMessage }}</div>
{{ end }}
</div>
</main>
{{ end }}

View file

@ -1,3 +1,20 @@
/*
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
import "embed"